Skip to content

Commit 84f8969

Browse files
fcoury-oaicopyberry
authored andcommitted
Move the TUI cursor before showing it
## Why Showing the cursor before moving it can briefly expose it at its previous position during a draw. ## What changed Reorder terminal cursor updates so the cursor is positioned before it is made visible. ## Testing Add a regression test that verifies the cursor move escape sequence is emitted before the show-cursor sequence. GitOrigin-RevId: 2df277ba315bef4a7a56c11ecb78fcd52c354ac5
1 parent a73485d commit 84f8969

1 file changed

Lines changed: 42 additions & 3 deletions

File tree

codex-rs/tui/src/custom_terminal.rs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ where
429429
None => self.hide_cursor()?,
430430
Some(position) => {
431431
self.set_cursor_style(cursor_style)?;
432-
self.show_cursor()?;
433432
self.set_cursor_position(position)?;
433+
self.show_cursor()?;
434434
}
435435
}
436436

@@ -896,11 +896,11 @@ mod tests {
896896
}
897897

898898
fn hide_cursor(&mut self) -> io::Result<()> {
899-
Ok(())
899+
queue!(self, crossterm::cursor::Hide)
900900
}
901901

902902
fn show_cursor(&mut self) -> io::Result<()> {
903-
Ok(())
903+
queue!(self, crossterm::cursor::Show)
904904
}
905905

906906
fn get_cursor_position(&mut self) -> io::Result<Position> {
@@ -909,6 +909,8 @@ mod tests {
909909

910910
fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> io::Result<()> {
911911
self.cursor = position.into();
912+
let Position { x, y } = self.cursor;
913+
queue!(self, MoveTo(x, y))?;
912914
Ok(())
913915
}
914916

@@ -1255,6 +1257,43 @@ mod tests {
12551257
);
12561258
}
12571259

1260+
#[test]
1261+
fn terminal_draw_moves_cursor_before_showing_it() {
1262+
let cursor_position = Position { x: 1, y: 0 };
1263+
let mut terminal =
1264+
Terminal::with_options(CaptureBackend::new(/*width*/ 2, /*height*/ 1))
1265+
.expect("terminal");
1266+
terminal.set_viewport_area(Rect::new(
1267+
/*x*/ 0, /*y*/ 0, /*width*/ 2, /*height*/ 1,
1268+
));
1269+
1270+
terminal
1271+
.try_draw(|frame| {
1272+
frame.set_cursor_position(cursor_position);
1273+
io::Result::Ok(())
1274+
})
1275+
.expect("draw");
1276+
1277+
let mut expected_move = Vec::new();
1278+
queue!(expected_move, MoveTo(cursor_position.x, cursor_position.y)).expect("queue move");
1279+
let expected_move = String::from_utf8(expected_move).expect("move utf8");
1280+
let mut expected_show = Vec::new();
1281+
queue!(expected_show, crossterm::cursor::Show).expect("queue show");
1282+
let expected_show = String::from_utf8(expected_show).expect("show utf8");
1283+
let actual = terminal.backend().output();
1284+
let move_index = actual.find(&expected_move).expect("cursor move");
1285+
let show_index = actual.find(&expected_show).expect("cursor show");
1286+
1287+
assert!(
1288+
move_index < show_index,
1289+
"expected cursor move before show, got {actual:?}"
1290+
);
1291+
assert_snapshot!(
1292+
actual[move_index..].escape_debug().to_string(),
1293+
@r"\u{1b}[1;2H\u{1b}[?25h"
1294+
);
1295+
}
1296+
12581297
#[test]
12591298
fn reset_cursor_style_emits_default_user_shape() {
12601299
let mut output = Vec::new();

0 commit comments

Comments
 (0)