update method

  1. @override
(TextInputModel, Cmd?) update(
  1. Msg msg
)
override

Updates the component state in response to a message.

Returns the updated component (often this) and an optional command.

Implementation

@override
(TextInputModel, Cmd?) update(Msg msg) {
  final oldPos = _pos;
  final cmds = <Cmd>[];

  if (msg is MouseMsg) {
    if (msg.y != 0) {
      if (msg.action == MouseAction.press && msg.button == MouseButton.left) {
        _selectionStart = null;
        _selectionEnd = null;
        _focused = false;
      }
      return (this, null);
    }
    final promptWidth = stringWidth(prompt);
    final localX = msg.x - promptWidth;
    final visibleValue = _value.sublist(_offset, _offsetRight);
    final idxInVisible = layout.localCellXToGraphemeIndex(
      visibleValue.join(),
      localX,
    );
    final x = _offset + idxInVisible;

    if (msg.action == MouseAction.press && msg.button == MouseButton.left) {
      _focused = true;
      final now = DateTime.now();
      if (_lastClickTime != null &&
          now.difference(_lastClickTime!) <
              const Duration(milliseconds: 500) &&
          _lastClickPos == x) {
        // Double click: select word
        final (start, end) = _findWordAt(x);
        _selectionStart = start;
        _selectionEnd = end;
        _pos = end;
        _lastClickTime = now;
        _lastClickPos = x;
      } else {
        position = x;
        _selectionStart = _pos;
        _selectionEnd = _pos;
        _lastClickTime = now;
        _lastClickPos = x;
      }
    } else if (msg.action == MouseAction.motion &&
        msg.button == MouseButton.left) {
      position = x;
      _selectionEnd = _pos;
    } else if (msg.action == MouseAction.release) {
      if (_selectionStart == _selectionEnd) {
        _selectionStart = null;
        _selectionEnd = null;
      }
    }
    return (this, null);
  }

  if (!_focused) {
    return (this, null);
  }

  // Check for suggestion acceptance first
  if (msg is KeyMsg && keyMatches(msg.key, [keyMap.acceptSuggestion])) {
    if (_canAcceptSuggestion()) {
      final suggestion = _matchedSuggestions[_currentSuggestionIndex];
      _value = [..._value, ...suggestion.sublist(_value.length)];
      cursorEnd();
    }
  }

  if (msg is KeyMsg) {
    if (keyMatches(msg.key, [keyMap.copy])) {
      final selected = getSelectedText();
      if (selected.isNotEmpty) {
        return (this, Cmd.setClipboard(selected));
      }
    }

    if (msg.key.type == KeyType.space) {
      _insertRunes([0x20]);
      return (this, null);
    }

    if (keyMatches(msg.key, [keyMap.deleteWordBackward])) {
      _deleteWordBackward();
    } else if (keyMatches(msg.key, [keyMap.deleteCharacterBackward])) {
      error = null;
      if (_value.isNotEmpty && _pos > 0) {
        _value = [
          ..._value.sublist(0, math.max(0, _pos - 1)),
          ..._value.sublist(_pos),
        ];
        error = _validate(_value);
        if (_pos > 0) position = _pos - 1;
      }
    } else if (keyMatches(msg.key, [keyMap.wordBackward])) {
      _wordBackward();
    } else if (keyMatches(msg.key, [keyMap.characterBackward])) {
      if (_pos > 0) position = _pos - 1;
    } else if (keyMatches(msg.key, [keyMap.wordForward])) {
      _wordForward();
    } else if (keyMatches(msg.key, [keyMap.characterForward])) {
      if (_pos < _value.length) position = _pos + 1;
    } else if (keyMatches(msg.key, [keyMap.lineStart])) {
      cursorStart();
    } else if (keyMatches(msg.key, [keyMap.deleteCharacterForward])) {
      if (_value.isNotEmpty && _pos < _value.length) {
        _value = [..._value.sublist(0, _pos), ..._value.sublist(_pos + 1)];
        error = _validate(_value);
      }
    } else if (keyMatches(msg.key, [keyMap.lineEnd])) {
      cursorEnd();
    } else if (keyMatches(msg.key, [keyMap.deleteAfterCursor])) {
      _deleteAfterCursor();
    } else if (keyMatches(msg.key, [keyMap.deleteBeforeCursor])) {
      _deleteBeforeCursor();
    } else if (keyMatches(msg.key, [keyMap.paste])) {
      // Return paste command - caller handles clipboard
      return (this, _pasteCmd());
    } else if (keyMatches(msg.key, [keyMap.deleteWordForward])) {
      _deleteWordForward();
    } else if (keyMatches(msg.key, [keyMap.nextSuggestion])) {
      _nextSuggestion();
    } else if (keyMatches(msg.key, [keyMap.prevSuggestion])) {
      _previousSuggestion();
    } else if (msg.key.runes.isNotEmpty) {
      // Regular character input
      _insertRunes(msg.key.runes);
    }

    _updateSuggestions();
  } else if (msg is PasteMsg) {
    _insertRunes(uni.codePoints(msg.content));
  } else if (msg is PasteErrorMsg) {
    error = msg.error.toString();
  }

  // Update cursor
  final (newCursor, cursorCmd) = cursor.update(msg);
  cursor = newCursor;
  if (cursorCmd != null) cmds.add(cursorCmd);

  // Reset blink if position changed - use focus() to restart blink
  if (oldPos != _pos && cursor.mode == CursorMode.blink) {
    final (refocusedCursor, blinkCmd) = cursor.focus();
    cursor = refocusedCursor;
    if (blinkCmd != null) cmds.add(blinkCmd);
  }

  _handleOverflow();
  return (this, cmds.isNotEmpty ? Cmd.batch(cmds) : null);
}