executeHandleBackspace function

bool executeHandleBackspace(
  1. FluentDocument document, {
  2. bool ctrl = false,
  3. bool lineStart = false,
})

Handles the Backspace key.

Supported behaviors:

  1. If there's an active selection: delete the selection
  2. If cursor is at the start of a ListItem: outdent (remove from list)
  3. If cursor is at the start of another node: merge with the previous node
  4. If cursor is on an image: remove the image
  5. If lineStart is pressed: delete to beginning of line
  6. If ctrl is pressed: delete the previous word
  7. Otherwise: delete the previous character in the fragment

Implementation

bool executeHandleBackspace(FluentDocument document, {bool ctrl = false, bool lineStart = false}) {
  if (document.registry.dispatchBackspace(document, ctrl: ctrl, lineStart: lineStart)) {
    return true;
  }

  final root = document.content;
  final cursor = document.cursor;

  if (deleteSelectionIfExists(document)) return true;

  if (lineStart) {
    return _handleDeleteToLineStart(document);
  }

  if (ctrl) {
    return deleteWordHelper(document, forward: false);
  }

  final currentNode = document.nodeById(cursor.anchorId);
  if (currentNode is HorizontalRule || currentNode is FluentImage) {
    return removeNodeAndReposition(document, currentNode!);
  }

  final currentFrag = resolveFragmentFromCursor(currentNode, cursor.anchorOffset);
  if (currentFrag == null) return false;
  if (currentFrag is FluentImage) {
    removeNode(root, currentFrag);
    document.updateContent();
    return true;
  }

  final container = document.findLogicalContainerCached(cursor.anchorId);
  if (container == null) return false;

  if (container is Paragraph && container.text.isEmpty &&
      findAncestorCached<FluentCell>(document, container as FNode) == null) {
    final prevStop = moveLeft(
      root, CaretStop(cursor.anchorId, 0),
      stops: document.caretStops,
      cachedLines: document.logicalLines,
    ).position;
    if (prevStop != null) {
      removeNode(root, container as FNode);
      cursor.moveTo(prevStop.fragmentId, prevStop.offset);
      document.updateContent();
      return true;
    }
    return false;
  }

  if (currentFrag is FluentImage) {
    return removeNodeAndReposition(document, currentFrag);
  }

  if (findAncestorCached<FluentCell>(document, currentFrag) != null &&
      currentFrag.text.isNotEmpty &&
      currentFrag.text.replaceAll('\u200B', '').isEmpty) {
    return true;
  }

  if (cursor.anchorOffset == 0) {
    return _handleBackspaceAtStart(document, container, currentFrag);
  }

  int newOffset = FragmentOperations.getPreviousGraphemeOffsetSkippingZWS(currentFrag.text, cursor.anchorOffset);
  final deleteCount = cursor.anchorOffset - newOffset;

  final cellParent = findAncestorCached<FluentCell>(document, currentFrag);

  FragmentOperations.deleteTextInFragment(currentFrag, newOffset, count: deleteCount);

  if (cellParent != null && currentFrag.text.isEmpty) {
    currentFrag.text = '\u200B';
    cursor.moveTo(currentFrag.id, 0);
    document.updateContent();
    return true;
  }

  if (currentFrag.text.isEmpty) {
    if (_removeEmptyFragmentAndReposition(document, container, currentFrag)) {
      document.updateContent();
      return true;
    }
  }

  cursor.moveTo(currentFrag.id, newOffset);

  notifyTextMutation(document, container, currentFrag.id, newOffset, -1);

  document.updateContent();
  return true;
}