easycurses/
lib.rs

1#![warn(missing_docs)]
2#![deny(missing_debug_implementations)]
3#![deny(unsafe_code)]
4
5//! This is a crate that allows one to easily use a basic form of curses. It is
6//! based upon [pancurses](https://docs.rs/crate/pancurses) and so it's cross
7//! platform between windows and unix. It exposes a simplified view of curses
8//! functionality where there's just one Window and all of your actions are
9//! called upon a single struct type, `EasyCurses`. This ensures that curses
10//! functions are only called while curses is initialized, and also that curses
11//! is always cleaned up at the end (via `Drop`).
12//!
13//! The library can only perform proper automatic cleanup if Rust is allowed to
14//! run the `Drop` implementation. This happens during normal usage, and during
15//! an unwinding panic, but if you ever abort the program (either because you
16//! compiled with `panic=abort` or because you panic during an unwind) you lose
17//! the cleanup safety. That is why this library specifies `panic="unwind"` for
18//! all build modes, and you should too.
19
20extern crate pancurses;
21
22pub mod constants;
23
24pub use pancurses::Input;
25
26use std::iter::Iterator;
27use std::panic::*;
28use std::sync::atomic::{AtomicBool, Ordering};
29
30use pancurses::ToChtype;
31
32/// A handy macro to make describing color pairs read more like normal english.
33///
34/// ```rust
35/// #[macro_use]
36/// extern crate easycurses;
37/// use easycurses::{Color, ColorPair};
38/// use easycurses::Color::*;
39///
40/// fn main() {
41///     for fg in Color::color_iterator() {
42///         for bg in Color::color_iterator() {
43///             assert_eq!(colorpair!(fg on bg), ColorPair::new(fg,bg));
44///         }
45///     }
46/// }
47/// ```
48#[macro_export]
49macro_rules! colorpair {
50  ($fg: ident on $bg: ident) => {
51    ColorPair::new($fg, $bg)
52  };
53}
54
55#[allow(non_upper_case_globals)]
56static curses_is_on: AtomicBool = AtomicBool::new(false);
57
58/// The three options you can pass to [`EasyCurses::set_cursor_visibility`].
59///
60/// Note that not all terminals support all visibility modes.
61///
62/// [`EasyCurses::set_cursor_visibility`]: struct.EasyCurses.html#method.set_cursor_visibility
63#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
64pub enum CursorVisibility {
65  /// Makes the cursor invisible. Supported on most terminals.
66  Invisible,
67  /// Makes the cursor visible in the normal way. The Default.
68  Visible,
69  /// Makes the cursor "highly" visible in some way. Not supported on all terminals.
70  HighlyVisible,
71}
72
73impl Default for CursorVisibility {
74  /// The default `CursorVisibility` is `Visible`.
75  ///
76  /// ```
77  /// use easycurses::CursorVisibility;
78  /// assert_eq!(CursorVisibility::default(), CursorVisibility::Visible);
79  /// ```
80  fn default() -> Self {
81    CursorVisibility::Visible
82  }
83}
84
85/// The curses color constants.
86///
87/// Curses supports eight different colors. Each character cell has one "color
88/// pair" set which is a foreground and background pairing. Note that a cell can
89/// also be "bold", which might display as different colors on some terminals.
90#[allow(missing_docs)]
91#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
92pub enum Color {
93  Black,
94  Red,
95  Green,
96  Yellow,
97  Blue,
98  Magenta,
99  Cyan,
100  White,
101}
102
103type ColorIter = std::iter::Cloned<std::slice::Iter<'static, Color>>;
104
105impl Color {
106  /// Provides a handy Iterator over all of the Color values.
107  pub fn color_iterator() -> ColorIter {
108    use Color::*;
109    #[allow(non_upper_case_globals)]
110    static colors: &[Color] = &[Black, Red, Green, Yellow, Blue, Magenta, Cyan, White];
111    colors.iter().cloned()
112  }
113}
114
115/// Converts a `Color` to the `i16` associated with it.
116fn color_to_i16(color: Color) -> i16 {
117  use Color::*;
118  match color {
119    Black => 0,
120    Red => 1,
121    Green => 2,
122    Yellow => 3,
123    Blue => 4,
124    Magenta => 5,
125    Cyan => 6,
126    White => 7,
127  }
128}
129
130/// Converts an `i16` to the `Color` associated with it. Fails if the input is
131/// outside the range 0 to 7 (inclusive).
132#[cfg(test)]
133fn i16_to_color(val: i16) -> Option<Color> {
134  use Color::*;
135  match val {
136    0 => Some(Black),
137    1 => Some(Red),
138    2 => Some(Green),
139    3 => Some(Yellow),
140    4 => Some(Blue),
141    5 => Some(Magenta),
142    6 => Some(Cyan),
143    7 => Some(White),
144    _ => None,
145  }
146}
147
148#[cfg(test)]
149mod color_tests {
150  use super::*;
151
152  #[test]
153  fn test_color_i32_conversion_identity() {
154    use Color::*;
155    let colors = [Black, Red, Green, Yellow, Blue, Magenta, Cyan, White];
156    for &color in colors.iter() {
157      if i16_to_color(color_to_i16(color)).unwrap() != color {
158        panic!("{:?}", color);
159      }
160    }
161  }
162
163  #[test]
164  fn test_color_i32_matches_color_constants() {
165    use Color::*;
166    assert!(color_to_i16(Black) == pancurses::COLOR_BLACK);
167    assert!(color_to_i16(Red) == pancurses::COLOR_RED);
168    assert!(color_to_i16(Green) == pancurses::COLOR_GREEN);
169    assert!(color_to_i16(Yellow) == pancurses::COLOR_YELLOW);
170    assert!(color_to_i16(Blue) == pancurses::COLOR_BLUE);
171    assert!(color_to_i16(Magenta) == pancurses::COLOR_MAGENTA);
172    assert!(color_to_i16(Cyan) == pancurses::COLOR_CYAN);
173    assert!(color_to_i16(White) == pancurses::COLOR_WHITE);
174  }
175}
176
177/// A color pair for a character cell on the screen.
178///
179/// Use them with [`EasyCurses::set_color_pair`].
180///
181/// [`EasyCurses::set_color_pair`]: struct.EasyCurses.html#method.set_color_pair
182#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
183pub struct ColorPair(i16);
184
185impl ColorPair {
186  /// Creates a new `ColorPair` given a foreground and background.
187  pub fn new(fg: Color, bg: Color) -> Self {
188    let fgi = color_to_i16(fg);
189    let bgi = color_to_i16(bg);
190    ColorPair(ColorPair::fgbg_pairid(fgi, bgi))
191  }
192
193  /// The "low level" conversion using i16 values. Color pair 0 is white on black
194  /// but we can't assign to it. Technically we're only assured to have color
195  /// pairs 0 through 63 available, but you _usually_ get more so we're taking a
196  /// gamble that there's at least one additional bit available. The alternative
197  /// is a somewhat complicated conversion scheme where we special case
198  /// White/Black to be 0, then other things start ascending above that, until we
199  /// hit where White/Black should be and start subtracting one from everything to
200  /// keep it within spec. I don't wanna do that if I don't really have to.
201  fn fgbg_pairid(fg: i16, bg: i16) -> i16 {
202    1 + (8 * fg + bg)
203  }
204}
205
206impl Default for ColorPair {
207  /// The "default" color pair is White text on a Black background.
208  ///
209  /// ```
210  /// use easycurses::{Color,ColorPair};
211  /// assert_eq!(ColorPair::default(), ColorPair::new(Color::White,Color::Black));
212  /// ```
213  fn default() -> Self {
214    Self::new(Color::White, Color::Black)
215  }
216}
217
218/// The various input modes that you can set for the terminal.
219///
220/// Use this with `set_input_mode`.
221#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
222pub enum InputMode {
223  /// Line buffering (special character processing)
224  Cooked,
225  /// Characters visible immediately (special character processing)
226  Character,
227  /// Line buffering (no special processing)
228  RawCooked,
229  /// Characters visible immediately (no special processing)
230  RawCharacter,
231}
232
233/// The various timeouts that you can set for `get_input` to operate with.
234///
235/// Use this with the `set_input_timeout` method.
236#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
237pub enum TimeoutMode {
238  /// If no input is available, return `None`.
239  Immediate,
240  /// Wait up to this many milliseconds before returning `None`.
241  WaitUpTo(i32),
242  /// Block until input is given.
243  Never,
244}
245
246impl Default for TimeoutMode {
247  /// ```rust
248  /// use easycurses::TimeoutMode;
249  /// assert_eq!(TimeoutMode::default(), TimeoutMode::Never);
250  /// ```
251  fn default() -> Self {
252    TimeoutMode::Never
253  }
254}
255
256/// Converts a `pancurses::OK` value into `true`, and all other values into
257/// `false`.
258fn to_bool(curses_bool: i32) -> bool {
259  curses_bool == pancurses::OK
260}
261
262/// This is a handle to all your fun curses functionality.
263///
264/// `EasyCurses` will automatically restore the terminal when you drop it, so
265/// you don't need to worry about any manual cleanup. Automatic cleanup will
266/// happen even if your program panics and unwinds, but it **will not** happen
267/// if your program panics and aborts (obviously). So, don't abort the program
268/// while curses is active, or your terminal session will just be ruined.
269///
270/// Except in the case of [`is_color_terminal`], all `EasyCurses` methods that
271/// return a `bool` use it to indicate if the requested operation was successful
272/// or not. Unfortunately, the curses library doesn't provide any more info than
273/// that, so a `bool` is all you get.
274///
275/// [`is_color_terminal`]: #method.is_color_terminal
276#[derive(Debug)]
277pub struct EasyCurses {
278  /// This is the inner pancurses `Window` that the `EasyCurses` type wraps
279  /// over.
280  ///
281  /// This is only intended to be used as a last resort, if you really want to
282  /// call something that's not here. Under normal circumstances you shouldn't
283  /// need to touch this field at all. It's not "unsafe" to use in the
284  /// rust/memory sense, but if you access this field and then cause a bug in
285  /// `EasyCurses`, well that's your own fault.
286  pub win: pancurses::Window,
287  color_support: bool,
288  /// Determines if the window will automatically resize itself when
289  /// `KeyResize` comes in through the input channel. Defaults to true. If you
290  /// disable this and then don't call resize yourself then `KeyResize` comes
291  /// in you'll have a bad time.
292  pub auto_resize: bool,
293}
294
295impl Drop for EasyCurses {
296  /// Dropping EasyCurses causes the
297  /// [endwin](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/endwin.html)
298  /// curses function to be called.
299  fn drop(&mut self) {
300    // We will assume that the initialization code is correctly never
301    // initializing curses twice, and thus we will assume that it's safe to
302    // call endwin and then store that curses is off once that's done. If we
303    // were paranoid we'd do another compare_and_swap, but that's slower for
304    // no reason (again, assuming that the initialization code is correct).
305    pancurses::endwin();
306    curses_is_on.store(false, Ordering::SeqCst);
307  }
308}
309
310impl EasyCurses {
311  /// Initializes the curses system so that you can begin using curses.
312  ///
313  /// The name is long to remind you of the seriousness of attempting to turn
314  /// on curses: If the C layer encounters an error while trying to initialize
315  /// the user's terminal into curses mode it will "helpfully" print an error
316  /// message and exit the process on its own. There's no way to prevent this
317  /// from happening at the Rust level.
318  ///
319  /// If the terminal supports colors, they are automatically activated and
320  /// `ColorPair` values are initialized for all color foreground and
321  /// background combinations.
322  ///
323  /// # Errors
324  ///
325  /// Curses must not be double-initialized. This is tracked by easycurses
326  /// with an `AtomicBool` being flipped on and off. If it is on when you call
327  /// this method you get `None` back instead.
328  pub fn initialize_system() -> Option<Self> {
329    // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.compare_and_swap
330    // This method call is goofy as hell but basically we try to turn
331    // `curses_is_on` to true and then we're told if we actually changed it
332    // or not. If we did that means it was off and it's safe to turn it on.
333    // If we didn't change it that means it was already on and we should
334    // back out.
335    if !curses_is_on.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).unwrap_or_else(|x| x) {
336      let w = pancurses::initscr();
337      let color_support = if pancurses::has_colors() {
338        to_bool(pancurses::start_color())
339      } else {
340        false
341      };
342      if color_support {
343        let color_count = pancurses::COLORS();
344        let pair_count = pancurses::COLOR_PAIRS();
345        if color_count >= 8 && pair_count >= 8 * 8 {
346          for fg in Color::color_iterator() {
347            for bg in Color::color_iterator() {
348              let fgi: i16 = color_to_i16(fg);
349              let bgi: i16 = color_to_i16(bg);
350              let pair_id: i16 = ColorPair::fgbg_pairid(fgi, bgi);
351              debug_assert!(
352                fgi as i32 <= color_count,
353                "Curses reported {} color ids available, but {:?} has id {}",
354                color_count,
355                fg,
356                fgi
357              );
358              debug_assert!(
359                bgi as i32 <= color_count,
360                "Curses reported {} color ids available, but {:?} has id {}",
361                color_count,
362                bg,
363                bgi
364              );
365              debug_assert!(
366                pair_id as i32 <= pair_count,
367                "Curses reported {} colorpair ids available, but {:?} on {:?} would be id {}",
368                pair_count,
369                fg,
370                bg,
371                pair_id
372              );
373              pancurses::init_pair(pair_id, fgi, bgi);
374            }
375          }
376        }
377      }
378      Some(EasyCurses {
379        win: w,
380        color_support: color_support,
381        auto_resize: true,
382      })
383    } else {
384      None
385    }
386  }
387
388  /// On Win32 systems this allows you to set the title of the PDcurses
389  /// window. On other systems this does nothing at all.
390  pub fn set_title_win32(&mut self, title: &str) {
391    pancurses::set_title(title);
392  }
393
394  /// Attempts to assign a new cursor visibility. If this is successful you
395  /// get a `Some` back with the old setting inside. If this fails you get a
396  /// `None` back. For more info see
397  /// [curs_set](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/curs_set.html)
398  pub fn set_cursor_visibility(&mut self, vis: CursorVisibility) -> Option<CursorVisibility> {
399    use CursorVisibility::*;
400    let result = pancurses::curs_set(match vis {
401      Invisible => 0,
402      Visible => 1,
403      HighlyVisible => 2,
404    });
405    match result {
406      0 => Some(Invisible),
407      1 => Some(Visible),
408      2 => Some(HighlyVisible),
409      _ => None,
410    }
411  }
412
413  /// The terminal gets input from the user. Then it's sometimes buffered up. At
414  /// some point it's passed into the program's input buffer, and then
415  /// `get_input` gets things out of that buffer.
416  ///
417  /// * Character: Input is passed in 1 character at a time, but special
418  ///   characters (such as Ctrl+C and Ctrl+S) are automatically processed for
419  ///   you by the terminal.
420  /// * Cooked: Input is passed in 1 line at a time, with the special character
421  ///   processing mentioned above enabled.
422  /// * RawCharacter: Input is passed in 1 character at a time, and special
423  ///   character sequences are not processed automatically.
424  /// * RawCooked: Input is passed in 1 line at a time, and special
425  ///   character sequences are not processed automatically.
426  ///
427  /// The default mode is inherited from the terminal that started the program
428  /// (usually Cooked), so you should _always_ set the desired mode explicitly
429  /// at the start of your program.
430  ///
431  /// See also the [Input
432  /// Mode](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/intov.html#tag_001_005_002)
433  /// section of the curses documentation.
434  pub fn set_input_mode(&mut self, mode: InputMode) -> bool {
435    to_bool(match mode {
436      InputMode::Character => pancurses::cbreak(),
437      InputMode::Cooked => pancurses::nocbreak(),
438      InputMode::RawCharacter => pancurses::raw(),
439      InputMode::RawCooked => pancurses::noraw(),
440    })
441  }
442
443  /// This controls how long `get_input` will wait before returning a `None`
444  /// value.
445  ///
446  /// The default mode is an unlimited wait.
447  ///
448  /// The `WaitUpTo` value is measured in milliseconds, and any negative value
449  /// is treated as 0 (the same as an immediate timeout).
450  ///
451  /// See also: The
452  /// [notimeout](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/notimeout.html)
453  /// curses function.
454  pub fn set_input_timeout(&mut self, mode: TimeoutMode) {
455    match mode {
456      TimeoutMode::Immediate => self.win.timeout(0),
457      TimeoutMode::WaitUpTo(n) => self.win.timeout(n.max(0)),
458      TimeoutMode::Never => self.win.timeout(-1),
459    };
460  }
461
462  /// Enables special key processing from buttons such as the keypad and arrow
463  /// keys. This defaults to `false`. You probably want to set it to `true`.
464  /// If it's not on and the user presses a special key then get_key will
465  /// return will do nothing or give `ERR`.
466  pub fn set_keypad_enabled(&mut self, use_keypad: bool) -> bool {
467    to_bool(self.win.keypad(use_keypad))
468  }
469
470  /// Enables or disables the automatic echoing of input into the window as
471  /// the user types. Default to on, but you probably want it to be off most
472  /// of the time.
473  pub fn set_echo(&mut self, echoing: bool) -> bool {
474    to_bool(if echoing { pancurses::echo() } else { pancurses::noecho() })
475  }
476
477  // TODO: pancurses::resize_term?
478
479  /// Checks if the current terminal supports the use of colors.
480  pub fn is_color_terminal(&self) -> bool {
481    self.color_support
482  }
483
484  /// Sets the current color pair of the window. Output at any location will
485  /// use this pair until a new pair is set. Does nothing if the terminal does
486  /// not support colors in the first place.
487  pub fn set_color_pair(&mut self, pair: ColorPair) {
488    if self.color_support {
489      self.win.color_set(pair.0);
490    }
491  }
492
493  /// Enables or disables bold text for all future input.
494  pub fn set_bold(&mut self, bold_on: bool) -> bool {
495    to_bool(if bold_on {
496      self.win.attron(pancurses::Attribute::Bold)
497    } else {
498      self.win.attroff(pancurses::Attribute::Bold)
499    })
500  }
501
502  /// Enables or disables underlined text for all future input.
503  pub fn set_underline(&mut self, underline_on: bool) -> bool {
504    to_bool(if underline_on {
505      self.win.attron(pancurses::Attribute::Underline)
506    } else {
507      self.win.attroff(pancurses::Attribute::Underline)
508    })
509  }
510
511  /// Returns the number of rows and columns available in the window. Each of
512  /// these are the number of locations in that dimension, but the rows and
513  /// cols (as well as the Xs and Ys if you care to use that coordinate space)
514  /// use 0-based indexing, so the actual addressable locations are numbered 0
515  /// to N-1, similar to with slices, `.len()`, and indexing. Fortunately, the
516  /// normal rust Range type already handles this for us. If you wanted to
517  /// iterate every cell of the window you'd probably use a loop like this:
518  ///
519  /// ```rust
520  /// let mut easy = easycurses::EasyCurses::initialize_system().unwrap();
521  /// let (row_count,col_count) = easy.get_row_col_count();
522  /// // using RC coordinates.
523  /// for row in 0..row_count {
524  ///     for col in 0..col_count {
525  ///         easy.move_rc(row,col);
526  ///         let (actual_row,actual_col) = easy.get_cursor_rc();
527  ///         assert!(actual_row == row && actual_col == col);
528  ///     }
529  /// }
530  /// // using XY coordinates.
531  /// for y in 0..row_count {
532  ///     for x in 0..col_count {
533  ///         easy.move_xy(x,y);
534  ///         let (actual_x,actual_y) = easy.get_cursor_xy();
535  ///         assert!(actual_x == x && actual_y == y);
536  ///     }
537  /// }
538  /// ```
539  pub fn get_row_col_count(&self) -> (i32, i32) {
540    self.win.get_max_yx()
541  }
542
543  /// Moves the virtual cursor to the row and column specified, relative to
544  /// the top left ("notepad" space). Does not move the terminal's displayed
545  /// cursor (if any) until `refresh` is also called. Out of bounds locations
546  /// cause this command to be ignored.
547  pub fn move_rc(&mut self, row: i32, col: i32) -> bool {
548    to_bool(self.win.mv(row, col))
549  }
550
551  /// Obtains the cursor's current position using `(R,C)` coordinates
552  /// relative to the top left corner.
553  pub fn get_cursor_rc(&self) -> (i32, i32) {
554    self.win.get_cur_yx()
555  }
556
557  /// Moves the virtual cursor to the x and y specified, relative to the
558  /// bottom left ("cartesian" space). Does not move the terminal's displayed
559  /// cursor (if any) until `refresh` is also called. Out of bounds locations
560  /// cause this command to be ignored.
561  pub fn move_xy(&mut self, x: i32, y: i32) -> bool {
562    let row_count = self.win.get_max_y();
563    to_bool(self.win.mv(row_count - (y + 1), x))
564  }
565
566  /// Obtains the cursor's current position using `(X,Y)` coordinates relative
567  /// to the bottom left corner.
568  pub fn get_cursor_xy(&self) -> (i32, i32) {
569    let row_count = self.win.get_max_y();
570    let (row, col) = self.win.get_cur_yx();
571    (col, row_count - (row + 1))
572  }
573
574  /// When scrolling is enabled, any attempt to move off the bottom margin
575  /// will cause lines within the scrolling region to scroll up one line. If a
576  /// scrolling region is set but scrolling is not enabled then attempts to go
577  /// off the bottom will just print nothing instead. Use `set_scroll_region`
578  /// to control the size of the scrolling region.
579  pub fn set_scrolling(&mut self, scrolling: bool) -> bool {
580    to_bool(self.win.scrollok(scrolling))
581  }
582
583  /// Sets the top and bottom margins of the scrolling region. The inputs
584  /// should be the line numbers (relative to the top of the screen) for the
585  /// borders. Either border can be 0.
586  ///
587  /// See also:
588  /// [setscrreg](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/setscrreg.html)
589  pub fn set_scroll_region(&mut self, top: i32, bottom: i32) -> bool {
590    to_bool(self.win.setscrreg(top, bottom))
591  }
592
593  /// Prints the given string-like value into the window by printing each
594  /// individual character into the window. If there is any error encountered
595  /// upon printing a character, that cancels the printing of the rest of the
596  /// characters.
597  pub fn print<S: AsRef<str>>(&mut self, asref: S) -> bool {
598    // Here we want to
599    if cfg!(windows) {
600      // PDCurses does an extra intermediate CString allocation, so we just
601      // print out each character one at a time to avoid that.
602      asref.as_ref().chars().all(|c| self.print_char(c))
603    } else {
604      // NCurses, it seems, doesn't do the intermediate allocation and also uses
605      // a faster routine for printing a whole string at once.
606      to_bool(self.win.printw(asref.as_ref()))
607    }
608  }
609
610  /// Prints the given character into the window.
611  pub fn print_char<T: ToChtype>(&mut self, character: T) -> bool {
612    to_bool(self.win.addch(character))
613  }
614
615  /// Inserts the character desired at the current location, pushing the
616  /// current character at that location (and all after it on the same line)
617  /// one cell to the right.
618  pub fn insert_char<T: ToChtype>(&mut self, character: T) -> bool {
619    to_bool(self.win.insch(character))
620  }
621
622  /// Deletes the character under the cursor. Characters after it on same the
623  /// line are pulled left one position and the final character cell is left
624  /// blank. The cursor position does not move.
625  pub fn delete_char(&mut self) -> bool {
626    to_bool(self.win.delch())
627  }
628
629  /// Inserts a line above the current line. The bottom line is lost.
630  pub fn insert_line(&mut self) -> bool {
631    to_bool(self.win.insertln())
632  }
633
634  /// Deletes the line under the cursor. Lines below are moved up one line and
635  /// the final line is left blank. The cursor position does not move.
636  pub fn delete_line(&mut self) -> bool {
637    to_bool(self.win.deleteln())
638  }
639
640  /// For positive n, insert n lines into the specified window above the current
641  /// line. The n bottom lines are lost. For negative n, delete n lines
642  /// (starting with the one under the cursor), and move the remaining lines up.
643  /// The bottom n lines are cleared. The current cursor position remains the
644  /// same.
645  pub fn bulk_insert_delete_line(&mut self, n: i32) -> bool {
646    to_bool(self.win.insdelln(n))
647  }
648
649  /// Clears the entire screen.
650  ///
651  /// **Note:** This function can cause flickering of the output with PDCurses
652  /// if you're clearing the screen and then immediately writing to the whole
653  /// screen before you end up calling `refresh`. The exact location of the
654  /// flickering effect seems to vary from machine to machine. If you intend
655  /// to simply replace the whole window with new content, just overwrite the
656  /// previous values without calling `clear` and things will be fine.
657  pub fn clear(&mut self) -> bool {
658    to_bool(self.win.clear())
659  }
660
661  /// Refreshes the window's appearance on the screen. With some
662  /// implementations you don't need to call this, the screen will refresh
663  /// itself on its own. However, for portability, you should call this at the
664  /// end of each draw cycle.
665  pub fn refresh(&mut self) -> bool {
666    to_bool(self.win.refresh())
667  }
668
669  /// Plays an audible beep if possible, if not the screen is flashed. If
670  /// neither is available then nothing happens.
671  pub fn beep(&mut self) {
672    pancurses::beep();
673  }
674
675  /// Flashes the screen if possible, if not an audible beep is played. If
676  /// neither is available then nothing happens.
677  pub fn flash(&mut self) {
678    pancurses::flash();
679  }
680
681  /// Gets an `Input` from the curses input buffer. This will block or not
682  /// according to the input mode, see `set_input_mode`. If `KeyResize` is
683  /// seen and `auto_resize` is enabled then the window will automatically
684  /// update its size for you. In that case, `KeyResize` is still passed to
685  /// you so that you can change anything else that might need to be updated.
686  pub fn get_input(&mut self) -> Option<pancurses::Input> {
687    let ret = self.win.getch();
688    if self.auto_resize {
689      match ret {
690        Some(Input::KeyResize) => {
691          self.resize(0, 0);
692        }
693        _ => (),
694      };
695    }
696    ret
697  }
698
699  /// Discards all type-ahead that has been input by the user but not yet read
700  /// by the program.
701  pub fn flush_input(&mut self) {
702    pancurses::flushinp();
703  }
704
705  /// Pushes an `Input` value into the input stack so that it will be returned
706  /// by the next call to `get_input`. The return value is if the operation
707  /// was successful.
708  pub fn un_get_input(&mut self, input: pancurses::Input) -> bool {
709    to_bool(self.win.ungetch(&input))
710  }
711
712  /// Sets the window to use the number of lines and columns specified. If you
713  /// pass zero for both then this will make the window's data structures
714  /// attempt to match the current size of the window. This is done
715  /// automatically for you when `KeyResize` comes in through the input
716  /// buffer.
717  pub fn resize(&mut self, new_lines: i32, new_cols: i32) -> bool {
718    to_bool(pancurses::resize_term(new_lines, new_cols))
719  }
720}
721
722/// Wraps the use of curses with `catch_unwind` to preserve panic info.
723///
724/// Normally, if your program panics while in curses mode the panic message
725/// prints immediately and then is destroyed before you can see it by the
726/// automatic cleanup of curses mode. Instead, this runs the function you pass
727/// it within `catch_unwind` and when there's a panic it catches the panic value
728/// and attempts to downcast it into a `String` you can print out or log or
729/// whatever you like. Since a panic value can be anything at all this won't
730/// always succeed, thus the `Option` wrapper on the `Err` case. Regardless of
731/// what of `Result` you get back, curses mode will be fully cleaned up and shut
732/// down by the time this function returns.
733///
734/// Note that you *don't* have to use this if you just want your terminal
735/// restored to normal when your program panics while in curses mode. That is
736/// handled automatically by the `Drop` implementation of `EasyCurses`. You only
737/// need to use this if you care about the panic message itself.
738pub fn preserve_panic_message<F: FnOnce(&mut EasyCurses) -> R + UnwindSafe, R>(user_function: F) -> Result<R, Option<String>> {
739  let result = catch_unwind(|| {
740    // Normally calling `expect` is asking for eventual trouble to bite us,
741    // but we're specifically inside a `catch_unwind` block so it's fine.
742    let mut easy = EasyCurses::initialize_system().expect("Curses double-initialization.");
743    user_function(&mut easy)
744  });
745  result.map_err(|e| match e.downcast_ref::<&str>() {
746    Some(andstr) => Some(andstr.to_string()),
747    None => match e.downcast_ref::<String>() {
748      Some(string) => Some(string.to_string()),
749      None => None,
750    },
751  })
752}