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}