wip: in-progress changes (grid, config, wizard, settings, pty) before session-persistence

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 15:28:19 +07:00
parent e37faf49d3
commit 4419f5660e
18 changed files with 365 additions and 42 deletions
+49 -5
View File
@@ -1,4 +1,6 @@
use alacritty_terminal::event::VoidListener;
use std::sync::{Arc, Mutex};
use alacritty_terminal::event::{Event, EventListener};
use alacritty_terminal::grid::Dimensions;
use alacritty_terminal::index::{Column, Line, Point};
use alacritty_terminal::term::{Config, Term};
@@ -23,24 +25,55 @@ impl Dimensions for GridSize {
}
}
/// Collects the escape sequences the terminal model wants written back to the PTY
/// (Primary/Secondary Device Attributes, DSR cursor/status reports, etc.). Programs
/// like fish block on these replies at startup; with a void listener they hang ~2s
/// and then warn ("could not read response to Primary Device Attribute query").
#[derive(Clone, Default)]
pub struct ReplyCollector {
buf: Arc<Mutex<Vec<u8>>>,
}
impl EventListener for ReplyCollector {
fn send_event(&self, event: Event) {
if let Event::PtyWrite(text) = event {
if let Ok(mut b) = self.buf.lock() {
b.extend_from_slice(text.as_bytes());
}
}
}
}
/// Owns an alacritty terminal model and feeds raw PTY bytes into it.
pub struct GridSurface {
term: Term<VoidListener>,
term: Term<ReplyCollector>,
parser: Processor,
size: GridSize,
replies: ReplyCollector,
}
impl GridSurface {
pub fn new(cols: u16, rows: u16) -> Self {
let size = GridSize { cols: cols as usize, lines: rows as usize };
let term = Term::new(Config::default(), &size, VoidListener);
Self { term, parser: Processor::new(), size }
let replies = ReplyCollector::default();
let term = Term::new(Config::default(), &size, replies.clone());
Self { term, parser: Processor::new(), size, replies }
}
pub fn feed(&mut self, bytes: &[u8]) {
self.parser.advance(&mut self.term, bytes);
}
/// Drain any escape sequences the model produced in response to queries fed so
/// far. The caller must write these back to the PTY for query-driven programs
/// (fish, vim, etc.) to proceed without timing out.
pub fn take_replies(&mut self) -> Vec<u8> {
match self.replies.buf.lock() {
Ok(mut b) => std::mem::take(&mut *b),
Err(_) => Vec::new(),
}
}
pub fn resize(&mut self, cols: u16, rows: u16) {
self.size = GridSize { cols: cols as usize, lines: rows as usize };
self.term.resize(self.size);
@@ -56,7 +89,7 @@ impl GridSurface {
self.term.grid()[point].c
}
pub fn term(&self) -> &Term<VoidListener> {
pub fn term(&self) -> &Term<ReplyCollector> {
&self.term
}
@@ -97,4 +130,15 @@ mod tests {
assert_eq!(g.char_at(0, 0), 'a');
assert_eq!(g.char_at(1, 0), 'c');
}
#[test]
fn primary_device_attribute_query_gets_a_reply() {
// fish (and friends) send ESC[c at startup and block on the response.
let mut g = GridSurface::new(20, 5);
g.feed(b"\x1b[c");
let reply = g.take_replies();
assert!(reply.starts_with(b"\x1b[?"), "expected a DA1 reply, got {reply:?}");
// Replies are drained, not duplicated.
assert!(g.take_replies().is_empty());
}
}