Merge branch 'master' into akbash/2674-shifted-backslash

This commit is contained in:
Can Celik
2026-08-11 22:57:14 +03:00
committed by GitHub
3 changed files with 88 additions and 13 deletions
+1
View File
@@ -20,6 +20,7 @@
### Fixed
- Prefix keybindings now disambiguate layout-aware shifted punctuation, so a shifted `\` no longer triggers `prefix+|` on keyboard layouts where the same key produces both characters. (#2674)
- Remote clients now continue redrawing at very large terminal sizes instead of freezing when a full ANSI frame exceeds the transport limit. (#2670)
- OpenCode panes now track the root conversation selected in their own TUI for native restore without adopting activity from attached clients. (#2450)
- Server stop requests now bypass pane and API traffic, preventing busy sessions from blocking shutdown or admitting a client while shutdown is pending. (#2612)
- Fish `Ctrl+Alt` keybindings now work in panes after legacy Alt-prefixed control bytes are decoded with both modifiers. (#2514)
+13 -13
View File
@@ -633,9 +633,11 @@ fn write_ime_anchor_cursor_state(writer: &mut impl Write, cursor: HostCursorStat
}
fn write_all_cells(writer: &mut impl Write, frame: &FrameData) {
let mut last_sgr = String::new();
let mut active_hyperlink = None;
for row in 0..frame.height {
let mut to_skip = 0usize;
let mut next_inline_col = None;
for col in 0..frame.width {
if to_skip > 0 {
to_skip -= 1;
@@ -646,25 +648,23 @@ fn write_all_cells(writer: &mut impl Write, frame: &FrameData) {
let cell = &frame.cells[idx];
if cell.skip {
next_inline_col = None;
continue;
}
// Move cursor to position (1-based).
let _ = write!(writer, "\x1b[{};{}H", row + 1, col + 1);
// Set style.
let sgr = build_sgr(cell.fg, cell.bg, cell.modifier);
let _ = writer.write_all(sgr.as_bytes());
write_hyperlink_if_changed(
let cursor_position = (next_inline_col != Some(col)).then_some((col, row));
write_cell(
writer,
cursor_position,
cell,
&mut last_sgr,
&mut active_hyperlink,
cell_hyperlink_uri(frame, cell),
frame,
);
// Write the symbol.
let _ = writer.write_all(cell.symbol.as_bytes());
to_skip = cell_width(cell).saturating_sub(1);
let width = cell_width(cell);
next_inline_col =
(cell.symbol.is_ascii() && width == 1).then_some(col.saturating_add(1));
to_skip = width.saturating_sub(1);
}
}
+74
View File
@@ -9155,6 +9155,80 @@ next_tab = ""
);
}
#[test]
fn render_and_stream_sends_large_terminal_frame_for_terminal_ansi_client() {
let mut server = test_headless_server();
server.app.state.workspaces = vec![crate::workspace::Workspace::test_new("test")];
server.app.state.ensure_test_terminals();
server.app.state.active = Some(0);
server.app.state.selected = 0;
server.app.state.mode = crate::app::Mode::Terminal;
let (client_tx, _client_control_rx, client_rx) = test_client_writer();
server.clients.insert(
1,
ClientConnection::new(
(278, 85),
crate::kitty_graphics::HostCellSize::default(),
crate::terminal_theme::TerminalTheme::default(),
None,
1,
RenderEncoding::TerminalAnsi,
Some(client_tx),
),
);
server.foreground_client_id = Some(1);
server.sync_foreground_client_state();
server.render_and_stream();
match read_server_message(
client_rx
.recv_timeout(Duration::from_millis(100))
.expect("initial terminal frame"),
) {
ServerMessage::Terminal(frame) => {
assert_eq!(frame.seq, 1);
assert_eq!((frame.width, frame.height), (278, 85));
assert!(frame.full);
}
other => panic!("expected terminal frame, got {other:?}"),
}
assert!(server.handle_server_event(ServerEvent::ClientResize {
client_id: 1,
cols: 710,
rows: 202,
cell_width_px: 0,
cell_height_px: 0,
}));
server.render_and_stream();
match read_server_message(
client_rx
.recv_timeout(Duration::from_millis(100))
.expect("large terminal frame"),
) {
ServerMessage::Terminal(frame) => {
assert_eq!(frame.seq, 2);
assert_eq!((frame.width, frame.height), (710, 202));
assert!(frame.full);
assert!(!frame.bytes.is_empty());
}
other => panic!("expected terminal frame, got {other:?}"),
}
server.app.state.mode = crate::app::Mode::Navigate;
server.render_and_stream();
match read_server_message(
client_rx
.recv_timeout(Duration::from_millis(100))
.expect("follow-up terminal frame"),
) {
ServerMessage::Terminal(frame) => assert_eq!(frame.seq, 3),
other => panic!("expected terminal frame, got {other:?}"),
}
}
#[test]
fn terminal_ansi_input_does_not_reset_blit_baseline() {
let mut server = test_headless_server();