compio_term\command/
mod.rs1use std::{fmt, io, mem};
2
3use compio_buf::{BufResult, IntoInner, IoBufExt};
4use compio_io::AsyncWrite;
5use crossterm::Command;
6
7mod multi;
8
9pub use multi::Commands;
10
11pub trait Queueable: AsyncWrite {
13 fn queue(&mut self, command: impl Command) -> io::Result<CommandQueue<&mut Self>>;
17
18 fn queue_many(&mut self, command: impl Commands) -> io::Result<CommandQueue<&mut Self>>;
25}
26
27impl<W> CommandQueue<W> {
28 pub fn queue(&mut self, command: impl Command) -> io::Result<&mut Self> {
34 self.append(|writer| command.write_ansi(writer))
35 }
36
37 pub fn queue_many(&mut self, commands: impl Commands) -> io::Result<&mut Self> {
44 self.append(|writer| commands.write_ansi(writer))
45 }
46}
47
48impl<W: AsyncWrite + ?Sized> Queueable for W {
49 fn queue(&mut self, command: impl Command) -> io::Result<CommandQueue<&mut Self>> {
50 let mut queue = CommandQueue::new(self);
51 queue.queue(command)?;
52 Ok(queue)
53 }
54
55 fn queue_many(&mut self, command: impl Commands) -> io::Result<CommandQueue<&mut Self>> {
56 let mut queue = CommandQueue::new(self);
57 queue.queue_many(command)?;
58 Ok(queue)
59 }
60}
61
62#[derive(Debug)]
89#[must_use = "queued commands are discarded unless flush is awaited"]
90pub struct CommandQueue<W> {
91 writer: W,
92 buffer: Vec<u8>,
93 written: usize,
94}
95
96impl<W> CommandQueue<W> {
97 pub fn new(writer: W) -> Self {
99 Self {
100 writer,
101 buffer: Vec::new(),
102 written: 0,
103 }
104 }
105
106 pub fn with_capacity(capacity: usize, writer: W) -> Self {
108 Self {
109 writer,
110 buffer: Vec::with_capacity(capacity),
111 written: 0,
112 }
113 }
114
115 pub fn buffered_len(&self) -> usize {
117 self.buffer.len() - self.written
118 }
119
120 pub fn is_empty(&self) -> bool {
122 self.buffered_len() == 0
123 }
124
125 fn append(
126 &mut self,
127 write: impl FnOnce(&mut AnsiBuffer<'_>) -> fmt::Result,
128 ) -> io::Result<&mut Self> {
129 let original_len = self.buffer.len();
130 if write(&mut AnsiBuffer(&mut self.buffer)).is_err() {
131 self.buffer.truncate(original_len);
132 return Err(io::Error::other(
133 "terminal command failed to render its ANSI representation",
134 ));
135 }
136 Ok(self)
137 }
138}
139
140impl CommandQueue<compio_fs::Stdout> {
141 pub fn stdout() -> Self {
143 Self::new(compio_fs::stdout())
144 }
145}
146
147impl CommandQueue<compio_fs::Stderr> {
148 pub fn stderr() -> Self {
150 Self::new(compio_fs::stderr())
151 }
152}
153
154impl<W: AsyncWrite> CommandQueue<W> {
155 pub async fn flush(&mut self) -> io::Result<()> {
161 while self.written < self.buffer.len() {
162 let buffer = mem::take(&mut self.buffer).slice(self.written..);
163 let BufResult(result, buffer) = self.writer.write(buffer).await;
164 self.buffer = buffer.into_inner();
165
166 match result {
167 Ok(0) => {
168 return Err(io::Error::new(
169 io::ErrorKind::WriteZero,
170 "failed to write queued terminal commands",
171 ));
172 }
173 Ok(written) => {
174 let remaining = self.buffer.len() - self.written;
175 if written > remaining {
176 return Err(io::Error::new(
177 io::ErrorKind::InvalidData,
178 "asynchronous writer reported too many written bytes",
179 ));
180 }
181 self.written += written;
182 }
183 Err(error) if error.kind() == io::ErrorKind::Interrupted => {}
184 Err(error) => return Err(error),
185 }
186 }
187
188 self.buffer.clear();
189 self.written = 0;
190 self.writer.flush().await
191 }
192}
193
194struct AnsiBuffer<'a>(&'a mut Vec<u8>);
195
196impl fmt::Write for AnsiBuffer<'_> {
197 fn write_str(&mut self, value: &str) -> fmt::Result {
198 self.0.extend_from_slice(value.as_bytes());
199 Ok(())
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::{cell::RefCell, fmt, io, rc::Rc};
206
207 use compio_buf::{BufResult, IoBuf, IoBufExt};
208 use compio_io::AsyncWrite;
209 use futures_util::FutureExt;
210
211 use super::CommandQueue;
212 use crate::{Command, cursor::MoveTo, style::Print};
213
214 #[test]
215 fn queues_commands_until_flush_in_order() {
216 let writer = TestWriter::new(2, None);
217 let state = writer.state();
218 let mut queue = CommandQueue::new(writer);
219 let expected = b"\x1b[5;4Hready";
220
221 queue
222 .queue(MoveTo(3, 4))
223 .unwrap()
224 .queue(Print("ready"))
225 .unwrap();
226
227 assert_eq!(queue.buffered_len(), expected.len());
228 assert!(state.borrow().output.is_empty());
229 flush(&mut queue).unwrap();
230 assert_eq!(&state.borrow().output, expected);
231 assert!(queue.is_empty());
232 }
233
234 #[test]
235 fn failed_command_does_not_leave_partial_bytes() {
236 let writer = TestWriter::new(usize::MAX, None);
237 let state = writer.state();
238 let mut queue = CommandQueue::new(writer);
239
240 queue.queue(Print("before")).unwrap();
241 let error = queue
242 .queue(FailingCommand)
243 .err()
244 .expect("failing command must return an error");
245 queue.queue(Print("after")).unwrap();
246 flush(&mut queue).unwrap();
247
248 assert_eq!(error.kind(), io::ErrorKind::Other);
249 assert_eq!(&state.borrow().output, b"beforeafter");
250 }
251
252 #[test]
253 fn retry_resumes_after_a_partial_write() {
254 let writer = TestWriter::new(2, Some(2));
255 let state = writer.state();
256 let mut queue = CommandQueue::new(writer);
257 queue.queue(Print("abcdef")).unwrap();
258
259 let error = flush(&mut queue).unwrap_err();
260 assert_eq!(error.kind(), io::ErrorKind::Other);
261 assert_eq!(&state.borrow().output, b"ab");
262
263 queue.queue(Print("gh")).unwrap();
264 flush(&mut queue).unwrap();
265 assert_eq!(&state.borrow().output, b"abcdefgh");
266 }
267
268 fn flush(queue: &mut CommandQueue<TestWriter>) -> io::Result<()> {
269 queue
270 .flush()
271 .now_or_never()
272 .expect("test writer must not yield")
273 }
274
275 struct FailingCommand;
276
277 impl Command for FailingCommand {
278 fn write_ansi(&self, writer: &mut impl fmt::Write) -> fmt::Result {
279 fmt::Write::write_str(writer, "partial")?;
280 Err(fmt::Error)
281 }
282
283 #[cfg(windows)]
284 fn execute_winapi(&self) -> io::Result<()> {
285 Ok(())
286 }
287 }
288
289 #[derive(Clone)]
290 struct TestWriter {
291 state: Rc<RefCell<WriterState>>,
292 }
293
294 impl TestWriter {
295 fn new(chunk_size: usize, fail_at: Option<usize>) -> Self {
296 Self {
297 state: Rc::new(RefCell::new(WriterState {
298 output: Vec::new(),
299 chunk_size,
300 writes: 0,
301 fail_at,
302 })),
303 }
304 }
305
306 fn state(&self) -> Rc<RefCell<WriterState>> {
307 Rc::clone(&self.state)
308 }
309 }
310
311 struct WriterState {
312 output: Vec<u8>,
313 chunk_size: usize,
314 writes: usize,
315 fail_at: Option<usize>,
316 }
317
318 impl AsyncWrite for TestWriter {
319 async fn write<T: IoBuf>(&mut self, buffer: T) -> BufResult<usize, T> {
320 let mut state = self.state.borrow_mut();
321 state.writes += 1;
322 if state.fail_at == Some(state.writes) {
323 state.fail_at = None;
324 return BufResult(Err(io::Error::other("injected write failure")), buffer);
325 }
326
327 let written = state.chunk_size.min(buffer.buf_len());
328 state.output.extend_from_slice(&buffer.as_init()[..written]);
329 BufResult(Ok(written), buffer)
330 }
331
332 async fn flush(&mut self) -> io::Result<()> {
333 Ok(())
334 }
335
336 async fn shutdown(&mut self) -> io::Result<()> {
337 Ok(())
338 }
339 }
340}