resolveSelection function

ResolvedSelection? resolveSelection(
  1. Root root,
  2. String anchorFragmentId,
  3. int anchorOffset,
  4. String focusFragmentId,
  5. int focusOffset, {
  6. List<CaretStop>? cachedStops,
  7. List<LogicalLine>? cachedLines,
  8. FluentDocument? document,
})

Resolves the selection defined by (anchorFragmentId, anchorOffset) → (focusFragmentId, focusOffset) in the root tree.

Returns null if:

  • anchor == focus (collapsed selection)
  • one of the fragments is not found in the tree
  • positions are not on the stop rail

Usage example:

final sel = resolveSelection(
  document.content,
  cursor.anchorId, cursor.anchorOffset,
  cursor.focusId,  cursor.focusOffset,
);
if (sel != null) {
  for (final node in sel.nodes) {
    print(node);
  }
}

Implementation

ResolvedSelection? resolveSelection(
  Root root,
  String anchorFragmentId,
  int anchorOffset,
  String focusFragmentId,
  int focusOffset, {
  List<CaretStop>? cachedStops,
  List<LogicalLine>? cachedLines,
  FluentDocument? document,
}) {
  if (anchorFragmentId == focusFragmentId && anchorOffset == focusOffset) {
    return null;
  }

  final stops = cachedStops ?? buildAllStops(root);
  final lines = cachedLines ?? buildAllLogicalLines(root);

  final anchorIdx = findStopIndex(stops, anchorFragmentId, anchorOffset);
  final focusIdx  = findStopIndex(stops, focusFragmentId,  focusOffset);

  if (anchorIdx < 0 || focusIdx < 0) return null;

  final baseIsAnchor = anchorIdx <= focusIdx;
  final baseIdx   = baseIsAnchor ? anchorIdx : focusIdx;
  final extentIdx = baseIsAnchor ? focusIdx  : anchorIdx;

  final anchorFragResolved = document?.nodeById(anchorFragmentId) ?? findById(root, anchorFragmentId);
  final focusFragResolved  = document?.nodeById(focusFragmentId)  ?? findById(root, focusFragmentId);
  if (anchorFragResolved is! Fragment || focusFragResolved is! Fragment) return null;

  final anchorContainer = document?.findLogicalContainerCached(anchorFragmentId) ??
      findLogicalContainer(root, anchorFragmentId);
  final focusContainer  = document?.findLogicalContainerCached(focusFragmentId) ??
      findLogicalContainer(root, focusFragmentId);
  if (anchorContainer == null || focusContainer == null) return null;

  final anchorEndpoint = SelectionEndpoint(
    fragment:  anchorFragResolved,
    offset:    anchorOffset,
    container: anchorContainer,
  );
  final focusEndpoint = SelectionEndpoint(
    fragment:  focusFragResolved,
    offset:    focusOffset,
    container: focusContainer,
  );

  final baseEndpoint   = baseIsAnchor ? anchorEndpoint : focusEndpoint;
  final extentEndpoint = baseIsAnchor ? focusEndpoint  : anchorEndpoint;

  final selectedNodes = <SelectedNode>[];

  for (final line in lines) {
    if (line.stops.isEmpty) continue;
    final firstStop = line.stops.first;
    final lastStop = line.stops.last;
    final firstIdx = findStopIndex(stops, firstStop.fragmentId, firstStop.offset);
    final lastIdx = findStopIndex(stops, lastStop.fragmentId, lastStop.offset);

    if (firstIdx < 0 || lastIdx < 0) continue;
    final lineMinIdx = firstIdx <= lastIdx ? firstIdx : lastIdx;
    final lineMaxIdx = firstIdx >= lastIdx ? firstIdx : lastIdx;

    if (lineMaxIdx < baseIdx || lineMinIdx > extentIdx) continue;

    final Fragment startFrag;
    final int startOff;

    final lineContainerId = (line.node as FNode).id;
    final isBaseLine   = lineContainerId == (baseEndpoint.container as FNode).id;
    final isExtentLine = lineContainerId == (extentEndpoint.container as FNode).id;

    if (isBaseLine) {
      startFrag = baseEndpoint.fragment;
      startOff  = baseEndpoint.offset;
    } else {
      final firstStop = line.stops.first;
      final firstNode = document?.nodeById(firstStop.fragmentId) ?? findById(root, firstStop.fragmentId);
      if (firstNode is! Fragment) continue;
      final frag = firstNode;
      startFrag = frag;
      startOff  = 0;
    }

    final Fragment endFrag;
    final int endOff;

    if (isExtentLine) {
      endFrag = extentEndpoint.fragment;
      endOff  = extentEndpoint.offset;
    } else {
      final lastStop = line.stops.last;
      final lastNode = document?.nodeById(lastStop.fragmentId) ?? findById(root, lastStop.fragmentId);
      if (lastNode is! Fragment) continue;
      final frag = lastNode;
      endFrag = frag;
      endOff  = frag.text.length;
    }

    final isFullySelected = !isBaseLine && !isExtentLine;

    selectedNodes.add(SelectedNode(
      container:       line.node,
      startFragment:   startFrag,
      startOffset:     startOff,
      endFragment:     endFrag,
      endOffset:       endOff,
      isFullySelected: isFullySelected,
    ));
  }

  if (selectedNodes.isEmpty) return null;

  return ResolvedSelection(
    anchor: anchorEndpoint,
    focus:  focusEndpoint,
    base:   baseEndpoint,
    extent: extentEndpoint,
    nodes:  selectedNodes,
  );
}