1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! `shout` — buffered terminal-output stream for the ZLE display.
//!
//! Rust-only infrastructure, not a port: it supplies the stdio buffering
//! that zsh gets for free from libc. C's display code writes every escape,
//! pad byte and glyph through `FILE *shout` (`putc`/`fputs`/
//! `tputs(..., putshout)`) and flushes ONCE per frame — `fflush(shout)` at
//! `Src/Zle/zle_refresh.c:1488` and `Src/Zle/zle_main.c:2090` — so a repaint
//! reaches the terminal as a single write.
//!
//! zshrs had no such stream: every fragment went straight to
//! `write_loop(SHTTY, …)`, one `write(2)` per escape or cell. Measured on a
//! 20-match `menu select` list (100x40 pty): zsh delivered the frame in 2 tty
//! writes, zshrs in 22+. The terminal renders each fragment as it lands, so
//! the repaint is visible as the cursor stepping across the menu.
//!
//! [`begin`]/[`end`] bracket a frame; writes inside accumulate and go out as
//! one `write_loop` when the outermost region closes. Outside a region
//! [`write`] passes through immediately, so no path that never opened a
//! region can strand output in the buffer. [`flush`] empties the buffer
//! without closing the region — the equivalent of C's `fflush(shout)`, to be
//! called before anything that blocks on the user (a key read).
use crate::ported::init::SHTTY;
use crate::ported::utils::write_loop;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
/// Bytes accumulated by the currently open region.
static BUF: Mutex<Vec<u8>> = Mutex::new(Vec::new());
/// Nesting depth of open [`begin`] regions; 0 = write through.
static DEPTH: AtomicI32 = AtomicI32::new(0);
/// Open a buffered region. Writes accumulate until the matching [`end`].
pub fn begin() {
DEPTH.fetch_add(1, Ordering::SeqCst);
}
/// Close a buffered region; the outermost close flushes the frame.
pub fn end() {
if DEPTH.fetch_sub(1, Ordering::SeqCst) <= 1 {
DEPTH.store(0, Ordering::SeqCst);
flush();
}
}
/// Write out what the region has accumulated, leaving it open
/// (C: `fflush(shout)`).
pub fn flush() {
let pending = {
let mut buf = match BUF.lock() {
Ok(b) => b,
Err(poisoned) => poisoned.into_inner(),
};
if buf.is_empty() {
return;
}
std::mem::take(&mut *buf)
};
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, &pending);
}
/// Write to the terminal through `shout`: buffered inside a region,
/// immediate outside one.
pub fn write(bytes: &[u8]) {
if bytes.is_empty() {
return;
}
if DEPTH.load(Ordering::SeqCst) > 0 {
if let Ok(mut buf) = BUF.lock() {
buf.extend_from_slice(bytes);
return;
}
}
let fd = SHTTY.load(Ordering::Relaxed);
let _ = write_loop(if fd >= 0 { fd } else { 1 }, bytes);
}
#[cfg(test)]
mod tests {
use super::*;
/// A region defers its writes and emits them in order on close; the
/// depth counter must survive nesting so an inner `end` does not flush
/// a frame the outer region is still building.
#[test]
fn nested_region_defers_until_outermost_end() {
let _g = crate::test_util::global_state_lock();
let mut fds: [libc::c_int; 2] = [0; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe(2) ok");
let saved = SHTTY.load(Ordering::Relaxed);
SHTTY.store(fds[1], Ordering::Relaxed);
begin();
write(b"outer-");
begin();
write(b"inner");
end(); // inner close: still buffered
let mut probe = [0u8; 16];
let ready = unsafe {
let mut set: libc::fd_set = std::mem::zeroed();
libc::FD_SET(fds[0], &mut set);
let mut tv = libc::timeval {
tv_sec: 0,
tv_usec: 0,
};
libc::select(
fds[0] + 1,
&mut set,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut tv,
)
};
assert_eq!(ready, 0, "inner end must not flush the outer frame");
end(); // outer close: one write, in order
let n = unsafe {
libc::read(
fds[0],
probe.as_mut_ptr() as *mut libc::c_void,
probe.len(),
)
};
SHTTY.store(saved, Ordering::Relaxed);
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
assert!(n > 0, "frame reached the fd");
assert_eq!(&probe[..n as usize], b"outer-inner");
}
/// Outside a region every write goes straight to the fd, so code paths
/// that never call `begin` keep working unchanged.
#[test]
fn write_outside_region_is_immediate() {
let _g = crate::test_util::global_state_lock();
let mut fds: [libc::c_int; 2] = [0; 2];
assert_eq!(unsafe { libc::pipe(fds.as_mut_ptr()) }, 0, "pipe(2) ok");
let saved = SHTTY.load(Ordering::Relaxed);
SHTTY.store(fds[1], Ordering::Relaxed);
write(b"direct");
let mut probe = [0u8; 16];
let n = unsafe {
libc::read(
fds[0],
probe.as_mut_ptr() as *mut libc::c_void,
probe.len(),
)
};
SHTTY.store(saved, Ordering::Relaxed);
unsafe {
libc::close(fds[0]);
libc::close(fds[1]);
}
assert_eq!(&probe[..n.max(0) as usize], b"direct");
}
}