blob: 4f6594faa009b9088a60055a3b33c72db18d0a99 [file] [log] [blame]
/*
* Copyright 2000-2010 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.softwrap;
import com.intellij.openapi.editor.SoftWrap;
import com.intellij.openapi.editor.TextChange;
import com.intellij.openapi.editor.ex.SoftWrapChangeListener;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Holds registered soft wraps and provides monitoring and management facilities for them.
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since Jun 29, 2010 3:04:20 PM
*/
public class SoftWrapsStorage {
private final List<SoftWrapImpl> myWraps = new ArrayList<SoftWrapImpl>();
private final List<SoftWrapImpl> myWrapsView = Collections.unmodifiableList(myWraps);
private final List<SoftWrapChangeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
/**
* @return <code>true</code> if there is at least one soft wrap registered at the current storage; <code>false</code> otherwise
*/
public boolean isEmpty() {
return myWraps.isEmpty();
}
@Nullable
public SoftWrap getSoftWrap(int offset) {
int i = getSoftWrapIndex(offset);
return i >= 0 ? myWraps.get(i) : null;
}
/**
* @return view for registered soft wraps sorted by offset in ascending order if any; empty collection otherwise
*/
@NotNull
public List<SoftWrapImpl> getSoftWraps() {
return myWrapsView;
}
/**
* Tries to find index of the target soft wrap stored at {@link #myWraps} collection. <code>'Target'</code> soft wrap is the one
* that starts at the given offset.
*
* @param offset target offset
* @return index that conforms to {@link Collections#binarySearch(List, Object)} contract, i.e. non-negative returned
* index points to soft wrap that starts at the given offset; <code>'-(negative value) - 1'</code> points
* to position at {@link #myWraps} collection where soft wrap for the given index should be inserted
*/
public int getSoftWrapIndex(int offset) {
int start = 0;
int end = myWraps.size() - 1;
// We use custom inline implementation of binary search here because profiling shows that standard Collections.binarySearch()
// is a bottleneck. The most probable reason is a big number of interface calls.
while (start <= end) {
int i = (start + end) >>> 1;
SoftWrap softWrap = myWraps.get(i);
int softWrapOffset = softWrap.getStart();
if (softWrapOffset > offset) {
end = i - 1;
}
else if (softWrapOffset < offset) {
start = i + 1;
}
else {
return i;
}
}
return -(start + 1);
}
/**
* Allows to answer how many soft wraps which {@link TextChange#getStart() start offsets} belong to given
* <code>[start; end]</code> interval are registered withing the current storage.
*
* @param startOffset target start offset (inclusive)
* @param endOffset target end offset (inclusive)
* @return number of soft wraps which {@link TextChange#getStart() start offsets} belong to the target range
*/
public int getNumberOfSoftWrapsInRange(int startOffset, int endOffset) {
int startIndex = getSoftWrapIndex(startOffset);
if (startIndex < 0) {
startIndex = -startIndex - 1;
}
if (startIndex >= myWraps.size()) {
return 0;
}
int result = 0;
int endIndex = startIndex;
for (; endIndex < myWraps.size(); endIndex++) {
SoftWrap softWrap = myWraps.get(endIndex);
if (softWrap.getStart() > endOffset) {
break;
}
result++;
}
return result;
}
/**
* Inserts given soft wrap to {@link #myWraps} collection at the given index.
*
* @param softWrap soft wrap to store
* @param notifyListeners flag that indicates if registered listeners should be notified about soft wrap registration
* @return previous soft wrap object stored for the same offset if any; <code>null</code> otherwise
*/
@SuppressWarnings({"ForLoopReplaceableByForEach"})
@Nullable
public SoftWrap storeOrReplace(SoftWrapImpl softWrap, boolean notifyListeners) {
int i = getSoftWrapIndex(softWrap.getStart());
if (i >= 0) {
return myWraps.set(i, softWrap);
}
i = -i - 1;
myWraps.add(i, softWrap);
if (notifyListeners) {
// Use explicit loop as profiling shows that iterator-based processing is quite slow.
for (int j = 0; j < myListeners.size(); j++) {
SoftWrapChangeListener listener = myListeners.get(j);
listener.softWrapAdded(softWrap);
}
}
return null;
}
/**
* Allows to remove all soft wraps registered at the current storage with offsets from <code>[start; end)</code> range if any.
*
* @param startOffset start offset to use (inclusive)
* @param endOffset end offset to use (exclusive)
*/
public void removeInRange(int startOffset, int endOffset) {
//CachingSoftWrapDataMapper.log(String.format("xxxxxxxxxx SoftWrapsStorage.removeInRange(%d, %d). Current number: %d", startOffset, endOffset, myWraps.size()));
int startIndex = getSoftWrapIndex(startOffset);
if (startIndex < 0) {
startIndex = -startIndex - 1;
}
if (startIndex >= myWraps.size()) {
return;
}
int endIndex = startIndex;
for (; endIndex < myWraps.size(); endIndex++) {
SoftWrap softWrap = myWraps.get(endIndex);
if (softWrap.getStart() >= endOffset) {
break;
}
}
if (endIndex > startIndex) {
myWraps.subList(startIndex, endIndex).clear();
notifyListenersAboutRemoval();
}
//CachingSoftWrapDataMapper.log(String.format("xxxxxxxxxx SoftWrapsStorage.removeInRange(%d, %d). Remaining: %d", startOffset, endOffset, myWraps.size()));
}
/**
* Removes all soft wraps registered at the current storage.
*/
public void removeAll() {
myWraps.clear();
notifyListenersAboutRemoval();
}
/**
* Registers given listener within the current model
*
* @param listener listener to register
* @return <code>true</code> if given listener was not registered before; <code>false</code> otherwise
*/
public boolean addSoftWrapChangeListener(@NotNull SoftWrapChangeListener listener) {
return myListeners.add(listener);
}
private void notifyListenersAboutRemoval() {
for (SoftWrapChangeListener listener : myListeners) {
listener.softWrapsRemoved();
}
}
}