/** * 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; }
/** * Every soft wrap implies that multiple visual positions correspond to the same document offset. We can classify * such positions by the following criteria: * <pre> * <ul> * <li>positions from visual line with soft wrap start;</li> * <li>positions from visual line with soft wrap end;</li> * </ul> * </pre> * <p/> * This method allows to answer if caret offset of the given editor points to soft wrap and visual caret position * belongs to the visual line where soft wrap end is located. * * @param editor target editor * @return <code>true</code> if caret offset of the given editor points to visual position that belongs to * visual line where soft wrap end is located */ public static boolean isCaretAfterSoftWrap(CaretImpl caret) { if (!caret.isUpToDate()) { return false; } EditorImpl editor = caret.getEditor(); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int offset = caret.getOffset(); SoftWrap softWrap = softWrapModel.getSoftWrap(offset); if (softWrap == null) { return false; } if (editor.myUseNewRendering) { VisualPosition afterWrapPosition = editor.offsetToVisualPosition(offset, false, false); VisualPosition caretPosition = caret.getVisualPosition(); return caretPosition.line == afterWrapPosition.line && caretPosition.column <= afterWrapPosition.column; } else { return editor.offsetToVisualLine(offset) == caret.getVisualPosition().line; } }
/** * 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; }
private static VisualLineInfo getVisualLineInfo(@Nonnull EditorImpl editor, int offset, boolean beforeSoftWrap) { Document document = editor.getDocument(); int textLength = document.getTextLength(); if (offset <= 0 || textLength == 0) return new VisualLineInfo(0, false); offset = Math.min(offset, textLength); int startOffset = EditorUtil.getNotFoldedLineStartOffset(editor, offset); SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); int wrapIndex = softWrapModel.getSoftWrapIndex(offset); int prevSoftWrapIndex = wrapIndex < 0 ? (- wrapIndex - 2) : wrapIndex - (beforeSoftWrap ? 1 : 0); SoftWrap prevSoftWrap = prevSoftWrapIndex < 0 ? null : softWrapModel.getRegisteredSoftWraps().get(prevSoftWrapIndex); int visualLineStartOffset = prevSoftWrap == null ? startOffset : Math.max(startOffset, prevSoftWrap.getStart()); return new VisualLineInfo(visualLineStartOffset, prevSoftWrap != null && prevSoftWrap.getStart() == visualLineStartOffset); }
/** * Inserts given soft wrap to {@link #myWraps} collection at the given index. * * @param softWrap soft wrap to store * @return previous soft wrap object stored for the same offset if any; <code>null</code> otherwise */ @SuppressWarnings({"ForLoopReplaceableByForEach"}) @Nullable public SoftWrap storeOrReplace(SoftWrapImpl softWrap) { int i = getSoftWrapIndex(softWrap.getStart()); if (i >= 0) { return myWraps.set(i, softWrap); } i = -i - 1; myWraps.add(i, 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())); }
private VisualLineFragmentsIterator(EditorView view, int offset, boolean beforeSoftWrap, @Nullable Runnable quickEvaluationListener) { EditorImpl editor = view.getEditor(); int visualLineStartOffset = EditorUtil.getNotFoldedLineStartOffset(editor, offset); SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); List<? extends SoftWrap> softWraps = softWrapModel.getRegisteredSoftWraps(); int currentOrPrevWrapIndex = softWrapModel.getSoftWrapIndex(offset); if (currentOrPrevWrapIndex < 0) { currentOrPrevWrapIndex = - currentOrPrevWrapIndex - 2; } else if (beforeSoftWrap) { currentOrPrevWrapIndex--; } SoftWrap currentOrPrevWrap = currentOrPrevWrapIndex < 0 || currentOrPrevWrapIndex >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex); if (currentOrPrevWrap != null && currentOrPrevWrap.getStart() > visualLineStartOffset) { visualLineStartOffset = currentOrPrevWrap.getStart(); } int nextFoldingIndex = editor.getFoldingModel().getLastCollapsedRegionBefore(visualLineStartOffset) + 1; init(view, visualLineStartOffset, editor.getDocument().getLineNumber(visualLineStartOffset), currentOrPrevWrapIndex, nextFoldingIndex, quickEvaluationListener); }
private void init(EditorView view, int startOffset, int startLogicalLine, int currentOrPrevWrapIndex, int nextFoldingIndex, @Nullable Runnable quickEvaluationListener) { myQuickEvaluationListener = quickEvaluationListener; myView = view; EditorImpl editor = view.getEditor(); myDocument = editor.getDocument(); FoldingModelEx foldingModel = editor.getFoldingModel(); FoldRegion[] regions = foldingModel.fetchTopLevel(); myRegions = regions == null ? FoldRegion.EMPTY_ARRAY : regions; SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); List<? extends SoftWrap> softWraps = softWrapModel.getRegisteredSoftWraps(); SoftWrap currentOrPrevWrap = currentOrPrevWrapIndex < 0 || currentOrPrevWrapIndex >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex); SoftWrap followingWrap = (currentOrPrevWrapIndex + 1) < 0 || (currentOrPrevWrapIndex + 1) >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex + 1); myVisualLineStartOffset = mySegmentStartOffset = startOffset; myCurrentFoldRegionIndex = nextFoldingIndex; myCurrentEndLogicalLine = startLogicalLine; myCurrentX = myView.getInsets().left; if (mySegmentStartOffset == 0) { myCurrentX += myView.getPrefixTextWidthInPixels(); } else if (currentOrPrevWrap != null && mySegmentStartOffset == currentOrPrevWrap.getStart()) { myCurrentX += currentOrPrevWrap.getIndentInPixels(); myCurrentVisualColumn = currentOrPrevWrap.getIndentInColumns(); } myNextWrapOffset = followingWrap == null ? Integer.MAX_VALUE : followingWrap.getStart(); setInlaysAndFragmentIterator(); }
public static void verifySoftWrapPositions(Integer... positions) { List<Integer> softWrapPositions = new ArrayList<>(); for(SoftWrap softWrap : myEditor.getSoftWrapModel().getSoftWrapsForRange(0, myEditor.getDocument().getTextLength())) { softWrapPositions.add(softWrap.getStart()); } assertArrayEquals(positions, softWrapPositions.toArray()); }
@Nullable @Override public SoftWrap getSoftWrap(int offset) { return null; }
@NotNull @Override public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) { return Collections.emptyList(); }
@NotNull @Override public List<? extends SoftWrap> getSoftWrapsForRange(int start, int end) { return Collections.emptyList(); }
@Override public boolean isVisible(SoftWrap softWrap) { return false; }
private VisualLineFragmentsIterator(EditorView view, int offset, boolean beforeSoftWrap, @Nullable Runnable quickEvaluationListener) { myQuickEvaluationListener = quickEvaluationListener; myView = view; EditorImpl editor = view.getEditor(); myDocument = editor.getDocument(); FoldingModelEx foldingModel = editor.getFoldingModel(); FoldRegion[] regions = foldingModel.fetchTopLevel(); myRegions = regions == null ? FoldRegion.EMPTY_ARRAY : regions; int visualLineStartOffset = EditorUtil.getNotFoldedLineStartOffset(editor, offset); SoftWrapModelImpl softWrapModel = editor.getSoftWrapModel(); List<? extends SoftWrap> softWraps = softWrapModel.getRegisteredSoftWraps(); int currentOrPrevWrapIndex = softWrapModel.getSoftWrapIndex(offset); if (currentOrPrevWrapIndex < 0) { currentOrPrevWrapIndex = - currentOrPrevWrapIndex - 2; } else if (beforeSoftWrap) { currentOrPrevWrapIndex--; } SoftWrap currentOrPrevWrap = currentOrPrevWrapIndex < 0 || currentOrPrevWrapIndex >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex); SoftWrap followingWrap = (currentOrPrevWrapIndex + 1) >= softWraps.size() ? null : softWraps.get(currentOrPrevWrapIndex + 1); if (currentOrPrevWrap != null && currentOrPrevWrap.getStart() > visualLineStartOffset) { visualLineStartOffset = currentOrPrevWrap.getStart(); } myVisualLineStartOffset = mySegmentStartOffset = visualLineStartOffset; myCurrentFoldRegionIndex = foldingModel.getLastCollapsedRegionBefore(mySegmentStartOffset) + 1; myCurrentEndLogicalLine = myDocument.getLineNumber(mySegmentStartOffset); if (mySegmentStartOffset == 0) { myCurrentX = myView.getPrefixTextWidthInPixels(); } else if (currentOrPrevWrap != null && mySegmentStartOffset == currentOrPrevWrap.getStart()) { myCurrentX = currentOrPrevWrap.getIndentInPixels(); myCurrentVisualColumn = currentOrPrevWrap.getIndentInColumns(); } myNextWrapOffset = followingWrap == null ? Integer.MAX_VALUE : followingWrap.getStart(); setFragmentIterator(); }
@Nullable @Override public Integer processSoftWrap(@NotNull EditorPosition position, SoftWrap softWrap) { return null; }
@Nullable public SoftWrap getSoftWrap(int offset) { int i = getSoftWrapIndex(offset); return i >= 0 ? myWraps.get(i) : null; }
private void doTestSoftWraps(int wrapWidth, String text) throws IOException { List<MyFoldRegion> foldRegions = new ArrayList<MyFoldRegion>(); List<Integer> wrapPositions = new ArrayList<Integer>(); int foldInsertPosition = 0; int pos = 0; int docPos = 0; Matcher matcher = Pattern.compile(TAGS_PATTERN).matcher(text); StringBuilder cleanedText = new StringBuilder(); while(matcher.find()) { cleanedText.append(text.substring(pos, matcher.start())); docPos += matcher.start() - pos; pos = matcher.end(); if (matcher.group(1) != null) { // <fold> foldRegions.add(foldInsertPosition++, new MyFoldRegion(docPos, matcher.group(3), matcher.group(2) != null)); } else if (matcher.group(4) != null) { // </fold> assertTrue("Misplaced closing fold marker tag: " + text, foldInsertPosition > 0); foldRegions.get(--foldInsertPosition).endPos = docPos; } else { // <wrap> wrapPositions.add(docPos); } } assertTrue("Missing closing fold marker tag: " + text, foldInsertPosition == 0); cleanedText.append(text.substring(pos)); init(cleanedText.toString(), TestFileType.TEXT); for (MyFoldRegion region : foldRegions) { FoldRegion r = addFoldRegion(region.startPos, region.endPos, region.placeholder); if (region.collapse) { toggleFoldRegionState(r, false); } } EditorTestUtil.configureSoftWraps(myEditor, wrapWidth); List<Integer> actualWrapPositions = new ArrayList<Integer>(); for (SoftWrap wrap : myEditor.getSoftWrapModel().getSoftWrapsForRange(0, myEditor.getDocument().getTextLength())) { actualWrapPositions.add(wrap.getStart()); } assertEquals("Wrong wrap positions", wrapPositions, actualWrapPositions); }
@Override public List<? extends SoftWrap> getRegisteredSoftWraps() { return Collections.emptyList(); }
@Override public void softWrapAdded(@NotNull SoftWrap softWrap) { }
@Nonnull @Override public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) { return Collections.emptyList(); }
@Nonnull @Override public List<? extends SoftWrap> getSoftWrapsForRange(int start, int end) { return Collections.emptyList(); }
/** * Every soft wrap implies that multiple visual positions correspond to the same document offset. We can classify * such positions by the following criteria: * <pre> * <ul> * <li>positions from visual line with soft wrap start;</li> * <li>positions from visual line with soft wrap end;</li> * </ul> * </pre> * <p/> * This method allows to answer if caret offset of the given editor points to soft wrap and visual caret position * belongs to the visual line where soft wrap end is located. * * @param editor target editor * @return <code>true</code> if caret offset of the given editor points to visual position that belongs to * visual line where soft wrap end is located */ public static boolean isCaretAfterSoftWrap(CaretImpl caret) { if (!caret.isUpToDate()) { return false; } EditorImpl editor = caret.getEditor(); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int offset = caret.getOffset(); SoftWrap softWrap = softWrapModel.getSoftWrap(offset); if (softWrap == null) { return false; } VisualPosition afterWrapPosition = editor.offsetToVisualPosition(offset, false, false); VisualPosition caretPosition = caret.getVisualPosition(); return caretPosition.line == afterWrapPosition.line && caretPosition.column <= afterWrapPosition.column; }