blob: 27362b52527ae816da272903cba7bbcfbc9c0a5e [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.openapi.editor.impl.softwrap.mapping;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.util.Ref;
import gnu.trove.TIntObjectHashMap;
import gnu.trove.TIntObjectProcedure;
import gnu.trove.TObjectProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Encapsulates information to cache for the single visual line.
*/
@SuppressWarnings("unchecked")
class CacheEntry implements Comparable<CacheEntry>, Cloneable {
private static final TIntObjectHashMap<FoldingData> DUMMY = new TIntObjectHashMap<FoldingData>();
public int visualLine;
public int startLogicalLine;
public int startLogicalColumn;
public int startOffset;
public int startSoftWrapLinesBefore;
public int startSoftWrapLinesCurrent;
public int startSoftWrapColumnDiff;
public int startFoldedLines;
public int startFoldingColumnDiff;
public int endOffset;
public int endLogicalLine;
public int endLogicalColumn;
public int endVisualColumn;
public int endSoftWrapLinesBefore;
public int endSoftWrapLinesCurrent;
public int endSoftWrapColumnDiff;
public int endFoldedLines;
public int endFoldingColumnDiff;
public boolean locked;
private final Editor myEditor;
/** Holds positions for the tabulation symbols on a target visual line sorted by offset in ascending order. */
private List<TabData> myTabPositions = Collections.EMPTY_LIST;
/** Holds information about single line fold regions representation data. */
private TIntObjectHashMap<FoldingData> myFoldingData = DUMMY;
CacheEntry(int visualLine, @NotNull Editor editor) {
this.visualLine = visualLine;
myEditor = editor;
}
public void setLineStartPosition(@NotNull EditorPosition context) {
assert context.visualColumn == 0;
startLogicalLine = context.logicalLine;
startLogicalColumn = context.logicalColumn;
visualLine = context.visualLine;
startOffset = context.offset;
startSoftWrapLinesBefore = context.softWrapLinesBefore;
startSoftWrapLinesCurrent = context.softWrapLinesCurrent;
startSoftWrapColumnDiff = context.softWrapColumnDiff;
startFoldedLines = context.foldedLines;
startFoldingColumnDiff = context.foldingColumnDiff;
}
public void setLineEndPosition(@NotNull EditorPosition position) {
endOffset = position.offset;
endLogicalLine = position.logicalLine;
endLogicalColumn = position.logicalColumn;
endVisualColumn = position.visualColumn;
endSoftWrapLinesBefore = position.softWrapLinesBefore;
endSoftWrapLinesCurrent = position.softWrapLinesCurrent;
endSoftWrapColumnDiff = position.softWrapColumnDiff;
endFoldedLines = position.foldedLines;
endFoldingColumnDiff = position.foldingColumnDiff;
}
public EditorPosition buildStartLinePosition() {
EditorPosition result = new EditorPosition(myEditor);
result.logicalLine = startLogicalLine;
result.logicalColumn = startLogicalColumn;
result.offset = startOffset;
result.visualLine = visualLine;
result.visualColumn = 0;
result.softWrapLinesBefore = startSoftWrapLinesBefore;
result.softWrapLinesCurrent = startSoftWrapLinesCurrent;
result.softWrapColumnDiff = startSoftWrapColumnDiff;
result.foldedLines = startFoldedLines;
result.foldingColumnDiff = startFoldingColumnDiff;
return result;
}
public EditorPosition buildEndLinePosition() {
EditorPosition result = new EditorPosition(myEditor);
result.logicalLine = endLogicalLine;
result.logicalColumn = endLogicalColumn;
result.offset = endOffset;
result.visualLine = visualLine;
result.visualColumn = endVisualColumn;
result.softWrapLinesBefore = endSoftWrapLinesBefore;
result.softWrapLinesCurrent = endSoftWrapLinesCurrent;
result.softWrapColumnDiff = endSoftWrapColumnDiff;
result.foldedLines = endFoldedLines;
result.foldingColumnDiff = endFoldingColumnDiff;
return result;
}
/**
* Removes fold data for all fold regions that start at or after the given offset.
*
* @param offset target offset
*/
public void removeAllFoldDataAtOrAfter(final int offset) {
if (myFoldingData == DUMMY || myFoldingData.isEmpty()) {
return;
}
myFoldingData.retainEntries(new TIntObjectProcedure<FoldingData>() {
@Override
public boolean execute(int a, FoldingData b) {
return a < offset;
}
});
}
@Nullable
public FoldingData getFoldingData(@NotNull final FoldRegion region) {
FoldingData candidate = myFoldingData.get(region.getStartOffset());
if (candidate != null) {
return candidate;
}
// Folding implementation is known to postpone actual fold region offsets update on document change, i.e. it performs
// fold data caching with its further replace by up-to-date info. Hence, there is a possible case that soft wraps processing
// advances fold region offset but folding model still provides old cached values. Hence, we're trying to match exact given
// fold region against the cached data here.
final Ref<FoldingData> result = new Ref<FoldingData>();
myFoldingData.forEachValue(new TObjectProcedure<FoldingData>() {
@Override
public boolean execute(FoldingData data) {
if (data.getFoldRegion().equals(region)) {
result.set(data);
return false;
}
return true;
}
});
return result.get();
}
public void store(FoldingData foldData, int offset) {
if (myFoldingData == DUMMY) {
myFoldingData = new TIntObjectHashMap<FoldingData>();
}
myFoldingData.put(offset, foldData);
}
public List<TabData> getTabData() {
return myTabPositions;
}
public void storeTabData(TabData tabData) {
if (myTabPositions == Collections.EMPTY_LIST) {
myTabPositions = new ArrayList<TabData>();
}
myTabPositions.add(tabData);
}
@SuppressWarnings("ForLoopReplaceableByForEach")
public void advance(final int offsetDiff) {
startOffset += offsetDiff;
endOffset += offsetDiff;
// 'For-each' loop is not used here because this code is called quite often and profile shows the Iterator usage here
// produces performance drawback.
for (int i = 0; i < myTabPositions.size(); i++) {
myTabPositions.get(i).offset += offsetDiff;
}
if (myFoldingData.isEmpty()) {
return;
}
final TIntObjectHashMap<FoldingData> newFoldingData = new TIntObjectHashMap<FoldingData>(myFoldingData.size());
myFoldingData.forEachEntry(new TIntObjectProcedure<FoldingData>() {
@Override
public boolean execute(int offset, FoldingData foldingData) {
newFoldingData.put(offset + offsetDiff, foldingData);
return true;
}
});
myFoldingData = newFoldingData;
}
/**
* Asks current entry to update its state in a way to set start line data to the end line data.
*/
public void collapse() {
endOffset = startOffset;
endLogicalLine = startLogicalLine;
endLogicalColumn = startLogicalColumn;
endVisualColumn = 0;
endSoftWrapLinesBefore = startSoftWrapLinesBefore;
endSoftWrapLinesCurrent = startSoftWrapLinesCurrent;
endSoftWrapColumnDiff = startSoftWrapColumnDiff;
endFoldedLines = startFoldedLines;
endFoldingColumnDiff = startFoldingColumnDiff;
}
@Override
public int compareTo(CacheEntry e) {
return visualLine - e.visualLine;
}
@Override
public String toString() {
return String.format(
"%d - visual line: %d, offsets: %d-%d, logical lines: %d-%d, logical columns: %d-%d, end visual column: %d, "
+ "fold regions: %s, tab data: %s",
System.identityHashCode(this), visualLine, startOffset, endOffset, startLogicalLine, endLogicalLine, startLogicalColumn,
endLogicalColumn, endVisualColumn, Arrays.toString(myFoldingData.getValues()), myTabPositions
);
}
@Override
protected CacheEntry clone() {
final CacheEntry result = new CacheEntry(visualLine, myEditor);
result.startLogicalLine = startLogicalLine;
result.startLogicalColumn = startLogicalColumn;
result.startOffset = startOffset;
result.startSoftWrapLinesBefore = startSoftWrapLinesBefore;
result.startSoftWrapLinesCurrent = startSoftWrapLinesCurrent;
result.startSoftWrapColumnDiff = startSoftWrapColumnDiff;
result.startFoldedLines = startFoldedLines;
result.startFoldingColumnDiff = startFoldingColumnDiff;
result.endOffset = endOffset;
result.endLogicalLine = endLogicalLine;
result.endLogicalColumn = endLogicalColumn;
result.endVisualColumn = endVisualColumn;
result.endSoftWrapLinesBefore = endSoftWrapLinesBefore;
result.endSoftWrapLinesCurrent = endSoftWrapLinesCurrent;
result.endSoftWrapColumnDiff = endSoftWrapColumnDiff;
result.endFoldedLines = endFoldedLines;
result.endFoldingColumnDiff = endFoldingColumnDiff;
myFoldingData.forEachEntry(new TIntObjectProcedure<FoldingData>() {
@Override
public boolean execute(int offset, FoldingData foldData) {
result.store(foldData, offset);
return true;
}
});
for (TabData tabPosition : myTabPositions) {
result.storeTabData(tabPosition);
}
return result;
}
}