run method

Future<void> run()

Runs the IDE until the user quits, restoring the terminal afterwards.

Implementation

Future<void> run() async {
  // Report the mouse wheel so it scrolls the pane under the pointer (see
  // [_handleWheel]).
  _terminal.enter(mouseWheel: true);
  final decoder = KeyDecoder(mouseReports: true);
  StreamSubscription<List<int>>? inputSub;
  StreamSubscription<void>? resizeSub;
  LoneEscapeTimer? escape;
  try {
    // Load the tree root and git state before the first paint.
    _repo = await GitRepo.discover(
      _workspace,
      rootPath,
      syntax: _commandSyntax,
    );
    await _tree.init();
    await _refreshGit();
    _render();
    // Input is handled asynchronously (file ops await the workspace), so the
    // subscription is paused while a chunk is processed and resumed after, to
    // serialise key handling and keep ordering deterministic.
    Future<void> dispatch(List<KeyEvent> keys) async {
      inputSub!.pause();
      for (final key in keys) {
        // Never let an unexpected error from a key handler crash or freeze
        // the TUI: surface it in the status bar and keep the loop alive.
        try {
          await _handleKey(key);
        } on Object catch (e) {
          _setMessage('Unexpected error: $e', isError: true);
        }
        if (_done.isCompleted) break;
      }
      if (!_done.isCompleted) _render();
      if (!_done.isCompleted) inputSub!.resume();
    }

    final escapeTimer = escape = LoneEscapeTimer(decoder, (keys) {
      if (!_done.isCompleted) unawaited(dispatch(keys));
    });
    inputSub = _terminal.input.listen(
      (bytes) async {
        escapeTimer.cancel();
        await dispatch(decoder.decode(bytes));
        if (!_done.isCompleted) escapeTimer.arm();
      },
      onError: (_) => _finish(),
      onDone: _finish,
    );
    resizeSub = _terminal.resizeEvents.listen((_) {
      _terminal.invalidate();
      _render();
    });
    await _done.future;
  } finally {
    // The terminal is restored even if a teardown step throws (e.g. closing
    // a remote workspace whose connection dropped): a skipped leave() would
    // strand the user's shell in the alternate screen with mouse reporting
    // on, printing a report for every click and scroll.
    try {
      escape?.cancel();
      await inputSub?.cancel();
      await resizeSub?.cancel();
      await _terminalPanel?.dispose();
      if (_confirm != null && !_confirm!.completer.isCompleted) {
        _confirm!.completer.complete(false);
      }
      _agentBackend?.close();
      await _workspace.close();
    } finally {
      _terminal.leave();
    }
  }
}