/*
* 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.softwrap.mapping;
import com.intellij.diagnostic.Dumpable;
import com.intellij.openapi.diagnostic.Attachment;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.TextChangeImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapImpl;
import com.intellij.openapi.editor.impl.softwrap.SoftWrapsStorage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CachingSoftWrapDataMapper implements SoftWrapAwareDocumentParsingListener, Dumpable {
private static final Logger LOG = Logger.getInstance("#" + CachingSoftWrapDataMapper.class.getName());
private final List<SoftWrapImpl> myAffectedByUpdateSoftWraps = new ArrayList<>();
private final EditorEx myEditor;
private final SoftWrapsStorage myStorage;
public CachingSoftWrapDataMapper(@NotNull EditorEx editor, @NotNull SoftWrapsStorage storage)
{
myEditor = editor;
myStorage = storage;
}
boolean matchesOldSoftWrap(SoftWrap newSoftWrap, int lengthDiff) {
return Collections.binarySearch(myAffectedByUpdateSoftWraps, new SoftWrapImpl(new TextChangeImpl(newSoftWrap.getText(),
newSoftWrap.getStart() - lengthDiff,
newSoftWrap.getEnd() - lengthDiff),
newSoftWrap.getIndentInColumns(),
newSoftWrap.getIndentInPixels()),
(o1, o2) -> {
int offsetDiff = o1.getStart() - o2.getStart();
if (offsetDiff != 0) {
return offsetDiff;
}
int textDiff = o1.getText().toString().compareTo(o2.getText().toString());
if (textDiff != 0) {
return textDiff;
}
int colIndentDiff = o1.getIndentInColumns() - o2.getIndentInColumns();
if (colIndentDiff != 0) {
return colIndentDiff;
}
return o1.getIndentInPixels() - o2.getIndentInPixels();
}) >= 0;
}
@Override
public void recalculationEnds() {
}
@Override
public void onCacheUpdateStart(@NotNull IncrementalCacheUpdateEvent event) {
int startOffset = event.getStartOffset();
myAffectedByUpdateSoftWraps.clear();
myAffectedByUpdateSoftWraps.addAll(myStorage.removeStartingFrom(startOffset + 1));
}
@Override
public void onRecalculationEnd(@NotNull IncrementalCacheUpdateEvent event) {
advanceSoftWrapOffsets(event);
}
@Override
public void reset() {
myAffectedByUpdateSoftWraps.clear();
}
/**
* Determines which soft wraps were not affected by recalculation, and shifts them to their new offsets.
*
* @return Change in soft wraps count after recalculation
*/
private void advanceSoftWrapOffsets(@NotNull IncrementalCacheUpdateEvent event) {
int lengthDiff = event.getLengthDiff();
int recalcEndOffsetTranslated = event.getActualEndOffset() - lengthDiff;
int firstIndex = -1;
int softWrappedLinesDiff = myStorage.getNumberOfSoftWrapsInRange(event.getStartOffset() + 1, myEditor.getDocument().getTextLength());
boolean softWrapsChanged = softWrappedLinesDiff > 0;
for (int i = 0; i < myAffectedByUpdateSoftWraps.size(); i++) {
SoftWrap softWrap = myAffectedByUpdateSoftWraps.get(i);
if (firstIndex < 0) {
if (softWrap.getStart() > recalcEndOffsetTranslated) {
firstIndex = i;
if (lengthDiff == 0) {
break;
}
} else {
softWrappedLinesDiff--;
softWrapsChanged = true;
}
}
if (firstIndex >= 0 && i >= firstIndex) {
((SoftWrapImpl)softWrap).advance(lengthDiff);
}
}
if (firstIndex >= 0) {
List<SoftWrapImpl> updated = myAffectedByUpdateSoftWraps.subList(firstIndex, myAffectedByUpdateSoftWraps.size());
SoftWrapImpl lastSoftWrap = getLastSoftWrap();
if (lastSoftWrap != null && lastSoftWrap.getStart() >= updated.get(0).getStart()) {
LOG.error("Invalid soft wrap recalculation", new Attachment("state.txt", myEditor.getSoftWrapModel().toString()));
}
myStorage.addAll(updated);
}
myAffectedByUpdateSoftWraps.clear();
if (softWrapsChanged) {
myStorage.notifyListenersAboutChange();
}
}
@Nullable
SoftWrapImpl getLastSoftWrap() {
List<SoftWrapImpl> softWraps = myStorage.getSoftWraps();
return softWraps.isEmpty() ? null : softWraps.get(softWraps.size() - 1);
}
@NotNull
@Override
public String dumpState() {
return "Soft wraps affected by current update: " + myAffectedByUpdateSoftWraps;
}
@Override
public String toString() {
return dumpState();
}
}