mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 08:01:06 +00:00
Merge branch 'master' into akbash/2674-shifted-backslash
This commit is contained in:
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user