update method

  1. @override
(ViewportModel, 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
(ViewportModel, Cmd?) update(Msg msg) {
  final Stopwatch? sw = TuiTrace.enabled ? Stopwatch() : null;
  sw?.start();
  switch (msg) {
    case KeyMsg(:final key):
      if (key.matchesSingle(keyMap.pageDown)) {
        final next = pageDown();
        _traceUpdate('pageDown', next, sw);
        return (next, null);
      }
      if (key.matchesSingle(keyMap.pageUp)) {
        final next = pageUp();
        _traceUpdate('pageUp', next, sw);
        return (next, null);
      }
      if (key.matchesSingle(keyMap.halfPageDown)) {
        final next = halfPageDown();
        _traceUpdate('halfPageDown', next, sw);
        return (next, null);
      }
      if (key.matchesSingle(keyMap.halfPageUp)) {
        final next = halfPageUp();
        _traceUpdate('halfPageUp', next, sw);
        return (next, null);
      }
      if (key.matchesSingle(keyMap.down)) {
        final next = scrollDown(1);
        _traceUpdate('down', next, sw);
        return (next, null);
      }
      if (key.matchesSingle(keyMap.up)) {
        final next = scrollUp(1);
        _traceUpdate('up', next, sw);
        return (next, null);
      }
      if (horizontalStep > 0) {
        if (!softWrap && key.matchesSingle(keyMap.left)) {
          final next = scrollLeft(horizontalStep);
          _traceUpdate('left', next, sw);
          return (next, null);
        }
        if (!softWrap && key.matchesSingle(keyMap.right)) {
          final next = scrollRight(horizontalStep);
          _traceUpdate('right', next, sw);
          return (next, null);
        }
      }
      if (key.matchesSingle(keyMap.copy)) {
        final text = getSelectedText();
        if (text.isNotEmpty) {
          _traceUpdate('copy', this, sw);
          return (this, Cmd.setClipboard(text));
        }
      }
      _traceUpdate('key', this, sw);
      return (this, null);

    case MouseMsg(
      :final button,
      :final action,
      :final x,
      :final y,
      :final shift,
    ):
      if (button == MouseButton.left) {
        final isOutside =
            y < 0 || (height != null ? y >= height! : y >= lines.length);
        if (isOutside) {
          if (action == MouseAction.press) {
            final next = clearSelection();
            _traceUpdate('mouse-clear', next, sw);
            return (next, null);
          }
          _traceUpdate('mouse-outside', this, sw);
          return (this, null);
        }

        if (action == MouseAction.press) {
          final contentX = x - gutter + xOffset;
          final contentY = y + yOffset;
          final now = DateTime.now();

          // Check for double click
          if (lastClickTime != null &&
              now.difference(lastClickTime!) <
                  const Duration(milliseconds: 500) &&
              lastClickPos == (contentX, contentY)) {
            final (start, end) = _findWordAt(contentX, contentY);
            final next = copyWith(
              selectionStart: (start, contentY),
              selectionEnd: (end, contentY),
              lastClickTime: now,
              lastClickPos: (contentX, contentY),
            );
            _traceUpdate('mouse-double', next, sw);
            return (next, null);
          }

          // Start selection
          final next = copyWith(
            selectionStart: (contentX, contentY),
            selectionEnd: (contentX, contentY),
            lastClickTime: now,
            lastClickPos: (contentX, contentY),
          );
          _traceUpdate('mouse-press', next, sw);
          return (next, null);
        }

        if (action == MouseAction.motion && selectionStart != null) {
          // Update selection
          final contentX = x - gutter + xOffset;
          final contentY = y + yOffset;
          final next = copyWith(selectionEnd: (contentX, contentY));
          _traceUpdate('mouse-drag', next, sw);
          return (next, null);
        }

        if (action == MouseAction.release && button == MouseButton.left) {
          // Finalize selection (keep it for copying)
          _traceUpdate('mouse-release', this, sw);
          return (this, null);
        }
      }

      if (!mouseWheelEnabled ||
          (action != MouseAction.press && action != MouseAction.wheel)) {
        _traceUpdate('mouse-ignore', this, sw);
        return (this, null);
      }

      switch (button) {
        case MouseButton.wheelUp:
          if (!softWrap && shift && horizontalStep > 0) {
            final next = scrollLeft(horizontalStep);
            _traceUpdate('wheel-left', next, sw);
            return (next, null);
          }
          final next = scrollUp(mouseWheelDelta);
          _traceUpdate('wheel-up', next, sw);
          return (next, null);

        case MouseButton.wheelDown:
          if (!softWrap && shift && horizontalStep > 0) {
            final next = scrollRight(horizontalStep);
            _traceUpdate('wheel-right', next, sw);
            return (next, null);
          }
          final next = scrollDown(mouseWheelDelta);
          _traceUpdate('wheel-down', next, sw);
          return (next, null);

        case MouseButton.wheelLeft:
          if (!softWrap && horizontalStep > 0) {
            final next = scrollLeft(horizontalStep);
            _traceUpdate('wheel-left', next, sw);
            return (next, null);
          }
          _traceUpdate('wheel-left', this, sw);
          return (this, null);

        case MouseButton.wheelRight:
          if (!softWrap && horizontalStep > 0) {
            final next = scrollRight(horizontalStep);
            _traceUpdate('wheel-right', next, sw);
            return (next, null);
          }
          _traceUpdate('wheel-right', this, sw);
          return (this, null);

        default:
          _traceUpdate('mouse-default', this, sw);
          return (this, null);
      }

    default:
      _traceUpdate('msg', this, sw);
      return (this, null);
  }
}