executeHandleBackspace function

bool executeHandleBackspace(
  1. FluentDocument document, {
  2. bool ctrl = 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 ctrl is pressed: delete the previous word
  6. Otherwise: delete the previous character in the fragment

Implementation

bool executeHandleBackspace(FluentDocument document, {bool ctrl = false}) {
  final root = document.content;
  final cursor = document.cursor;

  // Case 1: If there's an active selection, delete it
  final selection = resolveSelection(
    root,
    cursor.anchorId,
    cursor.anchorOffset,
    cursor.focusId,
    cursor.focusOffset,
    cachedStops: document.caretStops,
    cachedLines: document.logicalLines,
  );

  if (selection != null) {
    // Delete the selection by replacing it with an empty string
    executeHandleReplaceSelection('', document);
    return true;
  }

  // Case 1.5: If ctrl is pressed, delete the previous word
  if (ctrl) {
    return _handleDeleteWord(document);
  }

  // Find the fragment and the current container
  // Case 2a: If cursor is on a HorizontalRule, remove it.
  final currentNode = document.nodeById(cursor.anchorId);
  if (currentNode is HorizontalRule) {
    final targetStop = _findPreviousStop(
      root, cursor.anchorId, 0,
      cachedStops: document.caretStops,
      cachedLines: document.logicalLines,
    );
    removeNode(root, currentNode);
    if (targetStop != null) {
      cursor.moveTo(targetStop.fragmentId, targetStop.offset);
    }
    document.updateContent();
    return true;
  }

  // Resolve the actual Fragment from the cursor anchor (which may be a
  // Paragraph, Image, HorizontalRule, or Fragment itself).
  Fragment? currentFrag;
  if (currentNode is Fragment) {
    currentFrag = currentNode;
  } else if (currentNode is InlineContainerNode) {
    final containerNode = currentNode as InlineContainerNode;
    final children = containerNode.getChildren();
    if (cursor.anchorOffset >= 0 && cursor.anchorOffset < children.length) {
      final child = children[cursor.anchorOffset];
      if (child is Fragment) currentFrag = child;
    }
    if (currentFrag == null) {
      for (final child in children) {
        if (child is Fragment) {
          currentFrag = child;
          break;
        }
      }
    }
  }
  if (currentFrag == null) return false;

  final container = findLogicalContainer(root, cursor.anchorId);
  if (container == null) return false;

  // Special case: cursor is inside an empty paragraph.
  // Remove the paragraph and move the cursor to the end of the previous one.
  if (container is Paragraph && container.text.isEmpty) {
    final prevStop = _findPreviousStop(
      root, cursor.anchorId, 0,
      cachedStops: document.caretStops,
      cachedLines: document.logicalLines,
    );
    if (prevStop != null) {
      removeNode(root, container as FNode);
      cursor.moveTo(prevStop.fragmentId, prevStop.offset);
      document.updateContent();
      return true;
    }
    // At the very start of the document: nothing to merge with.
    return false;
  }

  // Case 2b: If it's an image, remove the entire node.
  // The cursor should be positioned on the stop that precedes the image
  // (offset 0 of the image → moveLeft gives the true previous).
  if (currentFrag is FluentImage) {
    final targetStop = _findPreviousStop(
      root, cursor.anchorId, 0,
      cachedStops: document.caretStops,
      cachedLines: document.logicalLines,
    );
    removeNode(root, currentFrag);
    if (targetStop != null) {
      cursor.moveTo(targetStop.fragmentId, targetStop.offset);
    }
    document.updateContent();
    return true;
  }

  // Case 3: If we're at the start of the container (offset == 0)
  // handle the merge with the previous node or outdent for lists
  if (cursor.anchorOffset == 0) {
    return _handleBackspaceAtStart(document, container, currentFrag);
  }

  // Case 4: Normal character deletion
  final newOffset = cursor.anchorOffset - 1;
  FragmentOperations.deleteTextInFragment(currentFrag, newOffset, count: 1);

  // Update the cursor
  cursor.moveTo(currentFrag.id, newOffset);

  // Notify comment system of the text mutation.
  if (container is Paragraph) {
    final globalOffset = document.getGlobalOffsetInParagraph(
      container.id,
      currentFrag.id,
      newOffset,
    );
    if (globalOffset != null) {
      document.notifyTextMutation(container.id, globalOffset, -1);
    }
  }

  document.updateContent();
  return true;
}