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, ATOMIC_BOOL_INIT};
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 = ATOMIC_BOOL_INIT;
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
262fn to_option(curses_bool: i32) -> Option<()> {
263  if to_bool(curses_bool) {
264    Some(())
265  } else {
266    None
267  }
268}
269
270/// This is a handle to all your fun curses functionality.
271///
272/// `EasyCurses` will automatically restore the terminal when you drop it, so
273/// you don't need to worry about any manual cleanup. Automatic cleanup will
274/// happen even if your program panics and unwinds, but it **will not** happen
275/// if your program panics and aborts (obviously). So, don't abort the program
276/// while curses is active, or your terminal session will just be ruined.
277///
278/// Except in the case of [`is_color_terminal`], all `EasyCurses` methods that
279/// return a `Option<()>` use it to indicate if the requested operation was successful
280/// or not. Unfortunately, the curses library doesn't provide any more info than
281/// that, so a `Option<()>` is all you get.
282///
283/// [`is_color_terminal`]: #method.is_color_terminal
284#[derive(Debug)]
285pub struct EasyCurses {
286  /// This is the inner pancurses `Window` that the `EasyCurses` type wraps
287  /// over.
288  ///
289  /// This is only intended to be used as a last resort, if you really want to
290  /// call something that's not here. Under normal circumstances you shouldn't
291  /// need to touch this field at all. It's not "unsafe" to use in the
292  /// rust/memory sense, but if you access this field and then cause a bug in
293  /// `EasyCurses`, well that's your own fault.
294  pub win: pancurses::Window,
295  color_support: bool,
296  /// Determines if the window will automatically resize itself when
297  /// `KeyResize` comes in through the input channel. Defaults to true. If you
298  /// disable this and then don't call resize yourself then `KeyResize` comes
299  /// in you'll have a bad time.
300  pub auto_resize: bool,
301}
302
303impl Drop for EasyCurses {
304  /// Dropping EasyCurses causes the
305  /// [endwin](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/endwin.html)
306  /// curses function to be called.
307  fn drop(&mut self) {
308    // We will assume that the initialization code is correctly never
309    // initializing curses twice, and thus we will assume that it's safe to
310    // call endwin and then store that curses is off once that's done. If we
311    // were paranoid we'd do another compare_and_swap, but that's slower for
312    // no reason (again, assuming that the initialization code is correct).
313    pancurses::endwin();
314    curses_is_on.store(false, Ordering::SeqCst);
315  }
316}
317
318impl EasyCurses {
319  /// Initializes the curses system so that you can begin using curses.
320  ///
321  /// The name is long to remind you of the seriousness of attempting to turn
322  /// on curses: If the C layer encounters an error while trying to initialize
323  /// the user's terminal into curses mode it will "helpfully" print an error
324  /// message and exit the process on its own. There's no way to prevent this
325  /// from happening at the Rust level.
326  ///
327  /// If the terminal supports colors, they are automatically activated and
328  /// `ColorPair` values are initialized for all color foreground and
329  /// background combinations.
330  ///
331  /// # Errors
332  ///
333  /// Curses must not be double-initialized. This is tracked by easycurses
334  /// with an `AtomicBool` being flipped on and off. If it is on when you call
335  /// this method you get `None` back instead.
336  pub fn initialize_system() -> Option<Self> {
337    // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicBool.html#method.compare_and_swap
338    // This method call is goofy as hell but basically we try to turn
339    // `curses_is_on` to true and then we're told if we actually changed it
340    // or not. If we did that means it was off and it's safe to turn it on.
341    // If we didn't change it that means it was already on and we should
342    // back out.
343    if !curses_is_on.compare_and_swap(false, true, Ordering::SeqCst) {
344      let w = pancurses::initscr();
345      let color_support = if pancurses::has_colors() {
346        to_bool(pancurses::start_color())
347      } else {
348        false
349      };
350      if color_support {
351        let color_count = pancurses::COLORS();
352        let pair_count = pancurses::COLOR_PAIRS();
353        if color_count >= 8 && pair_count >= 8 * 8 {
354          for fg in Color::color_iterator() {
355            for bg in Color::color_iterator() {
356              let fgi: i16 = color_to_i16(fg);
357              let bgi: i16 = color_to_i16(bg);
358              let pair_id: i16 = ColorPair::fgbg_pairid(fgi, bgi);
359              debug_assert!(
360                fgi as i32 <= color_count,
361                "Curses reported {} color ids available, but {:?} has id {}",
362                color_count,
363                fg,
364                fgi
365              );
366              debug_assert!(
367                bgi as i32 <= color_count,
368                "Curses reported {} color ids available, but {:?} has id {}",
369                color_count,
370                bg,
371                bgi
372              );
373              debug_assert!(
374                pair_id as i32 <= pair_count,
375                "Curses reported {} colorpair ids available, but {:?} on {:?} would be id {}",
376                pair_count,
377                fg,
378                bg,
379                pair_id
380              );
381              pancurses::init_pair(pair_id, fgi, bgi);
382            }
383          }
384        }
385      }
386      Some(EasyCurses {
387        win: w,
388        color_support: color_support,
389        auto_resize: true,
390      })
391    } else {
392      None
393    }
394  }
395
396  /// On Win32 systems this allows you to set the title of the PDcurses
397  /// window. On other systems this does nothing at all.
398  pub fn set_title_win32(&mut self, title: &str) {
399    pancurses::set_title(title);
400  }
401
402  /// Attempts to assign a new cursor visibility. If this is successful you
403  /// get a `Some` back with the old setting inside. If this fails you get a
404  /// `None` back. For more info see
405  /// [curs_set](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/curs_set.html)
406  pub fn set_cursor_visibility(&mut self, vis: CursorVisibility) -> Option<CursorVisibility> {
407    use CursorVisibility::*;
408    let result = pancurses::curs_set(match vis {
409      Invisible => 0,
410      Visible => 1,
411      HighlyVisible => 2,
412    });
413    match result {
414      0 => Some(Invisible),
415      1 => Some(Visible),
416      2 => Some(HighlyVisible),
417      _ => None,
418    }
419  }
420
421  /// The terminal gets input from the user. Then it's sometimes buffered up. At
422  /// some point it's passed into the program's input buffer, and then
423  /// `get_input` gets things out of that buffer.
424  ///
425  /// * Character: Input is passed in 1 character at a time, but special
426  ///   characters (such as Ctrl+C and Ctrl+S) are automatically processed for
427  ///   you by the terminal.
428  /// * Cooked: Input is passed in 1 line at a time, with the special character
429  ///   processing mentioned above enabled.
430  /// * RawCharacter: Input is passed in 1 character at a time, and special
431  ///   character sequences are not processed automatically.
432  /// * RawCooked: Input is passed in 1 line at a time, and special
433  ///   character sequences are not processed automatically.
434  ///
435  /// The default mode is inherited from the terminal that started the program
436  /// (usually Cooked), so you should _always_ set the desired mode explicitly
437  /// at the start of your program.
438  ///
439  /// See also the [Input
440  /// Mode](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/intov.html#tag_001_005_002)
441  /// section of the curses documentation.
442  pub fn set_input_mode(&mut self, mode: InputMode) -> Option<()> {
443    to_option(match mode {
444      InputMode::Character => pancurses::cbreak(),
445      InputMode::Cooked => pancurses::nocbreak(),
446      InputMode::RawCharacter => pancurses::raw(),
447      InputMode::RawCooked => pancurses::noraw(),
448    })
449  }
450
451  /// This controls how long `get_input` will wait before returning a `None`
452  /// value.
453  ///
454  /// The default mode is an unlimited wait.
455  ///
456  /// The `WaitUpTo` value is measured in milliseconds, and any negative value
457  /// is treated as 0 (the same as an immediate timeout).
458  ///
459  /// See also: The
460  /// [notimeout](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/notimeout.html)
461  /// curses function.
462  pub fn set_input_timeout(&mut self, mode: TimeoutMode) {
463    match mode {
464      TimeoutMode::Immediate => self.win.timeout(0),
465      TimeoutMode::WaitUpTo(n) => self.win.timeout(n.max(0)),
466      TimeoutMode::Never => self.win.timeout(-1),
467    };
468  }
469
470  /// Enables special key processing from buttons such as the keypad and arrow
471  /// keys. This defaults to `false`. You probably want to set it to `true`.
472  /// If it's not on and the user presses a special key then get_key will
473  /// return will do nothing or give `ERR`.
474  pub fn set_keypad_enabled(&mut self, use_keypad: bool) -> Option<()> {
475    to_option(self.win.keypad(use_keypad))
476  }
477
478  /// Enables or disables the automatic echoing of input into the window as
479  /// the user types. Default to on, but you probably want it to be off most
480  /// of the time.
481  pub fn set_echo(&mut self, echoing: bool) -> Option<()> {
482    to_option(if echoing { pancurses::echo() } else { pancurses::noecho() })
483  }
484
485  // TODO: pancurses::resize_term?
486
487  /// Checks if the current terminal supports the use of colors.
488  pub fn is_color_terminal(&self) -> bool {
489    self.color_support
490  }
491
492  /// Sets the current color pair of the window. Output at any location will
493  /// use this pair until a new pair is set. Does nothing if the terminal does
494  /// not support colors in the first place.
495  pub fn set_color_pair(&mut self, pair: ColorPair) {
496    if self.color_support {
497      self.win.color_set(pair.0);
498    }
499  }
500
501  /// Enables or disables bold text for all future input.
502  pub fn set_bold(&mut self, bold_on: bool) -> Option<()> {
503    to_option(if bold_on {
504      self.win.attron(pancurses::Attribute::Bold)
505    } else {
506      self.win.attroff(pancurses::Attribute::Bold)
507    })
508  }
509
510  /// Enables or disables underlined text for all future input.
511  pub fn set_underline(&mut self, underline_on: bool) -> Option<()> {
512    to_option(if underline_on {
513      self.win.attron(pancurses::Attribute::Underline)
514    } else {
515      self.win.attroff(pancurses::Attribute::Underline)
516    })
517  }
518
519  /// Returns the number of rows and columns available in the window. Each of
520  /// these are the number of locations in that dimension, but the rows and
521  /// cols (as well as the Xs and Ys if you care to use that coordinate space)
522  /// use 0-based indexing, so the actual addressable locations are numbered 0
523  /// to N-1, similar to with slices, `.len()`, and indexing. Fortunately, the
524  /// normal rust Range type already handles this for us. If you wanted to
525  /// iterate every cell of the window you'd probably use a loop like this:
526  ///
527  /// ```rust
528  /// let mut easy = easycurses::EasyCurses::initialize_system().unwrap();
529  /// let (row_count,col_count) = easy.get_row_col_count();
530  /// // using RC coordinates.
531  /// for row in 0..row_count {
532  ///     for col in 0..col_count {
533  ///         easy.move_rc(row,col);
534  ///         let (actual_row,actual_col) = easy.get_cursor_rc();
535  ///         assert!(actual_row == row && actual_col == col);
536  ///     }
537  /// }
538  /// // using XY coordinates.
539  /// for y in 0..row_count {
540  ///     for x in 0..col_count {
541  ///         easy.move_xy(x,y);
542  ///         let (actual_x,actual_y) = easy.get_cursor_xy();
543  ///         assert!(actual_x == x && actual_y == y);
544  ///     }
545  /// }
546  /// ```
547  pub fn get_row_col_count(&self) -> (i32, i32) {
548    self.win.get_max_yx()
549  }
550
551  /// Moves the virtual cursor to the row and column specified, relative to
552  /// the top left ("notepad" space). Does not move the terminal's displayed
553  /// cursor (if any) until `refresh` is also called. Out of bounds locations
554  /// cause this command to be ignored.
555  pub fn move_rc(&mut self, row: i32, col: i32) -> Option<()> {
556    to_option(self.win.mv(row, col))
557  }
558
559  /// Obtains the cursor's current position using `(R,C)` coordinates
560  /// relative to the top left corner.
561  pub fn get_cursor_rc(&self) -> (i32, i32) {
562    self.win.get_cur_yx()
563  }
564
565  /// Moves the virtual cursor to the x and y specified, relative to the
566  /// bottom left ("cartesian" space). Does not move the terminal's displayed
567  /// cursor (if any) until `refresh` is also called. Out of bounds locations
568  /// cause this command to be ignored.
569  pub fn move_xy(&mut self, x: i32, y: i32) -> Option<()> {
570    let row_count = self.win.get_max_y();
571    to_option(self.win.mv(row_count - (y + 1), x))
572  }
573
574  /// Obtains the cursor's current position using `(X,Y)` coordinates relative
575  /// to the bottom left corner.
576  pub fn get_cursor_xy(&self) -> (i32, i32) {
577    let row_count = self.win.get_max_y();
578    let (row, col) = self.win.get_cur_yx();
579    (col, row_count - (row + 1))
580  }
581
582  /// When scrolling is enabled, any attempt to move off the bottom margin
583  /// will cause lines within the scrolling region to scroll up one line. If a
584  /// scrolling region is set but scrolling is not enabled then attempts to go
585  /// off the bottom will just print nothing instead. Use `set_scroll_region`
586  /// to control the size of the scrolling region.
587  pub fn set_scrolling(&mut self, scrolling: bool) -> Option<()> {
588    to_option(self.win.scrollok(scrolling))
589  }
590
591  /// Sets the top and bottom margins of the scrolling region. The inputs
592  /// should be the line numbers (relative to the top of the screen) for the
593  /// borders. Either border can be 0.
594  ///
595  /// See also:
596  /// [setscrreg](http://pubs.opengroup.org/onlinepubs/7908799/xcurses/setscrreg.html)
597  pub fn set_scroll_region(&mut self, top: i32, bottom: i32) -> Option<()> {
598    to_option(self.win.setscrreg(top, bottom))
599  }
600
601  /// Prints the given string-like value into the window by printing each
602  /// individual character into the window. If there is any error encountered
603  /// upon printing a character, that cancels the printing of the rest of the
604  /// characters.
605  pub fn print<S: AsRef<str>>(&mut self, asref: S) -> Option<()> {
606    // Here we want to
607    if cfg!(windows) {
608      // PDCurses does an extra intermediate CString allocation, so we just
609      // print out each character one at a time to avoid that.
610      asref.as_ref().chars().try_for_each(|c| self.print_char(c))
611    } else {
612      // NCurses, it seems, doesn't do the intermediate allocation and also uses
613      // a faster routine for printing a whole string at once.
614      to_option(self.win.printw(asref.as_ref()))
615    }
616  }
617
618  /// Prints the given character into the window.
619  pub fn print_char<T: ToChtype>(&mut self, character: T) -> Option<()> {
620    to_option(self.win.addch(character))
621  }
622
623  /// Inserts the character desired at the current location, pushing the
624  /// current character at that location (and all after it on the same line)
625  /// one cell to the right.
626  pub fn insert_char<T: ToChtype>(&mut self, character: T) -> Option<()> {
627    to_option(self.win.insch(character))
628  }
629
630  /// Deletes the character under the cursor. Characters after it on same the
631  /// line are pulled left one position and the final character cell is left
632  /// blank. The cursor position does not move.
633  pub fn delete_char(&mut self) -> Option<()> {
634    to_option(self.win.delch())
635  }
636
637  /// Inserts a line above the current line. The bottom line is lost.
638  pub fn insert_line(&mut self) -> Option<()> {
639    to_option(self.win.insertln())
640  }
641
642  /// Deletes the line under the cursor. Lines below are moved up one line and
643  /// the final line is left blank. The cursor position does not move.
644  pub fn delete_line(&mut self) -> Option<()> {
645    to_option(self.win.deleteln())
646  }
647
648  /// For positive n, insert n lines into the specified window above the current
649  /// line. The n bottom lines are lost. For negative n, delete n lines
650  /// (starting with the one under the cursor), and move the remaining lines up.
651  /// The bottom n lines are cleared. The current cursor position remains the
652  /// same.
653  pub fn bulk_insert_delete_line(&mut self, n: i32) -> Option<()> {
654    to_option(self.win.insdelln(n))
655  }
656
657  /// Clears the entire screen.
658  ///
659  /// **Note:** This function can cause flickering of the output with PDCurses
660  /// if you're clearing the screen and then immediately writing to the whole
661  /// screen before you end up calling `refresh`. The exact location of the
662  /// flickering effect seems to vary from machine to machine. If you intend
663  /// to simply replace the whole window with new content, just overwrite the
664  /// previous values without calling `clear` and things will be fine.
665  pub fn clear(&mut self) -> Option<()> {
666    to_option(self.win.clear())
667  }
668
669  /// Refreshes the window's appearance on the screen. With some
670  /// implementations you don't need to call this, the screen will refresh
671  /// itself on its own. However, for portability, you should call this at the
672  /// end of each draw cycle.
673  pub fn refresh(&mut self) -> Option<()> {
674    to_option(self.win.refresh())
675  }
676
677  /// Plays an audible beep if possible, if not the screen is flashed. If
678  /// neither is available then nothing happens.
679  pub fn beep(&mut self) {
680    pancurses::beep();
681  }
682
683  /// Flashes the screen if possible, if not an audible beep is played. If
684  /// neither is available then nothing happens.
685  pub fn flash(&mut self) {
686    pancurses::flash();
687  }
688
689  /// Gets an `Input` from the curses input buffer. This will block or not
690  /// according to the input mode, see `set_input_mode`. If `KeyResize` is
691  /// seen and `auto_resize` is enabled then the window will automatically
692  /// update its size for you. In that case, `KeyResize` is still passed to
693  /// you so that you can change anything else that might need to be updated.
694  pub fn get_input(&mut self) -> Option<pancurses::Input> {
695    let ret = self.win.getch();
696    if self.auto_resize {
697      match ret {
698        Some(Input::KeyResize) => {
699          self.resize(0, 0);
700        }
701        _ => (),
702      };
703    }
704    ret
705  }
706
707  /// Discards all type-ahead that has been input by the user but not yet read
708  /// by the program.
709  pub fn flush_input(&mut self) {
710    pancurses::flushinp();
711  }
712
713  /// Pushes an `Input` value into the input stack so that it will be returned
714  /// by the next call to `get_input`. The return value is if the operation
715  /// was successful.
716  pub fn un_get_input(&mut self, input: pancurses::Input) -> Option<()> {
717    to_option(self.win.ungetch(&input))
718  }
719
720  /// Sets the window to use the number of lines and columns specified. If you
721  /// pass zero for both then this will make the window's data structures
722  /// attempt to match the current size of the window. This is done
723  /// automatically for you when `KeyResize` comes in through the input
724  /// buffer.
725  pub fn resize(&mut self, new_lines: i32, new_cols: i32) -> Option<()> {
726    to_option(pancurses::resize_term(new_lines, new_cols))
727  }
728}
729
730/// Wraps the use of curses with `catch_unwind` to preserve panic info.
731///
732/// Normally, if your program panics while in curses mode the panic message
733/// prints immediately and then is destroyed before you can see it by the
734/// automatic cleanup of curses mode. Instead, this runs the function you pass
735/// it within `catch_unwind` and when there's a panic it catches the panic value
736/// and attempts to downcast it into a `String` you can print out or log or
737/// whatever you like. Since a panic value can be anything at all this won't
738/// always succeed, thus the `Option` wrapper on the `Err` case. Regardless of
739/// what of `Result` you get back, curses mode will be fully cleaned up and shut
740/// down by the time this function returns.
741///
742/// Note that you *don't* have to use this if you just want your terminal
743/// restored to normal when your program panics while in curses mode. That is
744/// handled automatically by the `Drop` implementation of `EasyCurses`. You only
745/// need to use this if you care about the panic message itself.
746pub fn preserve_panic_message<F: FnOnce(&mut EasyCurses) -> R + UnwindSafe, R>(user_function: F) -> Result<R, Option<String>> {
747  let result = catch_unwind(|| {
748    // Normally calling `expect` is asking for eventual trouble to bite us,
749    // but we're specifically inside a `catch_unwind` block so it's fine.
750    let mut easy = EasyCurses::initialize_system().expect("Curses double-initialization.");
751    user_function(&mut easy)
752  });
753  result.map_err(|e| match e.downcast_ref::<&str>() {
754    Some(andstr) => Some(andstr.to_string()),
755    None => match e.downcast_ref::<String>() {
756      Some(string) => Some(string.to_string()),
757      None => None,
758    },
759  })
760}