run method
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();
}
}
}