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}