Fix terminal clear command to remove residual output (#13531)

* Fix terminal clear command to remove residual output

* Update flutter/lib/models/terminal_model.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update flutter/lib/desktop/pages/terminal_page.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix: Prevent "Build scheduled during frame" in terminal resize

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
alonginwind
2025-11-18 23:31:54 +08:00
committed by GitHub
parent 7f804a0e45
commit ef62f1db29
2 changed files with 55 additions and 19 deletions

View File

@@ -41,6 +41,7 @@ class _TerminalPageState extends State<TerminalPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
late FFI _ffi; late FFI _ffi;
late TerminalModel _terminalModel; late TerminalModel _terminalModel;
double? _cellHeight;
@override @override
void initState() { void initState() {
@@ -60,6 +61,17 @@ class _TerminalPageState extends State<TerminalPage>
debugPrint( debugPrint(
'[TerminalPage] Terminal model created for terminal ${widget.terminalId}'); '[TerminalPage] Terminal model created for terminal ${widget.terminalId}');
_terminalModel.onResizeExternal = (w, h, pw, ph) {
_cellHeight = ph * 1.0;
// Schedule the setState for the next frame
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {});
}
});
};
// Register this terminal model with FFI for event routing // Register this terminal model with FFI for event routing
_ffi.registerTerminalModel(widget.terminalId, _terminalModel); _ffi.registerTerminalModel(widget.terminalId, _terminalModel);
@@ -95,30 +107,48 @@ class _TerminalPageState extends State<TerminalPage>
super.dispose(); super.dispose();
} }
// This method ensures that the number of visible rows is an integer by computing the
// extra space left after dividing the available height by the height of a single
// terminal row (`_cellHeight`) and distributing it evenly as top and bottom padding.
EdgeInsets _calculatePadding(double heightPx) {
if (_cellHeight == null) {
return const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2.0);
}
final rows = (heightPx / _cellHeight!).floor();
final extraSpace = heightPx - rows * _cellHeight!;
final topBottom = extraSpace / 2.0;
return EdgeInsets.symmetric(horizontal: 5.0, vertical: topBottom);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: TerminalView( body: LayoutBuilder(
_terminalModel.terminal, builder: (context, constraints) {
controller: _terminalModel.terminalController, final heightPx = constraints.maxHeight;
autofocus: true, return TerminalView(
backgroundOpacity: 0.7, _terminalModel.terminal,
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2.0), controller: _terminalModel.terminalController,
onSecondaryTapDown: (details, offset) async { autofocus: true,
final selection = _terminalModel.terminalController.selection; backgroundOpacity: 0.7,
if (selection != null) { padding: _calculatePadding(heightPx),
final text = _terminalModel.terminal.buffer.getText(selection); onSecondaryTapDown: (details, offset) async {
_terminalModel.terminalController.clearSelection(); final selection = _terminalModel.terminalController.selection;
await Clipboard.setData(ClipboardData(text: text)); if (selection != null) {
} else { final text = _terminalModel.terminal.buffer.getText(selection);
final data = await Clipboard.getData('text/plain'); _terminalModel.terminalController.clearSelection();
final text = data?.text; await Clipboard.setData(ClipboardData(text: text));
if (text != null) { } else {
_terminalModel.terminal.paste(text); final data = await Clipboard.getData('text/plain');
} final text = data?.text;
} if (text != null) {
_terminalModel.terminal.paste(text);
}
}
},
);
}, },
), ),
); );

View File

@@ -27,6 +27,8 @@ class TerminalModel with ChangeNotifier {
bool get isPeerWindows => parent.ffiModel.pi.platform == kPeerPlatformWindows; bool get isPeerWindows => parent.ffiModel.pi.platform == kPeerPlatformWindows;
void Function(int w, int h, int pw, int ph)? onResizeExternal;
Future<void> _handleInput(String data) async { Future<void> _handleInput(String data) async {
// If we press the `Enter` button on Android, // If we press the `Enter` button on Android,
// `data` can be '\r' or '\n' when using different keyboards. // `data` can be '\r' or '\n' when using different keyboards.
@@ -68,6 +70,10 @@ class TerminalModel with ChangeNotifier {
if (w > 0 && h > 0 && pw > 0 && ph > 0) { if (w > 0 && h > 0 && pw > 0 && ph > 0) {
debugPrint( debugPrint(
'[TerminalModel] Terminal resized to ${w}x$h (pixel: ${pw}x$ph)'); '[TerminalModel] Terminal resized to ${w}x$h (pixel: ${pw}x$ph)');
// This piece of code must be placed before the conditional check in order to initialize properly.
onResizeExternal?.call(w, h, pw, ph);
if (_terminalOpened) { if (_terminalOpened) {
// Notify remote terminal of resize // Notify remote terminal of resize
try { try {