currentTextEditingValue property

  1. @override
TextEditingValue? get currentTextEditingValue
override

The current state of the TextEditingValue held by this client.

Implementation

@override
TextEditingValue? get currentTextEditingValue {
  if (_isComposing) {
    // On buffer-sync platforms the IME computes deltas against the full
    // fragment text. Return the reconstructed full buffer so delta
    // application stays consistent and avoids random duplication.
    if (_shouldSyncBuffer) {
      final fragText = _getCurrentFragmentText() ?? '';
      final start = _preeditLocalOffset.clamp(0, fragText.length);
      final text = fragText.substring(0, start) + _preeditText + fragText.substring(start);
      final composingStart = start;
      final composingEnd = start + _preeditText.length;
      return TextEditingValue(
        text: text,
        selection: TextSelection.collapsed(
          offset: composingStart + _preeditCaretOffset.clamp(0, _preeditText.length),
        ),
        composing: TextRange(start: composingStart, end: composingEnd),
      );
    }
    return TextEditingValue(
      text: _preeditText,
      selection: TextSelection.collapsed(offset: _preeditText.length),
      composing: _composingRange,
    );
  }
  // Buffer-sync platforms (web, iOS, macOS, Windows): return the actual
  // fragment text so the IME computes deltas against the correct content.
  if (_shouldSyncBuffer) {
    final text = _getCurrentFragmentText();
    if (text != null) {
      final doc = _document!;
      final cursor = doc.cursor;
      final isSingleFragSelection =
          !cursor.isCollapsed && cursor.anchorId == cursor.focusId;
      final offset = _getCursorOffsetInFragment().clamp(0, text.length);
      if (_isIOS &&
          cursor.isCollapsed &&
          offset == 0 &&
          !text.startsWith(_emptyFragmentPlaceholder)) {
        // See _emptyFragmentPlaceholder: when the cursor sits at the very
        // start of a fragment (offset 0), UIKit's Backspace has nothing
        // before the cursor to delete and silently swallows the keystroke.
        // We prepend a single zero-width placeholder character so Backspace
        // always has something to act on. The placeholder never reaches the
        // document model — it is intercepted in updateEditingValueWithDeltas
        // and translated into a structural backspace (merge with prev node).
        return TextEditingValue(
          text: '$_emptyFragmentPlaceholder$text',
          selection: const TextSelection.collapsed(offset: 1),
          composing: TextRange.empty,
        );
      }
      if (isSingleFragSelection) {
        return TextEditingValue(
          text: text,
          selection: TextSelection(
            baseOffset: cursor.anchorOffset.clamp(0, text.length),
            extentOffset: cursor.focusOffset.clamp(0, text.length),
          ),
          composing: TextRange.empty,
        );
      }
      // Multi-fragment selection: use a full selection in the buffer so
      // the platform can delete or replace the entire buffer content,
      // which triggers the correct document-level selection
      // deletion/replacement.
      if (!cursor.isCollapsed) {
        return TextEditingValue(
          text: text,
          selection: TextSelection(
            baseOffset: 0,
            extentOffset: text.length,
          ),
          composing: TextRange.empty,
        );
      }
      return TextEditingValue(
        text: text,
        selection: TextSelection.collapsed(offset: offset),
        composing: TextRange.empty,
      );
    }
  }
  // Other platforms keep an empty buffer (backspace handled via KeyEvent).
  return const TextEditingValue();
}