moveUp function

NavigationResult moveUp(
  1. Root root,
  2. CaretStop current,
  3. double preferredX,
  4. CaretXResolver resolveX,
  5. CaretYResolver resolveY, {
  6. List<CaretStop>? stops,
})

Moves the cursor up by one LogicalLine. preferredX is the x coordinate in pixels to maintain. If it's -1.0, it's calculated from the current position via resolveX.

Implementation

NavigationResult moveUp(
  Root root,
  CaretStop current,
  double preferredX,
  CaretXResolver resolveX,
  CaretYResolver resolveY, {
  List<CaretStop>? stops,
}) {
  final stops_ = stops ?? buildAllStops(root);
  if (stops_.isEmpty) return NavigationResult.none;

  // resolveY/resolveX are expensive (O(visible) each). Cache their
  // results for the duration of this call to avoid redundant lookups.
  final yCache = <CaretStop, double>{};
  double cachedY(CaretStop s) => yCache[s] ??= resolveY(s);
  final xCache = <CaretStop, double>{};
  double cachedX(CaretStop s) => xCache[s] ??= resolveX(s);

  final x = preferredX >= 0.0 ? preferredX : cachedX(current);
  final currentY = cachedY(current);

  print('[MOVE_UP] current=${current.fragmentId}:${current.offset} x=$x currentY=$currentY');

  // Find the highest y that is strictly above the current line
  double? targetY;
  for (final stop in stops_) {
    final y = cachedY(stop);
    if (y < currentY - _kLineYTolerance) {
      if (targetY == null || y > targetY) targetY = y;
    }
  }

  print('[MOVE_UP] targetY=$targetY');

  // No line above: go to the first stop of the document
  if (targetY == null) {
    final first = stops_.first;
    if (first == current) return NavigationResult.none;
    print('[MOVE_UP] no line above → first=${first.fragmentId}:${first.offset}');
    return NavigationResult(position: first, preferredX: x);
  }

  // Among the stops on the target line, take the one with closest x
  final lineStops = stops_
      .where((s) => (cachedY(s) - targetY!).abs() <= _kLineYTolerance)
      .toList();

  print('[MOVE_UP] lineStops=${lineStops.map((s) => '${s.fragmentId}:${s.offset}').join(', ')}');

  final best = _stopNearestX(lineStops, x, cachedX);
  print('[MOVE_UP] best=${best.fragmentId}:${best.offset}');
  return NavigationResult(position: best, preferredX: x);
}