/* * Copyright 2019 The Chromium Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ package io.flutter.editor; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.TextRange; import org.dartlang.analysis.server.protocol.FlutterOutline; import org.dartlang.analysis.server.protocol.FlutterOutlineAttribute; import org.dartlang.analysis.server.protocol.Location; import java.util.ArrayList; /** * Analog to the IndentGuideDescriptor class from the regular FilteredIndentsHighlightingPass. * <p> * The core difference relative to IndentGuideDescriptor is this descriptor * tracks a list of child nodes to visualize the tree structure of a build * method. WidgetIndentsHighlightingPass will use this information to draw horizontal * lines to show part-child relationships. * <p> * Widget indent guides depend on the analysis service as the source of truth, * so more information has to be still accurate even after the document is * edited as there will be a slight delay before new analysis data is available. */ public class WidgetIndentGuideDescriptor { public static class WidgetPropertyDescriptor { private RangeMarker marker; private final FlutterOutlineAttribute attribute; WidgetPropertyDescriptor(FlutterOutlineAttribute attribute) { this.attribute = attribute; } public String getName() { return attribute.getName(); } public FlutterOutlineAttribute getAttribute() { return attribute; } public int getEndOffset() { if (marker == null) { final Location location = attribute.getValueLocation(); final int startOffset = location.getOffset(); final int endOffset = startOffset + location.getLength(); return endOffset; } return marker.getEndOffset(); } public void track(Document document) { if (marker != null) { // TODO(jacobr): it does indicate a bit of a logic bug if we are calling this method twice. assert (marker.getDocument() == document); return; } // Create a range marker that goes from the start of the indent for the line // to the column of the actual entity. final int docLength = document.getTextLength(); final Location location = attribute.getValueLocation(); final int startOffset = Math.min(location.getOffset(), docLength); final int endOffset = Math.min(startOffset + location.getLength(), docLength); marker = document.createRangeMarker(startOffset, endOffset); } public void dispose() { if (marker != null) { marker.dispose(); } } } public final WidgetIndentGuideDescriptor parent; public final ArrayList<OutlineLocation> childLines; public final OutlineLocation widget; public final int indentLevel; public final int startLine; public final int endLine; public final FlutterOutline outlineNode; public WidgetIndentGuideDescriptor( WidgetIndentGuideDescriptor parent, int indentLevel, int startLine, int endLine, ArrayList<OutlineLocation> childLines, OutlineLocation widget, FlutterOutline outlineNode ) { this.parent = parent; this.childLines = childLines; this.widget = widget; this.indentLevel = indentLevel; this.startLine = startLine; this.endLine = endLine; this.outlineNode = outlineNode; } void dispose() { if (widget != null) { widget.dispose(); } if (childLines == null) return; for (OutlineLocation childLine : childLines) { childLine.dispose(); } childLines.clear(); } /** * This method must be called to opt the indent guide into tracking * location changes due to document edits. * <p> * If trackLocations is called on a descriptor, you must later call dispose * to stop listening for changes to the document once the descriptor is * obsolete. */ boolean tracked = false; public void trackLocations(Document document) { if (tracked) return; tracked = true; if (widget != null) { widget.track(document); } if (childLines == null) return; for (OutlineLocation childLine : childLines) { childLine.track(document); } } public TextRange getMarker() { return widget.getFullRange(); } @Override public int hashCode() { int result = indentLevel; result = 31 * result + startLine; result = 31 * result + endLine; if (childLines != null) { for (OutlineLocation location : childLines) { result = 31 * result + location.hashCode(); } } if (widget != null) { result = 31 * result + widget.hashCode(); } return result; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final WidgetIndentGuideDescriptor that = (WidgetIndentGuideDescriptor)o; if (endLine != that.endLine) return false; if (indentLevel != that.indentLevel) return false; if (startLine != that.startLine) return false; if (childLines == null || that.childLines == null) { return childLines == that.childLines; } if (childLines.size() != that.childLines.size()) { return false; } for (int i = 0; i < childLines.size(); ++i) { if (!childLines.get(i).equals(that.childLines.get(i))) { return false; } } return true; } public int compareTo(WidgetIndentGuideDescriptor that) { int answer = endLine - that.endLine; if (answer != 0) { return answer; } answer = indentLevel - that.indentLevel; if (answer != 0) { return answer; } answer = startLine - that.startLine; if (answer != 0) { return answer; } if (childLines == that.childLines) { return 0; } if (childLines == null || that.childLines == null) { return childLines == null ? -1 : 1; } answer = childLines.size() - that.childLines.size(); if (answer != 0) { return answer; } for (int i = 0; i < childLines.size(); ++i) { answer = childLines.get(i).compareTo(that.childLines.get(i)); if (answer != 0) { return answer; } } return 0; } }