mortal/screen.rs
1//! Provides a drawable buffer on terminal devices
2
3use std::fmt;
4use std::io;
5use std::sync::{LockResult, TryLockResult};
6use std::time::Duration;
7
8use crate::priv_util::{map_lock_result, map_try_lock_result};
9use crate::sys;
10use crate::terminal::{
11 Color, Cursor, CursorMode, Event, PrepareConfig, Size, Style, Theme,
12 Terminal,
13};
14
15/// Provides operations on an underlying terminal device in screen mode.
16///
17/// `Screen` uses an internal buffer to store rendered text, colors, and style.
18///
19/// Each set of changes must be followed by a call to [`refresh`] to flush these
20/// changes to the terminal device.
21///
22/// # Concurrency
23///
24/// Access to read and write operations is controlled by two internal locks:
25/// One for [reading] and one for [writing]. Each lock may be held independently
26/// of the other.
27///
28/// If any one thread wishes to hold both locks, the read lock
29/// must be acquired first, in order to prevent deadlocks.
30///
31/// [`refresh`]: #method.refresh
32/// [reading]: struct.ScreenReadGuard.html
33/// [writing]: struct.ScreenWriteGuard.html
34pub struct Screen(sys::Screen);
35
36/// Holds an exclusive lock for read operations on a `Screen`
37///
38/// See [`Screen`] documentation for details on locking.
39///
40/// [`Screen`]: struct.Screen.html
41pub struct ScreenReadGuard<'a>(sys::ScreenReadGuard<'a>);
42
43/// Holds an exclusive lock for write operations on a `Screen`
44///
45/// See [`Screen`] documentation for details on locking.
46///
47/// [`Screen`]: struct.Screen.html
48pub struct ScreenWriteGuard<'a>(sys::ScreenWriteGuard<'a>);
49
50impl Screen {
51 /// Opens a new screen interface on `stdout`.
52 pub fn new(config: PrepareConfig) -> io::Result<Screen> {
53 sys::Screen::stdout(config).map(Screen)
54 }
55
56 /// Opens a new screen interface on `stderr`.
57 pub fn stderr(config: PrepareConfig) -> io::Result<Screen> {
58 sys::Screen::stderr(config).map(Screen)
59 }
60
61 /// Begins a new screen session using the given `Terminal` instance.
62 pub fn with_terminal(term: Terminal, config: PrepareConfig) -> io::Result<Screen> {
63 sys::Screen::new(term.0, config).map(Screen)
64 }
65
66 /// Returns the name of the terminal.
67 ///
68 /// # Notes
69 ///
70 /// On Unix, this method returns the contents of the `TERM` environment variable.
71 ///
72 /// On Windows, this method always returns the string `"windows-console"`.
73 #[inline]
74 pub fn name(&self) -> &str {
75 self.0.name()
76 }
77
78 /// Attempts to acquire an exclusive lock on terminal read operations.
79 ///
80 /// The current thread will block until the lock can be acquired.
81 #[inline]
82 pub fn lock_read(&self) -> LockResult<ScreenReadGuard> {
83 map_lock_result(self.0.lock_read(), ScreenReadGuard)
84 }
85
86 /// Attempts to acquire an exclusive lock on terminal write operations.
87 ///
88 /// The current thread will block until the lock can be acquired.
89 #[inline]
90 pub fn lock_write(&self) -> LockResult<ScreenWriteGuard> {
91 map_lock_result(self.0.lock_write(), ScreenWriteGuard)
92 }
93
94 /// Attempts to acquire an exclusive lock on terminal read operations.
95 ///
96 /// If the lock cannot be acquired immediately, `Err(_)` is returned.
97 #[inline]
98 pub fn try_lock_read(&self) -> TryLockResult<ScreenReadGuard> {
99 map_try_lock_result(self.0.try_lock_read(), ScreenReadGuard)
100 }
101
102 /// Attempts to acquire an exclusive lock on terminal write operations.
103 ///
104 /// If the lock cannot be acquired immediately, `Err(_)` is returned.
105 #[inline]
106 pub fn try_lock_write(&self) -> TryLockResult<ScreenWriteGuard> {
107 map_try_lock_result(self.0.try_lock_write(), ScreenWriteGuard)
108 }
109}
110
111/// # Locking
112///
113/// The following methods internally acquire the read lock.
114///
115/// The lock is released before the method returns.
116///
117/// These methods are also implemented on [`ScreenReadGuard`],
118/// which holds the `Screen` read lock until the value is dropped.
119///
120/// [`ScreenReadGuard`]: struct.ScreenReadGuard.html
121impl Screen {
122 /// Waits for an event from the terminal.
123 ///
124 /// Returns `Ok(false)` if `timeout` elapses without an event occurring.
125 ///
126 /// If `timeout` is `None`, this method will wait indefinitely.
127 ///
128 /// # Notes
129 ///
130 /// Some low-level terminal events may not generate an `Event` value.
131 /// Therefore, this method may return `Ok(true)`, while a follow-up call
132 /// to `read_event` may not immediately return an event.
133 pub fn wait_event(&self, timeout: Option<Duration>) -> io::Result<bool> {
134 self.0.wait_event(timeout)
135 }
136
137 /// Reads an event from the terminal.
138 ///
139 /// If `timeout` elapses without an event occurring, this method will return
140 /// `Ok(None)`.
141 ///
142 /// If `timeout` is `None`, this method will wait indefinitely.
143 pub fn read_event(&self, timeout: Option<Duration>) -> io::Result<Option<Event>> {
144 self.0.read_event(timeout)
145 }
146}
147
148/// # Locking
149///
150/// The following methods internally acquire the write lock.
151///
152/// The lock is released before the method returns.
153///
154/// These methods are also implemented on [`ScreenWriteGuard`],
155/// which holds the `Screen` write lock until the value is dropped.
156///
157/// [`ScreenWriteGuard`]: struct.ScreenWriteGuard.html
158impl Screen {
159 /// Returns the current size of the terminal screen.
160 #[inline]
161 pub fn size(&self) -> Size {
162 self.0.size()
163 }
164
165 /// Returns the current cursor position.
166 #[inline]
167 pub fn cursor(&self) -> Cursor {
168 self.0.cursor()
169 }
170
171 /// Sets the cursor position.
172 #[inline]
173 pub fn set_cursor<C: Into<Cursor>>(&self, pos: C) {
174 self.0.set_cursor(pos.into());
175 }
176
177 /// Moves the cursor to the given column on the next line.
178 #[inline]
179 pub fn next_line(&self, column: usize) {
180 self.0.next_line(column);
181 }
182
183 /// Set the current cursor mode.
184 ///
185 /// This setting is a visible hint to the user.
186 /// It produces no change in behavior.
187 ///
188 /// # Notes
189 ///
190 /// On Unix systems, this setting may have no effect.
191 pub fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()> {
192 self.0.set_cursor_mode(mode)
193 }
194
195 /// Clears the internal screen buffer.
196 pub fn clear_screen(&self) {
197 self.0.clear_screen();
198 }
199
200 /// Adds a set of `Style` flags to the current style setting.
201 #[inline]
202 pub fn add_style(&self, style: Style) {
203 self.0.add_style(style);
204 }
205
206 /// Removes a set of `Style` flags to the current style setting.
207 #[inline]
208 pub fn remove_style(&self, style: Style) {
209 self.0.remove_style(style);
210 }
211
212 /// Sets the current style setting to the given set of flags.
213 #[inline]
214 pub fn set_style<S: Into<Option<Style>>>(&self, style: S) {
215 self.0.set_style(style.into().unwrap_or_default());
216 }
217
218 /// Sets or removes foreground text color.
219 #[inline]
220 pub fn set_fg<C: Into<Option<Color>>>(&self, fg: C) {
221 self.0.set_fg(fg.into());
222 }
223
224 /// Sets or removes background text color.
225 #[inline]
226 pub fn set_bg<C: Into<Option<Color>>>(&self, bg: C) {
227 self.0.set_bg(bg.into());
228 }
229
230 /// Sets all attributes for the screen.
231 #[inline]
232 pub fn set_theme(&self, theme: Theme) {
233 self.0.set_theme(theme)
234 }
235
236 /// Removes color and style attributes.
237 #[inline]
238 pub fn clear_attributes(&self) {
239 self.0.clear_attributes();
240 }
241
242 /// Adds bold to the current style setting.
243 ///
244 /// This is equivalent to `self.add_style(Style::BOLD)`.
245 #[inline]
246 pub fn bold(&self) {
247 self.add_style(Style::BOLD);
248 }
249
250 /// Adds italic to the current style setting.
251 ///
252 /// This is equivalent to `self.add_style(Style::ITALIC)`.
253 #[inline]
254 pub fn italic(&self) {
255 self.add_style(Style::ITALIC);
256 }
257
258 /// Adds underline to the current style setting.
259 ///
260 /// This is equivalent to `self.add_style(Style::UNDERLINE)`.
261 #[inline]
262 pub fn underline(&self) {
263 self.add_style(Style::UNDERLINE);
264 }
265
266 /// Adds reverse to the current style setting.
267 ///
268 /// This is equivalent to `self.add_style(Style::REVERSE)`.
269 #[inline]
270 pub fn reverse(&self) {
271 self.add_style(Style::REVERSE);
272 }
273
274 /// Renders the internal buffer to the terminal screen.
275 pub fn refresh(&self) -> io::Result<()> {
276 self.0.refresh()
277 }
278
279 /// Writes text at the given position within the screen buffer.
280 ///
281 /// Any non-printable characters, such as escape sequences, will be ignored.
282 pub fn write_at<C>(&self, position: C, text: &str)
283 where C: Into<Cursor> {
284 self.0.write_at(position.into(), text);
285 }
286
287 /// Writes text with the given attributes at the current cursor position.
288 ///
289 /// Any non-printable characters, such as escape sequences, will be ignored.
290 pub fn write_styled<F, B, S>(&self, fg: F, bg: B, style: S, text: &str) where
291 F: Into<Option<Color>>,
292 B: Into<Option<Color>>,
293 S: Into<Option<Style>>,
294 {
295 self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), text);
296 }
297
298 /// Writes text with the given attributes at the given position within
299 /// the screen buffer.
300 ///
301 /// Any non-printable characters, such as escape sequences, will be ignored.
302 pub fn write_styled_at<C, F, B, S>(&self, position: C,
303 fg: F, bg: B, style: S, text: &str) where
304 C: Into<Cursor>,
305 F: Into<Option<Color>>,
306 B: Into<Option<Color>>,
307 S: Into<Option<Style>>,
308 {
309 self.0.write_styled_at(position.into(),
310 fg.into(), bg.into(), style.into().unwrap_or_default(), text);
311 }
312
313 /// Writes a single character at the cursor position
314 /// using the current style and color settings.
315 ///
316 /// If the character is a non-printable character, it will be ignored.
317 pub fn write_char(&self, ch: char) {
318 self.0.write_char(ch);
319 }
320
321 /// Writes a string at the cursor position
322 /// using the current style and color settings.
323 ///
324 /// Any non-printable characters, such as escape sequences, will be ignored.
325 pub fn write_str(&self, s: &str) {
326 self.0.write_str(s);
327 }
328
329 /// Writes formatted text at the cursor position
330 /// using the current style and color settings.
331 ///
332 /// This method enables `Screen` to be used as the receiver to
333 /// the [`write!`] and [`writeln!`] macros.
334 ///
335 /// Any non-printable characters, such as escape sequences, will be ignored.
336 ///
337 /// # Examples
338 ///
339 /// ```no_run
340 /// # use std::io;
341 /// # use mortal::Screen;
342 /// # fn example() -> io::Result<()> {
343 /// let screen = Screen::new(Default::default())?;
344 ///
345 /// writeln!(screen, "Hello, world!");
346 /// # Ok(())
347 /// # }
348 /// ```
349 ///
350 /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html
351 /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html
352 pub fn write_fmt(&self, args: fmt::Arguments) {
353 let s = args.to_string();
354 self.write_str(&s)
355 }
356
357 #[doc(hidden)]
358 pub fn borrow_term_write_guard(&self) -> ScreenWriteGuard {
359 self.lock_write().unwrap()
360 }
361}
362
363impl<'a> ScreenReadGuard<'a> {
364 /// Waits for an event from the terminal.
365 ///
366 /// Returns `Ok(false)` if `timeout` elapses without an event occurring.
367 ///
368 /// If `timeout` is `None`, this method will wait indefinitely.
369 ///
370 /// # Notes
371 ///
372 /// Some low-level terminal events may not generate an `Event` value.
373 /// Therefore, this method may return `Ok(true)`, while a follow-up call
374 /// to `read_event` may not immediately return an event.
375 pub fn wait_event(&mut self, timeout: Option<Duration>) -> io::Result<bool> {
376 self.0.wait_event(timeout)
377 }
378
379 /// Reads an event from the terminal.
380 ///
381 /// If `timeout` elapses without an event occurring, this method will return
382 /// `Ok(None)`.
383 ///
384 /// If `timeout` is `None`, this method will wait indefinitely.
385 pub fn read_event(&mut self, timeout: Option<Duration>) -> io::Result<Option<Event>> {
386 self.0.read_event(timeout)
387 }
388}
389
390impl<'a> ScreenWriteGuard<'a> {
391 /// Returns the current size of the terminal screen.
392 #[inline]
393 pub fn size(&self) -> Size {
394 self.0.size()
395 }
396
397 /// Sets the cursor position.
398 #[inline]
399 pub fn cursor(&self) -> Cursor {
400 self.0.cursor()
401 }
402
403 /// Moves the cursor to the given column on the next line.
404 #[inline]
405 pub fn set_cursor<C: Into<Cursor>>(&mut self, pos: C) {
406 self.0.set_cursor(pos.into());
407 }
408
409 /// Set the current cursor mode.
410 #[inline]
411 pub fn next_line(&mut self, column: usize) {
412 self.0.next_line(column);
413 }
414
415 /// Set the current cursor mode.
416 ///
417 /// This setting is a visible hint to the user.
418 /// It produces no change in behavior.
419 ///
420 /// # Notes
421 ///
422 /// On Unix systems, this setting may have no effect.
423 pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> {
424 self.0.set_cursor_mode(mode)
425 }
426
427 /// Adds a set of `Style` flags to the current style setting.
428 pub fn clear_screen(&mut self) {
429 self.0.clear_screen();
430 }
431
432 /// Removes a set of `Style` flags to the current style setting.
433 /// Adds a set of `Style` flags to the current style setting.
434 #[inline]
435 pub fn add_style(&mut self, style: Style) {
436 self.0.add_style(style)
437 }
438
439 /// Sets the current style setting to the given set of flags.
440 #[inline]
441 pub fn remove_style(&mut self, style: Style) {
442 self.0.remove_style(style)
443 }
444
445 /// Sets or removes foreground text color.
446 #[inline]
447 pub fn set_style<S: Into<Option<Style>>>(&mut self, style: S) {
448 self.0.set_style(style.into().unwrap_or_default())
449 }
450
451 /// Sets or removes background text color.
452 #[inline]
453 pub fn set_fg<C: Into<Option<Color>>>(&mut self, fg: C) {
454 self.0.set_fg(fg.into())
455 }
456
457 /// Removes color and style attributes.
458 #[inline]
459 pub fn set_bg<C: Into<Option<Color>>>(&mut self, bg: C) {
460 self.0.set_bg(bg.into())
461 }
462
463 /// Sets all attributes for the screen.
464 #[inline]
465 pub fn set_theme(&mut self, theme: Theme) {
466 self.0.set_theme(theme)
467 }
468
469 /// Adds bold to the current style setting.
470 #[inline]
471 pub fn clear_attributes(&mut self) {
472 self.0.clear_attributes()
473 }
474
475 /// Adds bold to the current style setting.
476 ///
477 /// This is equivalent to `self.add_style(Style::BOLD)`.
478 #[inline]
479 pub fn bold(&mut self) {
480 self.add_style(Style::BOLD)
481 }
482
483 /// Adds italic to the current style setting.
484 ///
485 /// This is equivalent to `self.add_style(Style::ITALIC)`.
486 #[inline]
487 pub fn italic(&mut self) {
488 self.add_style(Style::ITALIC);
489 }
490
491 /// Adds underline to the current style setting.
492 ///
493 /// This is equivalent to `self.add_style(Style::UNDERLINE)`.
494 #[inline]
495 pub fn underline(&mut self) {
496 self.add_style(Style::UNDERLINE)
497 }
498
499 /// Adds reverse to the current style setting.
500 ///
501 /// This is equivalent to `self.add_style(Style::REVERSE)`.
502 #[inline]
503 pub fn reverse(&mut self) {
504 self.add_style(Style::REVERSE)
505 }
506
507 /// Renders the internal buffer to the terminal screen.
508 ///
509 /// This is called automatically when the `ScreenWriteGuard` is dropped.
510 pub fn refresh(&mut self) -> io::Result<()> {
511 self.0.refresh()
512 }
513
514 /// Writes text at the given position within the screen buffer.
515 ///
516 /// Any non-printable characters, such as escape sequences, will be ignored.
517 pub fn write_at<C>(&mut self, position: C, text: &str)
518 where C: Into<Cursor> {
519 self.0.write_at(position.into(), text)
520 }
521
522 /// Writes text with the given attributes at the current cursor position.
523 ///
524 /// Any non-printable characters, such as escape sequences, will be ignored.
525 pub fn write_styled<F, B, S>(&mut self, fg: F, bg: B, style: S, text: &str) where
526 F: Into<Option<Color>>,
527 B: Into<Option<Color>>,
528 S: Into<Option<Style>>,
529 {
530 self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), text)
531 }
532
533 /// Writes text with the given attributes at the given position within
534 /// the screen buffer.
535 ///
536 /// Any non-printable characters, such as escape sequences, will be ignored.
537 pub fn write_styled_at<C, F, B, S>(&mut self, position: C,
538 fg: F, bg: B, style: S, text: &str) where
539 C: Into<Cursor>,
540 F: Into<Option<Color>>,
541 B: Into<Option<Color>>,
542 S: Into<Option<Style>>,
543 {
544 self.0.write_styled_at(position.into(),
545 fg.into(), bg.into(), style.into().unwrap_or_default(), text)
546 }
547
548 /// Writes a single character at the cursor position
549 /// using the current style and color settings.
550 ///
551 /// If the character is a non-printable character, it will be ignored.
552 pub fn write_char(&mut self, ch: char) {
553 self.0.write_char(ch)
554 }
555
556 /// Writes a string at the cursor position
557 /// using the current style and color settings.
558 ///
559 /// Any non-printable characters, such as escape sequences, will be ignored.
560 pub fn write_str(&mut self, s: &str) {
561 self.0.write_str(s)
562 }
563
564 /// Writes formatted text at the cursor position
565 /// using the current style and color settings.
566 ///
567 /// This method enables `ScreenWriteGuard` to be used as the receiver to
568 /// the [`write!`] and [`writeln!`] macros.
569 ///
570 /// Any non-printable characters, such as escape sequences, will be ignored.
571 ///
572 /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html
573 /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html
574 pub fn write_fmt(&mut self, args: fmt::Arguments) {
575 let s = args.to_string();
576 self.write_str(&s)
577 }
578
579 #[doc(hidden)]
580 pub fn borrow_term_write_guard(&mut self) -> &mut Self {
581 self
582 }
583}
584
585#[cfg(unix)]
586impl crate::unix::TerminalExt for Screen {
587 fn read_raw(&mut self, buf: &mut [u8], timeout: Option<Duration>) -> io::Result<Option<Event>> {
588 self.0.read_raw(buf, timeout)
589 }
590}
591
592#[cfg(unix)]
593impl<'a> crate::unix::TerminalExt for ScreenReadGuard<'a> {
594 fn read_raw(&mut self, buf: &mut [u8], timeout: Option<Duration>) -> io::Result<Option<Event>> {
595 self.0.read_raw(buf, timeout)
596 }
597}
598
599#[cfg(windows)]
600impl crate::windows::TerminalExt for Screen {
601 fn read_raw(&mut self, buf: &mut [u16], timeout: Option<Duration>) -> io::Result<Option<Event>> {
602 self.0.read_raw(buf, timeout)
603 }
604
605 fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD],
606 timeout: Option<Duration>) -> io::Result<Option<Event>> {
607 self.0.read_raw_event(events, timeout)
608 }
609}
610
611#[cfg(windows)]
612impl<'a> crate::windows::TerminalExt for ScreenReadGuard<'a> {
613 fn read_raw(&mut self, buf: &mut [u16], timeout: Option<Duration>) -> io::Result<Option<Event>> {
614 self.0.read_raw(buf, timeout)
615 }
616
617 fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD],
618 timeout: Option<Duration>) -> io::Result<Option<Event>> {
619 self.0.read_raw_event(events, timeout)
620 }
621}