@Override public void run(boolean softWrapAware) { if (!softWrapAware) { return; } try { for (FoldRegionInfo info : myDeferredFoldRegions) { // There is a possible case that given fold region is contained inside another collapsed // fold region. We don't want to process // such nested region then. FoldRegion outerRegion = myEditor.getFoldingModel().getCollapsedRegionAtOffset(info.start); if (outerRegion != null && outerRegion != info.region && outerRegion.getStartOffset() <= info.start && outerRegion.getEndOffset() >= info.end) { continue; } for (SoftWrapFoldingListener listener : myFoldListeners) { listener.onFoldRegionStateChange(info.start, info.end); } } } finally { myDeferredFoldRegions.clear(); } for (SoftWrapFoldingListener listener : myFoldListeners) { listener.onFoldProcessingEnd(); } }
@Override public void onFoldRegionStateChange(@NotNull FoldRegion region) { if (myDocument.isInBulkUpdate()) return; if (region.isValid()) { myFoldingChangeStartOffset = Math.min(myFoldingChangeStartOffset, region.getStartOffset()); myFoldingChangeEndOffset = Math.max(myFoldingChangeEndOffset, region.getEndOffset()); } }
public int getEndOffset(@NotNull FoldingGroup group) { final List<FoldRegion> regions = getGroupedRegions(group); int endOffset = 0; for (FoldRegion region : regions) { if (region.isValid()) { endOffset = Math.max(endOffset, region.getEndOffset()); } } return endOffset; }
private static boolean isSelected(FoldRegion foldRegion) { int regionStart = foldRegion.getStartOffset(); int regionEnd = foldRegion.getEndOffset(); int[] selectionStarts = foldRegion.getEditor().getSelectionModel().getBlockSelectionStarts(); int[] selectionEnds = foldRegion.getEditor().getSelectionModel().getBlockSelectionEnds(); for (int i = 0; i < selectionStarts.length; i++) { int start = selectionStarts[i]; int end = selectionEnds[i]; if (regionStart >= start && regionEnd <= end) return true; } return false; }
@Override public void onFoldRegionStateChange(@NotNull FoldRegion region) { myUpdateInProgress = true; if (!isSoftWrappingEnabled() || !region.isValid()) { myDirty = true; return; } // We delay processing of changed fold regions till the invocation of onFoldProcessingEnd(), as // FoldingModel can return inconsistent data before that moment. myDeferredFoldRegions.add(new TextRange(region.getStartOffset(), region.getEndOffset())); }
@Override @SuppressWarnings({"AssignmentToForLoopParameter"}) public void paint( @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) { int startOffset = highlighter.getStartOffset(); final Document doc = highlighter.getDocument(); if (startOffset >= doc.getTextLength()) return; final int endOffset = highlighter.getEndOffset(); final int endLine = doc.getLineNumber(endOffset); int off; int startLine = doc.getLineNumber(startOffset); IndentGuideDescriptor descriptor = editor.getIndentsModel().getDescriptor(startLine, endLine); final CharSequence chars = doc.getCharsSequence(); do { int start = doc.getLineStartOffset(startLine); int end = doc.getLineEndOffset(startLine); off = CharArrayUtil.shiftForward(chars, start, end, " \t"); startLine--; } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n'); final VisualPosition startPosition = editor.offsetToVisualPosition(off); int indentColumn = startPosition.column; // It's considered that indent guide can cross not only white space but comments, javadocs // etc. Hence, there is a possible // case that the first indent guide line is, say, single-line comment where comment // symbols ('//') are located at the first // visual column. We need to calculate correct indent guide column then. int lineShift = 1; if (indentColumn <= 0 && descriptor != null) { indentColumn = descriptor.indentLevel; lineShift = 0; } if (indentColumn <= 0) return; final FoldingModel foldingModel = editor.getFoldingModel(); if (foldingModel.isOffsetCollapsed(off)) return; final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off))); final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset( doc.getLineStartOffset(doc.getLineNumber(endOffset))); if (tailRegion != null && tailRegion == headerRegion) return; final boolean selected; final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide(); if (guide != null) { final CaretModel caretModel = editor.getCaretModel(); final int caretOffset = caretModel.getOffset(); selected = caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn; } else { selected = false; } Point start = editor.visualPositionToXY( new VisualPosition(startPosition.line + lineShift, indentColumn)); final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset); Point end = editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column)); int maxY = end.y; if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) { maxY += editor.getLineHeight(); } Rectangle clip = g.getClipBounds(); if (clip != null) { if (clip.y >= maxY || clip.y + clip.height <= start.y) { return; } maxY = Math.min(maxY, clip.y + clip.height); } final EditorColorsScheme scheme = editor.getColorsScheme(); g.setColor( selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR)); // There is a possible case that indent line intersects soft wrap-introduced text. // Example: // this is a long line <soft-wrap> // that| is soft-wrapped // | // | <- vertical indent // // Also it's possible that no additional intersections are added because of soft wrap: // this is a long line <soft-wrap> // | that is soft-wrapped // | // | <- vertical indent // We want to use the following approach then: // 1. Show only active indent if it crosses soft wrap-introduced text; // 2. Show indent as is if it doesn't intersect with soft wrap-introduced text; if (selected) { g.drawLine(start.x + 2, start.y, start.x + 2, maxY); } else { int y = start.y; int newY = start.y; SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineHeight = editor.getLineHeight(); for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) { List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i); int logicalLineHeight = softWraps.size() * lineHeight; if (i > startLine + lineShift) { logicalLineHeight += lineHeight; // We assume that initial 'y' value points just below the target // line. } if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) { if (y < newY || i > startLine + lineShift) { // There is a possible case that soft wrap is located on // indent start line. g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight); } newY += logicalLineHeight; y = newY; } else { newY += logicalLineHeight; } FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i)); if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) { i = doc.getLineNumber(foldRegion.getEndOffset()); } } if (y < maxY) { g.drawLine(start.x + 2, y, start.x + 2, maxY); } } }
FoldRegionInfo(@NotNull FoldRegion region) { this.region = region; start = region.getStartOffset(); end = region.getEndOffset(); }