executeHandleArrowKey function

bool executeHandleArrowKey(
  1. LogicalKeyboardKey key,
  2. FluentDocument document, {
  3. bool ctrl = false,
  4. bool shift = false,
})

Implementation

bool executeHandleArrowKey(
  LogicalKeyboardKey key,
  FluentDocument document, {
  bool ctrl = false,
  bool shift = false,
}) {
  final cursor = document.cursor;
  final current = shift
      ? CaretStop(cursor.focusId, cursor.focusOffset)
      : CaretStop(cursor.anchorId, cursor.anchorOffset);
  final root = document.content;

  final stops = document.caretStops;

  late final NavigationResult result;
  late final bool isVertical;

  if (!shift && !cursor.isCollapsed) {
    final anchorIdx = findStopIndex(stops, cursor.anchorId, cursor.anchorOffset);
    final focusIdx = findStopIndex(stops, cursor.focusId, cursor.focusOffset);

    if (anchorIdx >= 0 && focusIdx >= 0) {
      final start = anchorIdx <= focusIdx
          ? CaretStop(cursor.anchorId, cursor.anchorOffset)
          : CaretStop(cursor.focusId, cursor.focusOffset);
      final end = anchorIdx <= focusIdx
          ? CaretStop(cursor.focusId, cursor.focusOffset)
          : CaretStop(cursor.anchorId, cursor.anchorOffset);

      if (key == LogicalKeyboardKey.arrowLeft ||
          key == LogicalKeyboardKey.arrowUp) {
        cursor.batchUpdate(() {
          cursor.moveTo(start.fragmentId, start.offset);
        });
        document.syncPendingFontWithCursor();
        document.selectionManager.collapse();
        _syncSelectionManager(document);
        document.cursorOnlyUpdate();
        return true;
      }
      if (key == LogicalKeyboardKey.arrowRight ||
          key == LogicalKeyboardKey.arrowDown) {
        cursor.batchUpdate(() {
          cursor.moveTo(end.fragmentId, end.offset);
        });
        document.syncPendingFontWithCursor();
        document.selectionManager.collapse();
        _syncSelectionManager(document);
        document.cursorOnlyUpdate();
        return true;
      }
    }
  }

  if (key == LogicalKeyboardKey.arrowLeft) {
    result = ctrl
        ? moveWordLeft(root, current,
            stops: stops, cachedLines: document.logicalLines)
        : moveLeft(root, current, stops: stops);
    isVertical = false;
  } else if (key == LogicalKeyboardKey.arrowRight) {
    result = ctrl
        ? moveWordRight(root, current,
            stops: stops, cachedLines: document.logicalLines)
        : moveRight(root, current, stops: stops);
    isVertical = false;
  } else if (key == LogicalKeyboardKey.arrowUp ||
             key == LogicalKeyboardKey.arrowDown) {
    final currentNode = document.nodeById(current.fragmentId);
    final isBlockNode = currentNode is HorizontalRule || currentNode is FluentImage;

    if (isBlockNode) {
      final allStops = document.caretStops;
      final currentIdx = findStopIndex(allStops, current.fragmentId, current.offset);
      if (currentIdx >= 0) {
        if (key == LogicalKeyboardKey.arrowUp) {
          if (currentIdx > 0) {
            result = NavigationResult(position: allStops[currentIdx - 1], preferredX: -1.0);
          } else {
            result = NavigationResult.none;
          }
        } else { // arrowDown
          int nextIdx = currentIdx + 1;
          while (nextIdx < allStops.length &&
                 allStops[nextIdx].fragmentId == current.fragmentId) {
            nextIdx++;
          }
          if (nextIdx < allStops.length) {
            result = NavigationResult(position: allStops[nextIdx], preferredX: -1.0);
          } else {
            result = NavigationResult.none;
          }
        }
        isVertical = true;
      }
    } else {
      final currentContainerId = document.findLogicalContainerId(current.fragmentId);
      final containerOrder = document.containerOrder;
      final containerIdx = containerOrder.indexOf(currentContainerId ?? '');
      final candidateIds = <String>{};
      if (containerIdx >= 0) {
        candidateIds.add(containerOrder[containerIdx]);

        String? nearestStructure(String? id) {
          if (id == null) return null;
          String? pid = id;
          while (pid != null) {
            final node = document.nodeById(pid);
            if (node is FluentTable || node is FluentList) return pid;
            pid = document.findParentCached(pid);
          }
          return null;
        }

        bool isInsideStructure(String? containerId, String structureId) {
          if (containerId == null) return false;
          String? pid = containerId;
          while (pid != null) {
            if (pid == structureId) return true;
            pid = document.findParentCached(pid);
          }
          return false;
        }

        String? cellFor(String? containerId) {
          if (containerId == null) return null;
          String? pid = containerId;
          while (pid != null) {
            final node = document.nodeById(pid);
            if (node is FluentCell) return pid;
            pid = document.findParentCached(pid);
          }
          return null;
        }

        final currentEnclosing = nearestStructure(currentContainerId);
        if (currentEnclosing != null) {
          final enclosingNode = document.nodeById(currentEnclosing);
          if (enclosingNode is FluentTable) {
            final table = enclosingNode;
            final currentCellId = cellFor(currentContainerId);
            if (currentCellId != null) {
              int rowIndex = -1;
              int logicalCol = -1;
              for (int r = 0; r < table.rows.length; r++) {
                final row = table.rows[r];
                int col = 0;
                for (int c = 0; c < row.cells.length; c++) {
                  if (row.cells[c].id == currentCellId) {
                    rowIndex = r;
                    logicalCol = col;
                    break;
                  }
                  col += row.cells[c].colSpan;
                }
                if (rowIndex >= 0) break;
              }
              if (rowIndex >= 0 && logicalCol >= 0) {
                for (final id in containerOrder) {
                  if (isInsideStructure(id, currentCellId)) {
                    candidateIds.add(id);
                  }
                }
                if (rowIndex > 0) {
                  final aboveCellId = _findCellAtLogicalCol(
                    table.rows[rowIndex - 1], logicalCol);
                  if (aboveCellId != null) {
                    for (final id in containerOrder) {
                      if (isInsideStructure(id, aboveCellId)) {
                        candidateIds.add(id);
                      }
                    }
                  }
                }
                if (rowIndex < table.rows.length - 1) {
                  final belowCellId = _findCellAtLogicalCol(
                    table.rows[rowIndex + 1], logicalCol);
                  if (belowCellId != null) {
                    for (final id in containerOrder) {
                      if (isInsideStructure(id, belowCellId)) {
                        candidateIds.add(id);
                      }
                    }
                  }
                }
              }
            }
          } else if (enclosingNode is FluentList) {
            for (final id in containerOrder) {
              if (isInsideStructure(id, currentEnclosing)) {
                candidateIds.add(id);
              }
            }
          }
          if (containerIdx > 0) {
            for (int i = containerIdx - 1; i >= 0; i--) {
              final id = containerOrder[i];
              if (!isInsideStructure(id, currentEnclosing)) {
                candidateIds.add(id);
                break;
              }
            }
          }
          if (containerIdx < containerOrder.length - 1) {
            for (int i = containerIdx + 1; i < containerOrder.length; i++) {
              final id = containerOrder[i];
              if (!isInsideStructure(id, currentEnclosing)) {
                candidateIds.add(id);
                break;
              }
            }
          }
        } else {
          if (containerIdx > 0) candidateIds.add(containerOrder[containerIdx - 1]);
          if (containerIdx < containerOrder.length - 1) {
            candidateIds.add(containerOrder[containerIdx + 1]);
          }
        }
      }
      final candidateStops = candidateIds.isNotEmpty
          ? candidateIds
              .expand<CaretStop>((id) => document.stopsByContainer[id] ?? [])
              .toList()
          : stops;
      final pref = _adjustPreferredXForBlockImage(document, current, cursor.preferredX);
      if (key == LogicalKeyboardKey.arrowUp) {
        result = moveUp(root, current, pref,
            document.resolveCaretX, document.resolveCaretY,
            stops: candidateStops, allStops: stops,
            parentResolver: document.findParentCached,
            containerResolver: document.findLogicalContainerId,
            topLevelIndexResolver: document.topLevelIndexOf);
      } else {
        result = moveDown(root, current, pref,
            document.resolveCaretX, document.resolveCaretY,
            stops: candidateStops, allStops: stops,
            parentResolver: document.findParentCached,
            containerResolver: document.findLogicalContainerId,
            topLevelIndexResolver: document.topLevelIndexOf);
      }
      isVertical = true;
    }
  } else {
    return false;
  }

  final newPos = result.position;
  if (newPos == null) return true;

  if (shift) {
    cursor.batchUpdate(() {
      cursor.focusTo(newPos.fragmentId, newPos.offset);
      cursor.preferredX = isVertical ? result.preferredX : -1.0;
    });
  } else {
    cursor.batchUpdate(() {
      cursor.moveTo(newPos.fragmentId, newPos.offset);
      if (isVertical) cursor.preferredX = result.preferredX;
    });
    document.syncPendingFontWithCursor();
  }

  _syncSelectionManager(document);

  document.cursorOnlyUpdate();

  return true;
}