/* * 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.view; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.FoldRegion; import com.intellij.openapi.editor.SoftWrap; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.impl.SoftWrapModelImpl; import org.jetbrains.annotations.NotNull; import java.util.List; public class VisualLinesIterator { private final EditorImpl myEditor; private final Document myDocument; private final FoldRegion[] myFoldRegions; private final List<? extends SoftWrap> mySoftWraps; @NotNull private Location myLocation; private Location myNextLocation; public VisualLinesIterator(@NotNull EditorImpl editor, int startVisualLine) { myEditor = editor; SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel(); myDocument = myEditor.getDocument(); FoldRegion[] regions = myEditor.getFoldingModel().fetchTopLevel(); myFoldRegions = regions == null ? FoldRegion.EMPTY_ARRAY : regions; mySoftWraps = softWrapModel.getRegisteredSoftWraps(); myLocation = new Location(startVisualLine); } public boolean atEnd() { return myLocation.atEnd(); } public void advance() { checkEnd(); if (myNextLocation == null) { myLocation.advance(); } else { myLocation = myNextLocation; myNextLocation = null; } } public int getVisualLine() { checkEnd(); return myLocation.visualLine; } public int getVisualLineStartOffset() { checkEnd(); return myLocation.offset; } public int getVisualLineEndOffset() { checkEnd(); if (myNextLocation == null) { myNextLocation = myLocation.clone(); myNextLocation.advance(); } return myNextLocation.atEnd() ? myDocument.getTextLength() : myNextLocation.softWrap == myLocation.softWrap ? myDocument.getLineEndOffset(myNextLocation.logicalLine - 2) : myNextLocation.offset; } public int getStartLogicalLine() { checkEnd(); return myLocation.logicalLine - 1; } public int getStartOrPrevWrapIndex() { checkEnd(); return myLocation.softWrap - 1; } public int getStartFoldingIndex() { checkEnd(); return myLocation.foldRegion; } public int getY() { checkEnd(); return myLocation.y; } private void checkEnd() { if (atEnd()) throw new IllegalStateException("Iteration finished"); } private final class Location implements Cloneable { private final int lineHeight; // editor's line height private int visualLine; // current visual line private int offset; // start offset of the current visual line private int logicalLine = 1; // 1 + start logical line of the current visual line private int foldRegion; // index of the first folding region on current or following visual lines private int softWrap; // index of the first soft wrap after the start of current visual line private int y; // y coordinate of visual line's top private Location(int startVisualLine) { lineHeight = myEditor.getLineHeight(); if (startVisualLine < 0 || startVisualLine >= myEditor.getVisibleLineCount()) { offset = -1; } else if (startVisualLine > 0) { visualLine = startVisualLine; offset = myEditor.visualLineStartOffset(startVisualLine); logicalLine = myDocument.getLineNumber(offset) + 1; softWrap = myEditor.getSoftWrapModel().getSoftWrapIndex(offset) + 1; if (softWrap <= 0) { softWrap = -softWrap; } foldRegion = myEditor.getFoldingModel().getLastCollapsedRegionBefore(offset) + 1; y = myEditor.visibleLineToY(startVisualLine); } } private void advance() { int nextWrapOffset = getNextSoftWrapOffset(); offset = getNextVisualLineStartOffset(nextWrapOffset); if (offset == Integer.MAX_VALUE) { offset = -1; } else if (offset == nextWrapOffset) { softWrap++; } visualLine++; while (foldRegion < myFoldRegions.length && myFoldRegions[foldRegion].getStartOffset() < offset) foldRegion++; y += lineHeight; } private int getNextSoftWrapOffset() { return softWrap < mySoftWraps.size() ? mySoftWraps.get(softWrap).getStart() : Integer.MAX_VALUE; } private int getNextVisualLineStartOffset(int nextWrapOffset) { while (logicalLine < myDocument.getLineCount()) { int lineStartOffset = myDocument.getLineStartOffset(logicalLine); if (lineStartOffset > nextWrapOffset) return nextWrapOffset; logicalLine++; if (!isCollapsed(lineStartOffset)) return lineStartOffset; } return nextWrapOffset; } private boolean isCollapsed(int offset) { while (foldRegion < myFoldRegions.length) { FoldRegion region = myFoldRegions[foldRegion]; if (offset <= region.getStartOffset()) return false; if (offset <= region.getEndOffset()) return true; foldRegion++; } return false; } private boolean atEnd() { return offset == -1; } @Override protected Location clone() { try { return (Location)super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } } }