/* * 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.psi.impl.smartPointers; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.impl.FrozenDocument; import com.intellij.openapi.editor.impl.ManualRangeMarker; import com.intellij.openapi.editor.impl.event.DocumentEventImpl; import com.intellij.openapi.editor.impl.event.RetargetRangeMarkers; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UnfairTextRange; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * @author peter */ class MarkerCache { static final Comparator<SelfElementInfo> INFO_COMPARATOR = (info1, info2) -> { int o1 = info1.getPsiStartOffset(); int o2 = info2.getPsiStartOffset(); if (o1 < 0 || o2 < 0) return o1 >= 0 ? -1 : o2 >= 0 ? 1 : 0; // infos without range go after infos with range if (o1 != o2) return o1 > o2 ? 1 : -1; o1 = info1.getPsiEndOffset(); o2 = info2.getPsiEndOffset(); if (o1 != o2) return o1 > o2 ? 1 : -1; return (info1.isGreedy() ? 1 : 0) - (info2.isGreedy() ? 1 : 0); }; private final SmartPointerTracker myPointers; private UpdatedRanges myUpdatedRanges; MarkerCache(SmartPointerTracker pointers) { myPointers = pointers; } private UpdatedRanges getUpdatedMarkers(@NotNull FrozenDocument frozen, @NotNull List<DocumentEvent> events) { int eventCount = events.size(); assert eventCount > 0; UpdatedRanges cache = myUpdatedRanges; if (cache != null && cache.myEventCount == eventCount) return cache; UpdatedRanges answer; if (cache != null && cache.myEventCount < eventCount) { // apply only the new events answer = applyEvents(events.subList(cache.myEventCount, eventCount), cache); } else { List<SelfElementInfo> infos = myPointers.getSortedInfos(); ManualRangeMarker[] markers = createMarkers(infos); answer = applyEvents(events, new UpdatedRanges(0, frozen, infos, markers)); } myUpdatedRanges = answer; return answer; } @NotNull private static ManualRangeMarker[] createMarkers(List<SelfElementInfo> infos) { ManualRangeMarker[] markers = new ManualRangeMarker[infos.size()]; int i = 0; while (i < markers.length) { SelfElementInfo info = infos.get(i); boolean greedy = info.isGreedy(); int start = info.getPsiStartOffset(); int end = info.getPsiEndOffset(); markers[i] = new ManualRangeMarker(start, end, greedy, greedy, !greedy, null); i++; while (i < markers.length && rangeEquals(infos.get(i), start, end, greedy)) { markers[i] = markers[i - 1]; i++; } } return markers; } private static boolean rangeEquals(SelfElementInfo info, int start, int end, boolean greedy) { return start == info.getPsiStartOffset() && end == info.getPsiEndOffset() && greedy == info.isGreedy(); } private static UpdatedRanges applyEvents(@NotNull List<DocumentEvent> events, final UpdatedRanges struct) { FrozenDocument frozen = struct.myResultDocument; ManualRangeMarker[] resultMarkers = struct.myMarkers.clone(); for (DocumentEvent event : events) { final FrozenDocument before = frozen; final DocumentEvent corrected; if (event instanceof RetargetRangeMarkers) { RetargetRangeMarkers retarget = (RetargetRangeMarkers)event; corrected = new RetargetRangeMarkers(frozen, retarget.getStartOffset(), retarget.getEndOffset(), retarget.getMoveDestinationOffset()); } else { corrected = new DocumentEventImpl(frozen, event.getOffset(), event.getOldFragment(), event.getNewFragment(), event.getOldTimeStamp(), event.isWholeTextReplaced(), ((DocumentEventImpl) event).getInitialStartOffset(), ((DocumentEventImpl) event).getInitialOldLength()); frozen = frozen.applyEvent(event, 0); } int i = 0; while (i < resultMarkers.length) { ManualRangeMarker currentRange = resultMarkers[i]; int sameMarkersEnd = i + 1; while (sameMarkersEnd < resultMarkers.length && resultMarkers[sameMarkersEnd] == currentRange) { sameMarkersEnd++; } ManualRangeMarker updatedRange = currentRange == null ? null : currentRange.getUpdatedRange(corrected, before); while (i < sameMarkersEnd) { resultMarkers[i] = updatedRange; i++; } } } return new UpdatedRanges(struct.myEventCount + events.size(), frozen, struct.mySortedInfos, resultMarkers); } boolean updateMarkers(@NotNull FrozenDocument frozen, @NotNull List<DocumentEvent> events) { UpdatedRanges updated = getUpdatedMarkers(frozen, events); boolean sorted = true; for (int i = 0; i < updated.myMarkers.length; i++) { SelfElementInfo info = updated.mySortedInfos.get(i); info.setRange(updated.myMarkers[i]); if (sorted && i > 0 && INFO_COMPARATOR.compare(updated.mySortedInfos.get(i - 1), info) > 0) { sorted = false; } } myUpdatedRanges = null; return sorted; } @Nullable TextRange getUpdatedRange(@NotNull SelfElementInfo info, @NotNull FrozenDocument frozen, @NotNull List<DocumentEvent> events) { UpdatedRanges struct = getUpdatedMarkers(frozen, events); int i = Collections.binarySearch(struct.mySortedInfos, info, INFO_COMPARATOR); ManualRangeMarker updated = i >= 0 ? struct.myMarkers[i] : null; return updated == null ? null : new UnfairTextRange(updated.getStartOffset(), updated.getEndOffset()); } void rangeChanged() { myUpdatedRanges = null; } private static class UpdatedRanges { private final int myEventCount; private final FrozenDocument myResultDocument; private final List<SelfElementInfo> mySortedInfos; private final ManualRangeMarker[] myMarkers; public UpdatedRanges(int eventCount, FrozenDocument resultDocument, List<SelfElementInfo> sortedInfos, ManualRangeMarker[] markers) { myEventCount = eventCount; myResultDocument = resultDocument; mySortedInfos = sortedInfos; myMarkers = markers; } } }