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}