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 TextAreaCompletionMsg(:final result):
        if (result == null) return (this, null);
        final maxItems = workAssessment.maxCompletionItems;
        final items = result.items.length > maxItems
            ? result.items.take(maxItems).toList(growable: false)
            : result.items;
        _completionItems = List<EditorCompletionItem>.unmodifiable(items);
        _completionIndex = _completionItems.isEmpty ? -1 : 0;
        _completionError = null;
        return (this, null);
      case TextAreaCompletionErrorMsg(:final error):
        _completionItems = const [];
        _completionIndex = -1;
        _completionError = error;
        return (this, null);
      case TextAreaCodeActionsMsg(:final actions):
        if (actions == null) return (this, null);
        _codeActions = List<EditorCodeAction>.unmodifiable(actions);
        _codeActionIndex = _codeActions.isEmpty ? -1 : 0;
        _codeActionError = null;
        return (this, null);
      case TextAreaCodeActionsErrorMsg(:final error):
        _codeActions = const [];
        _codeActionIndex = -1;
        _codeActionError = error;
        return (this, null);
      case KeyMsg(key: final key):
        if (completionVisible) {
          switch (key.type) {
            case KeyType.up:
              moveCompletionSelection(-1);
              return (this, null);
            case KeyType.down:
              moveCompletionSelection(1);
              return (this, null);
            case KeyType.tab || KeyType.enter:
              acceptCompletion();
              return (this, null);
            case KeyType.escape:
              cancelCompletions();
              return (this, null);
            default:
              break;
          }
        }
        if (key.matchesSingle(keyMap.undo)) {
          executeCommand(EditorCommandIds.undo);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.redo)) {
          executeCommand(EditorCommandIds.redo);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.selectAll)) {
          executeCommand(EditorCommandIds.selectAll);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.selectLine)) {
          executeCommand(EditorCommandIds.selectLine);
          return (this, null);
        }

        // deletion
        if (key.matchesSingle(keyMap.deleteBeforeCursor)) {
          executeCommand(EditorCommandIds.deleteLeft);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteCharacterForward)) {
          executeCommand(EditorCommandIds.deleteRight);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteWordBackward)) {
          executeCommand(EditorCommandIds.deleteWordLeft);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteWordForward)) {
          executeCommand(EditorCommandIds.deleteWordRight);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteToLineStart)) {
          executeCommand(EditorCommandIds.deleteLineLeft);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteToLineEnd)) {
          executeCommand(EditorCommandIds.deleteLineRight);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.deleteAfterCursor)) {
          executeCommand(EditorCommandIds.deleteLineRight);
          return (this, null);
        }

        // navigation
        if (_matchesMovementBinding(key, keyMap.wordForward)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectWordRight
                : EditorCommandIds.cursorWordRight,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.wordBackward)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectWordLeft
                : EditorCommandIds.cursorWordLeft,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.lineStart)) {
          final visual = key.type == KeyType.home;
          late final String commandId;
          if (key.shift) {
            commandId = visual
                ? EditorCommandIds.selectVisualLineStart
                : EditorCommandIds.selectLineStart;
          } else {
            commandId = visual
                ? EditorCommandIds.cursorVisualLineStart
                : EditorCommandIds.cursorLineStart;
          }
          executeCommand(commandId);
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.lineEnd)) {
          final visual = key.type == KeyType.end;
          late final String commandId;
          if (key.shift) {
            commandId = visual
                ? EditorCommandIds.selectVisualLineEnd
                : EditorCommandIds.selectLineEnd;
          } else {
            commandId = visual
                ? EditorCommandIds.cursorVisualLineEnd
                : EditorCommandIds.cursorLineEnd;
          }
          executeCommand(commandId);
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.inputBegin)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectDocumentStart
                : EditorCommandIds.cursorDocumentStart,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.inputEnd)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectDocumentEnd
                : EditorCommandIds.cursorDocumentEnd,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.characterForward)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectRight
                : EditorCommandIds.cursorRight,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.characterBackward)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectLeft
                : EditorCommandIds.cursorLeft,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.lineNext)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectDown
                : EditorCommandIds.cursorDown,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.linePrevious)) {
          executeCommand(
            key.shift ? EditorCommandIds.selectUp : EditorCommandIds.cursorUp,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.pageUp)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectPageUp
                : EditorCommandIds.cursorPageUp,
          );
          return (this, null);
        }
        if (_matchesMovementBinding(key, keyMap.pageDown)) {
          executeCommand(
            key.shift
                ? EditorCommandIds.selectPageDown
                : EditorCommandIds.cursorPageDown,
          );
          return (this, null);
        }
        if (key.matchesSingle(keyMap.transposeCharacterBackward)) {
          executeCommand(EditorCommandIds.transposeCharacters);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.uppercaseWordForward)) {
          executeCommand(EditorCommandIds.uppercaseWord);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.lowercaseWordForward)) {
          executeCommand(EditorCommandIds.lowercaseWord);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.capitalizeWordForward)) {
          executeCommand(EditorCommandIds.capitalizeWord);
          return (this, null);
        }
        if (key.matchesSingle(keyMap.copy)) {
          final text = getSelectedText();
          if (text.isNotEmpty) {
            return (this, Cmd.setClipboardBestEffort(text));
          }
        }

        // Fallback direct modifier checks for common combos.
        if (key.type == KeyType.delete && key.alt) {
          executeCommand(EditorCommandIds.deleteWordRight);
          return (this, null);
        }
        if (key.ctrl && key.type == KeyType.runes && key.runes.isNotEmpty) {
          final r = key.runes.first;
          if (r == 0x74) {
            // ctrl+t
            executeCommand(EditorCommandIds.transposeCharacters);
            return (this, null);
          }
        }
        if (key.alt && key.type == KeyType.runes && key.runes.isNotEmpty) {
          final r = key.runes.first;
          if (r == 0x75) {
            executeCommand(EditorCommandIds.uppercaseWord);
            return (this, null);
          }
          if (r == 0x6c) {
            executeCommand(EditorCommandIds.lowercaseWord);
            return (this, null);
          }
          if (r == 0x63) {
            executeCommand(EditorCommandIds.capitalizeWord);
            return (this, null);
          }
        }

        if (key.type == KeyType.tab) {
          if (key.shift) {
            executeCommand(EditorCommandIds.outdentLines);
          } else {
            executeCommand(EditorCommandIds.indentLines);
          }
          return (this, null);
        }

        if (key.type == KeyType.space) {
          executeCommand(EditorCommandIds.insertText, argument: ' ');
          return (this, null);
        }

        if (key.type == KeyType.enter && keyMap.insertNewline.enabled) {
          executeCommand(EditorCommandIds.insertLineBreak);
          return (this, null);
        }

        if (key.type == KeyType.runes && key.runes.isNotEmpty) {
          final rune = key.runes.first;
          if (rune == 0x0a) {
            executeCommand(EditorCommandIds.insertLineBreak);
          } else {
            executeCommand(
              EditorCommandIds.insertText,
              argument: String.fromCharCode(rune),
            );
          }
          return (this, null);
        }
    }

    if (msg is MouseMsg) {
      final lineNumberDigits = _lineNumberDigits;
      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 = _nowProvider();

        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);
  });
}