update method

  1. @override
(TextAreaModel, 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
(TextAreaModel, Cmd?) update(Msg msg) {
  return _runEditFrame(() {
    switch (msg) {
      case TextAreaPasteMsg(:final content):
        _beginHistoryAction(_TextAreaHistoryAction.paste, breakChain: true);
        return (this, _pasteContent(content));
      case PasteMsg(:final content):
        _beginHistoryAction(_TextAreaHistoryAction.paste, breakChain: true);
        return (this, _pasteContent(content));
      case PasteTextMsg(:final content):
        _beginHistoryAction(_TextAreaHistoryAction.paste, breakChain: true);
        return (this, _pasteContent(content));
      case _TextAreaPasteChunkMsg():
        _beginHistoryAction(_TextAreaHistoryAction.paste);
        _applyNextPasteChunk();
        if (_pasteController.hasPendingChunkedPaste) {
          return (this, _schedulePasteChunk());
        }
        return (this, null);
      case KeyMsg(key: final key):
        if (key.matchesSingle(keyMap.undo)) {
          undo();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.redo)) {
          redo();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.selectAll)) {
          selectAll();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.selectLine)) {
          selectCurrentLine();
          return (this, null);
        }

        // deletion
        if (key.matchesSingle(keyMap.deleteBeforeCursor)) {
          _beginHistoryAction(_TextAreaHistoryAction.deleteBackward);
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _backspace();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteCharacterForward)) {
          _beginHistoryAction(_TextAreaHistoryAction.deleteForward);
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteCharForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteWordBackward)) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteBackward,
            breakChain: true,
          );
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteWordBackward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteWordForward)) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteForward,
            breakChain: true,
          );
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteWordForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteToLineStart)) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteBackward,
            breakChain: true,
          );
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteToLineStart();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteToLineEnd)) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteForward,
            breakChain: true,
          );
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteToLineEnd();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteAfterCursor)) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteForward,
            breakChain: true,
          );
          if (_deleteSelectionIfAny()) {
            return (this, null);
          }
          _deleteToLineEnd();
          return (this, null);
        }

        // navigation
        if (key.matchesSingle(keyMap.wordForward)) {
          _moveWordForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.wordBackward)) {
          _moveWordBackward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.lineStart)) {
          _cursorStartOfLine();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.lineEnd)) {
          _cursorEndOfLine();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.inputBegin)) {
          _cursorStartOfInput();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.inputEnd)) {
          _cursorEndOfInput();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.characterForward)) {
          _moveRight();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.characterBackward)) {
          _moveLeft();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.lineNext)) {
          _lineNext();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.linePrevious)) {
          _linePrev();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.transposeCharacterBackward)) {
          _transposeBackward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.uppercaseWordForward)) {
          _uppercaseWordForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.lowercaseWordForward)) {
          _lowercaseWordForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.capitalizeWordForward)) {
          _capitalizeWordForward();
          return (this, null);
        }
        if (key.matchesSingle(keyMap.copy)) {
          final text = getSelectedText();
          if (text.isNotEmpty) {
            return (this, Cmd.setClipboard(text));
          }
        }

        // Fallback direct modifier checks for common combos.
        if (key.type == KeyType.delete && key.alt) {
          _beginHistoryAction(
            _TextAreaHistoryAction.deleteForward,
            breakChain: true,
          );
          _deleteWordForward();
          return (this, null);
        }
        if (key.ctrl && key.type == KeyType.runes && key.runes.isNotEmpty) {
          final r = key.runes.first;
          if (r == 0x74) {
            // ctrl+t
            _beginHistoryAction(
              _TextAreaHistoryAction.transform,
              breakChain: true,
            );
            _transposeBackward();
            return (this, null);
          }
        }
        if (key.alt && key.type == KeyType.runes && key.runes.isNotEmpty) {
          final r = key.runes.first;
          if (r == 0x75) {
            _beginHistoryAction(
              _TextAreaHistoryAction.transform,
              breakChain: true,
            );
            _uppercaseWordForward();
            return (this, null);
          }
          if (r == 0x6c) {
            _beginHistoryAction(
              _TextAreaHistoryAction.transform,
              breakChain: true,
            );
            _lowercaseWordForward();
            return (this, null);
          }
          if (r == 0x63) {
            _beginHistoryAction(
              _TextAreaHistoryAction.transform,
              breakChain: true,
            );
            _capitalizeWordForward();
            return (this, null);
          }
        }

        if (key.type == KeyType.space) {
          _beginHistoryAction(_TextAreaHistoryAction.insert);
          _insertChar(' ');
          return (this, null);
        }

        if (key.type == KeyType.enter && keyMap.insertNewline.enabled) {
          _beginHistoryAction(
            _TextAreaHistoryAction.insert,
            breakChain: true,
          );
          _newline();
          return (this, null);
        }

        if (key.type == KeyType.runes && key.runes.isNotEmpty) {
          _beginHistoryAction(_TextAreaHistoryAction.insert);
          final rune = key.runes.first;
          if (rune == 0x0a) {
            _newline();
          } else {
            _insertChar(String.fromCharCode(rune));
          }
          return (this, null);
        }
    }

    if (msg is MouseMsg) {
      final lineNumberDigits = showLineNumbers ? '$lineCount'.length : 0;
      final displayLines = _softWrappedLines(lineNumberDigits);
      final action = msg.action;
      final button = msg.button;
      final x = msg.x;
      final y = msg.y;

      if (y < 0 || y >= displayLines.length) {
        if (action == MouseAction.press && button == MouseButton.left) {
          _mouseSelecting = false;
          _clearLineSelection();
          _focused = false;
          _syncCoreState();
        }
        if (action == MouseAction.release && button == MouseButton.left) {
          _mouseSelecting = false;
          if (!_hasSelection()) {
            _clearLineSelection();
            _syncCoreState();
          }
        }
        return (this, null);
      }

      if (action == MouseAction.press && button == MouseButton.left) {
        _focused = true;
        final promptW = _getPromptWidth(y);
        final lineNumberW = showLineNumbers ? (lineNumberDigits + 1) : 0;
        final displayLine = displayLines[y];
        final inLineNumberGutter =
            showLineNumbers &&
            x >= promptW &&
            x < promptW + lineNumberW &&
            displayLine.charOffset == 0;
        if (inLineNumberGutter &&
            selectDiagnosticAtLine(displayLine.rowIndex)) {
          _mouseSelecting = false;
          return (this, null);
        }
        final hit = _textView.hitTestContent(
          _document,
          _editorState,
          localX: x - promptW - lineNumberW,
          visualRow: y,
        );
        if (hit == null) {
          _mouseSelecting = false;
          return (this, null);
        }
        final contentX = hit.column;
        final contentY = hit.line;
        final now = DateTime.now();

        final clickCount =
            _lastClickTime != null &&
                now.difference(_lastClickTime!) <
                    const Duration(milliseconds: 500) &&
                _lastClickPos == (contentX, contentY)
            ? (_lastClickCount + 1).clamp(1, 3)
            : 1;
        _lastClickTime = now;
        _lastClickPos = (contentX, contentY);
        _lastClickCount = clickCount;

        if (clickCount == 2) {
          _mouseSelecting = false;
          final (start, end) = _findWordAt(contentX, contentY);
          _selectLineState(
            base: TextPosition(line: contentY, column: start),
            extent: TextPosition(line: contentY, column: end),
          );
          _syncCoreState();
          return (this, null);
        }
        if (clickCount >= 3) {
          _mouseSelecting = false;
          _selectLineState(
            base: TextPosition(line: contentY, column: 0),
            extent: TextPosition(
              line: contentY,
              column: _document.lineLength(contentY),
            ),
          );
          _syncCoreState();
          return (this, null);
        }

        // Start selection
        _mouseSelecting = true;
        _selectLineState(
          base: TextPosition(line: contentY, column: contentX),
          extent: TextPosition(line: contentY, column: contentX),
          preserveCollapsedSelection: true,
        );
        _syncCoreState();
        return (this, null);
      }

      if (action == MouseAction.motion &&
          _mouseSelecting &&
          _selectionStart != null) {
        final promptW = _getPromptWidth(y);
        final lineNumberW = showLineNumbers ? (lineNumberDigits + 1) : 0;
        final hit = _textView.hitTestContent(
          _document,
          _editorState,
          localX: x - promptW - lineNumberW,
          visualRow: y,
        );
        if (hit == null) {
          return (this, null);
        }
        final contentX = hit.column;
        final contentY = hit.line;
        _selectLineState(
          base: TextPosition(
            line: _selectionStart!.$2,
            column: _selectionStart!.$1,
          ),
          extent: TextPosition(line: contentY, column: contentX),
        );
        _syncCoreState();
        return (this, null);
      }

      if (action == MouseAction.release && button == MouseButton.left) {
        _mouseSelecting = false;
        if (!_hasSelection()) {
          _clearLineSelection();
          _syncCoreState();
        }
        return (this, null);
      }
    }

    return (this, null);
  });
}