Skip to main content

utile_cli/
cli.rs

1extern crate pancurses;
2use pancurses::{Window, Input, initscr};
3
4/// A terminal containing a pancurses window.
5/// 
6/// Contains multiple abstractions over pancurses.
7/// 
8/// # Examples
9/// Creating a terminal:
10/// ```
11/// let t = Terminal::new()
12/// ```
13pub struct Terminal {
14  win: Window,
15  layers: LayerArrangement
16}
17
18struct LayerArrangement {
19  layer_stack: Vec<Layer2D>
20}
21
22/// An arbitrary type that contains a position and content.
23/// A layer can be positioned anywhere in the console, and it can instead be edited as a string without manual manipulation of the cursor.
24/// 
25/// A layer is one dimensional, meaning that it does not fit multiple lines.
26/// 
27/// Layers may shrink and grow, however have allocated space in the terminal, for example, a layer with content "Hello" which then is changed to "Bye" will actually appear on the console as "Bye  " (with two spaces).
28/// This means that they have their own space depending on their longest length string.
29/// This trait is useful for hiding certain objects from view, however can be reset using `shrink` which removes trailing whitespace.
30/// 
31/// # Examples
32/// A layer can be initialized using `new`:
33/// ```
34/// let layer = Layer::new(0, 0);
35/// layer.set_content("Hello world!".into());
36/// ```
37/// 
38/// A layer can then be displayed from a terminal either by being added using `add_layer` and then displayed using `refresh`:
39/// ```
40/// let new: &mut Layer = t.add_layer(layer);
41/// t.refresh();
42/// ```
43/// 
44/// Or it can be displayed externally by using `draw_layer`:
45/// ```
46/// t.draw_layer(&layer);
47/// t.draw_layer_static(&layer); // <- a layer can also be drawn without editting the cursor position
48/// ```
49/// 
50/// A layer also contains another type of content called `inner_content` which will *never* be displayed to the terminal, however may contain useful data about the layer:
51/// ```
52/// layer.inner_content = "Hello rust!".into();
53/// layer.inner_to_outer(); // <- replaces the outer content with the inner content.
54/// ```
55#[derive(Clone, Debug)]
56pub struct Layer {
57  pub posx: i32,
58  pub posy: i32,
59  pub inner_content: String,
60  content: String,
61  length: usize
62}
63
64/// An arbitrary type that derives from Layer however can handle both X and Y coordinates.
65/// A Layer2D contains an (2D) array of layers allowing them to be rendered in a container and be indexed as such.
66/// 
67/// # Examples
68/// A Layer2D can be initialized using `new`:
69/// ```
70/// let l = Layer::new(0, 0);
71/// let l2d = Layer2D::new(0, 0, 5, 5, l);
72/// ```
73/// `l` in this case is the *populator* which will fill the Layer2D on initialization.
74/// `l` also does not need any positional arguments as they will be overriden on the l2d initialization.
75/// 
76/// A Layer2D can be populated again using the `populate` method:
77/// ```
78/// let l2 = Layer::new(0, 0);
79/// l2.set_content("X".into());
80/// l2d.populate(l2);
81/// ```
82/// This results in a 5x5 box of `X` characters.
83/// 
84/// Any layer in a Layer2D can be retrieved using `index` or `get`:
85/// ```
86/// l2d.index(3, 4); // returns a *mutable* Layer (&mut Layer)
87/// l2d.get(3, 4);   // returns a *immutable* Layer (&Layer)
88/// ```
89/// 
90/// A layer can be displayed from a terminal using `add_layer2d` and `refresh`, much like regular layers:
91/// ```
92/// t.add_layer2d(l2d); // <- returns a &mut Layer2D which can be editted
93/// t.refresh();
94/// ```
95/// 
96/// Or it can be displayed externally by using `draw_layer2d`:
97/// ```
98/// t.draw_layer2d(&layer);
99/// t.draw_layer2d_static(&layer); // <- a layer can also be drawn without editting the cursor position
100/// ```
101#[derive(Clone, Debug)]
102pub struct Layer2D {
103  pub posx: i32,
104  pub posy: i32,
105  pub length: usize,
106  pub height: usize,
107  pub layers: Vec<Layer>,
108  char_count: usize,
109  stack_loc: i32
110}
111
112#[derive(Debug, PartialEq)]
113pub enum Key {
114  Alpha(char),
115  Enter,
116  Space,
117  Backspace,
118  ArrowUp,
119  ArrowDown,
120  ArrowLeft,
121  ArrowRight,
122  F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12
123}
124
125
126impl Layer {
127  /// Returns a new layer at `posx`, `posy`
128  pub fn new(posx: i32, posy: i32) -> Layer {
129    Layer { posx, posy, content: String::new(), inner_content: String::new(), length: 0 }
130  }
131
132  /// Returns a clone of the outer content of the layer.
133  pub fn get_content(&self) -> String {
134    self.content.clone()
135  }
136
137  /// Sets the current outer content of the layer.
138  pub fn set_content(&mut self, c: String) -> &mut Layer {
139    self.content = c;
140    if self.content.len() > self.length {
141      self.length = self.content.len();
142    }
143    self
144  }
145
146  /// Replaces the outer content with the inner content.
147  pub fn inner_to_outer(&mut self) {
148    self.set_content(self.inner_content.clone());
149  }
150
151  /// Removes any hiding content.
152  pub fn shrink(&mut self) {
153    self.length = self.content.len();
154  }
155}
156
157impl Layer2D {
158  /// Returns a new layer2d at `posx`, `posy` with length and height.
159  /// Position is determined from the top left corner.
160  pub fn new(posx: i32, posy: i32, length: usize, height: usize, populator: Layer) -> Layer2D {
161    let mut l = Layer2D { posx, posy, length, height, layers: vec![], char_count: 0, stack_loc: 0 };
162    l.populate(populator);
163    l
164  }
165
166  /// Populates a layer2d with `populator`
167  pub fn populate(&mut self, populator: Layer) {
168    let n = populator.clone();
169    self.char_count = n.get_content().len();
170    self.layers = std::iter::repeat(n).take(self.length * self.height)
171                              .enumerate()
172                              .map(|(i, l)| { 
173                                let mut x = l.clone(); 
174                                x.posx = (i % self.length * self.char_count) as i32; 
175                                x.posy = ((i / self.length) as f64).floor() as i32; 
176                                x 
177                              }).collect();
178  }
179
180  /// Returns a *mutable* reference to the layer at x and y
181  pub fn index(&mut self, x: usize, y: usize) -> &mut Layer {
182    &mut self.layers[x + y * self.length]
183  }
184
185  /// Returns a *immutable* reference to the layer at x and y
186  pub fn get(&self, x: usize, y: usize) -> &Layer {
187    &self.layers[x + y * self.length]
188  }
189}
190
191impl LayerArrangement {
192  fn new() -> LayerArrangement {
193    LayerArrangement { layer_stack: vec![] }
194  }
195
196  fn push(&mut self, layer: Layer2D) -> &mut Layer2D {
197    self.layer_stack.push(layer);
198    self.layer_stack.last_mut().unwrap()
199  }
200
201  fn pop(&mut self) -> Option<Layer2D> {
202    self.layer_stack.pop()
203  }
204}
205
206// locate index in stack
207fn locate_idx(len: usize, l: i32) -> usize {
208  match l <= 0 {
209    false => l as usize,
210    true => ((len - 1) as i32 + l) as usize
211  }
212}
213
214impl Terminal {
215
216  /// Creates a new terminal
217  pub fn new() -> Terminal {
218    let win = initscr();
219    win.keypad(true);
220    Terminal { win, layers: LayerArrangement::new() }
221  }
222
223  // Adds a layer to the bottom of the layer 'queue'
224  pub fn add_layer(&mut self, layer: Layer) -> &mut Layer {
225    let mut r2d = Layer2D::new(layer.posx, layer.posy, 1, 1, layer);
226    r2d.stack_loc = -(self.layers.layer_stack.len() as i32);
227    self.layers.push(r2d).index(0, 0)
228  }
229
230  // Adds a layer2D to the bottom of the layer 'queue'
231  pub fn add_layer2d(&mut self, layer: Layer2D) -> &mut Layer2D {
232    let mut r = layer.clone();
233    r.stack_loc = -(self.layers.layer_stack.len() as i32);
234    self.layers.push(layer)
235  }
236
237  /// Refreshes and re-draws all layers.
238  pub fn refresh(&self) {
239    self.win.refresh();
240    let here = self.raw_posxy();
241    for l2d in &self.layers.layer_stack {
242      self.draw_layer2d(l2d);
243    }
244    self.raw_move(here.0, here.1);
245    self.win.refresh();
246  }
247
248  /// Returns the layer at the front.
249  pub fn layer_front(&mut self) -> &mut Layer2D {
250    self.layers.layer_stack.last_mut().unwrap()
251  }
252
253  /// Returns the layer at the back.
254  pub fn layer_back(&mut self) -> &mut Layer2D {
255    self.layers.layer_stack.first_mut().unwrap()
256  }
257
258  /// Swaps two layers. Check the `layer_locate` documentation about the number parameters.
259  pub fn layer_swap(&mut self, a: i32, b: i32) {
260    let len = self.layers.layer_stack.len();
261    self.layers.layer_stack.swap(locate_idx(len, a), locate_idx(len, b));
262  }
263
264  /// Locates a layer by the distance to the stack or by the last item -
265  /// 
266  /// A negative number such as -1 returns the layer *after* the top layer,
267  /// A positive number such as 1 returns the second to last layer,
268  /// A zero returns the *top* layer.
269  /// 
270  /// # Examples:
271  /// Imagine a stack:
272  /// ```
273  /// [ L1 ] <- `layer_locate(0)` or `layer_front()`
274  /// [ L2 ] <- `layer_locate(-1)`
275  /// [ L3 ] <- `layer_locate(1)` or `layer_locate(-2)`
276  /// [ L4 ] <- `layer_back()`
277  /// ```
278  pub fn layer_locate(&mut self, l: i32) -> &mut Layer2D {
279    let len = self.layers.layer_stack.len();
280    &mut self.layers.layer_stack[locate_idx(len, l)]
281  }
282
283  /// Draws a layer to the console.
284  pub fn draw_layer(&self, layer: &Layer) {
285    self.raw_move(layer.posx, layer.posy);
286    self.raw_out_static(" ".repeat(layer.length)); // clear layer
287    self.raw_out(layer.get_content());
288  }
289
290  /// Draws a layer to the console however does not affect the cursor.
291  pub fn draw_layer_static(&self, layer: &Layer) {
292    let here = self.raw_posxy();
293    self.draw_layer(layer);
294    self.raw_move(here.0, here.1);
295  }
296
297  /// Draws a layer2D to the console.
298  pub fn draw_layer2d(&self, layer: &Layer2D) {
299    self.raw_move(layer.posx, layer.posy);
300    layer.layers.iter().for_each(|l| self.draw_layer_static(l) );
301  }
302
303  /// Draws a layer2D to the console however does not affect the cursor.
304  pub fn draw_layer2d_static(&self, layer: &Layer) {
305    let here = self.raw_posxy();
306    self.draw_layer(layer);
307    self.raw_move(here.0, here.1);
308  }
309  
310  /// Outputs a string over the cursor.
311  pub fn out(&self, s: String) {
312    self.win.printw(s);
313    self.refresh();
314  }
315
316  /// Outputs a string that does not affect the cursor position.
317  pub fn out_static(&self, s: String) {
318    let here = self.raw_posxy();
319    self.out(s);
320    self.raw_move(here.0, here.1);
321  }
322
323  /// Outputs a string over the cursor and outputs a break / newline.
324  pub fn outln(&self, s: String) {
325    self.win.printw(s);
326    self.outbr();
327  }
328
329  /// Outputs a newline.
330  pub fn outbr(&self) {
331    self.win.printw(String::from("\n"));
332    self.refresh();
333  }
334
335  /// Outputs a string without refreshing the terminal.
336  pub fn raw_out(&self, s: String) {
337    self.win.printw(s);
338  }
339
340  /// Outputs a string that does not affect the cursor position and does not refresh the terminal.
341  pub fn raw_out_static(&self, s: String) {
342    let here = self.raw_posxy();
343    self.raw_out(s);
344    self.raw_move(here.0, here.1);
345  }
346
347  /// Outputs a string and a break / newline without refreshing the terminal.
348  pub fn raw_outln(&self, s: String) {
349    self.win.printw(s);
350    self.raw_br();
351  }
352
353  /// Outputs a newline without refreshing the terminal.
354  pub fn raw_br(&self) {
355    self.win.printw(String::from("\n"));
356  }
357
358  /// Returns a tuple containing the current cursor position in the form (x, y)
359  pub fn raw_posxy(&self) -> (i32, i32) {
360    (self.win.get_cur_x(), self.win.get_cur_y())
361  }
362
363  /// Returns the x position of the current cursor position.
364  pub fn raw_posx(&self) -> i32 {
365    self.win.get_cur_x()
366  }
367
368  /// Returns the y position of the current cursor position.
369  pub fn raw_posy(&self) -> i32 {
370    self.win.get_cur_y()
371  }
372
373  /// Moves the cursor to position x and y.
374  pub fn raw_move(&self, x: i32, y: i32) {
375    self.win.mv(y, x);
376  }
377
378  /// Offsets the cursor by x and y.
379  pub fn raw_move_offset(&self, xoffs: i32, yoffs: i32) {
380    self.raw_move(self.raw_posx() + xoffs, self.raw_posy() + yoffs);
381  }
382
383  /// Moves the cursor to the start of the line.
384  pub fn raw_move_first(&self) {
385    self.raw_move(0, self.raw_posy());
386  }
387
388  /// Moves one position back (x - 1).
389  pub fn raw_move_prev(&self) {
390    self.raw_move_offset(-1, 0);
391  }
392
393  /// Moves one position forwards (x + 1).
394  pub fn raw_move_next(&self) {
395    self.raw_move_offset(1, 0);
396  }
397
398  /// Deletes the character that the current cursor is on.
399  pub fn raw_delete(&self) {
400    self.win.delch();
401  }
402
403  /// Deletes the last character (not letter).
404  pub fn raw_delete_prev(&self) {
405    self.raw_move_prev();
406    self.raw_delete();
407  }
408
409  /// Deletes all the characters from the cursor until the current position is offset position.
410  /// 
411  /// # Examples
412  /// ```
413  /// t.out("Hello world!".into());
414  /// t.raw_delete_offset(-6);
415  /// ```
416  /// Output: `Hello `
417  pub fn raw_delete_offset(&self, xoffs: i32) {
418    let offs = xoffs / xoffs.abs(); // -1 or 1
419    let here = self.raw_posx();
420    while self.raw_posx() != here + xoffs {
421      self.raw_move_offset(offs, 0);
422      self.raw_delete();
423    }
424  }
425
426  /// Deletes, from the start of the line, all the characters until `chars` position.
427  /// 
428  /// # Examples
429  /// ```
430  /// t.out("Hello world!".into());
431  /// t.raw_delete_from(5);
432  /// ```
433  /// Output: ` world!`
434  pub fn raw_delete_from(&self, chars: usize) {
435    self.raw_move_first();
436    self.raw_move_offset(chars as i32, 0);
437    self.raw_delete_to(0);
438  }
439
440  /// Deletes all characters from the cursor until the x position is `x`
441  /// 
442  /// # Examples
443  /// ```
444  /// t.out("Hello world!".into());
445  /// t.raw_delete_to(5);
446  /// ```
447  /// Output: `Hello`
448  pub fn raw_delete_to(&self, x: i32) {
449    let offs = if x > self.raw_posx() {
450      1
451    } else {
452      -1
453    };
454
455    while self.raw_posx() != x {
456      self.raw_move_offset(offs, 0);
457      self.raw_delete();
458    }
459  }
460
461  /// Gets a character from input.
462  pub fn get_char(&self) -> Option<Key> {
463    match self.win.getch() {
464      Some(Input::Character('\n')) | Some(Input::Character('\r')) => Some(Key::Enter),
465      Some(Input::Character('\x08')) => Some(Key::Backspace),
466      Some(Input::Character(c)) => Some(Key::Alpha(c)),
467      Some(Input::KeyUp) => Some(Key::ArrowUp),
468      Some(Input::KeyDown) => Some(Key::ArrowDown),
469      Some(Input::KeyLeft) => Some(Key::ArrowLeft),
470      Some(Input::KeyRight) => Some(Key::ArrowRight),
471      Some(Input::KeyF1) => Some(Key::F1),
472      Some(Input::KeyF2) => Some(Key::F2),
473      Some(Input::KeyF3) => Some(Key::F3),
474      Some(Input::KeyF4) => Some(Key::F4),
475      Some(Input::KeyF5) => Some(Key::F5),
476      Some(Input::KeyF6) => Some(Key::F6),
477      Some(Input::KeyF7) => Some(Key::F7),
478      Some(Input::KeyF8) => Some(Key::F8),
479      Some(Input::KeyF9) => Some(Key::F9),
480      Some(Input::KeyF10) => Some(Key::F10),
481      Some(Input::KeyF11) => Some(Key::F11),
482      Some(Input::KeyF12) => Some(Key::F12),
483      None => None,
484      _ => None,
485    }
486  }
487
488  /// Returns a character however hides it from input.
489  pub fn get_char_hidden(&self) -> Option<Key> {
490    let here = self.raw_posxy();
491    let ret = self.get_char();
492    match ret {
493      Some(Key::Alpha(c)) => { self.raw_delete_prev(); },
494      _ | None => ()
495    }
496    self.raw_move(here.0, here.1);
497    ret
498  }
499
500  /// Asks the user for input, prefixing the question with `prefix`
501  /// 
502  /// # Examples
503  /// ```
504  /// t.ask("> ".into())
505  /// ```
506  pub fn ask(&self, prefix: String) -> String {
507    self.out(prefix);
508    let mut r = Layer::new(self.raw_posx(), self.raw_posy());
509    while let Some(i) = self.get_char_hidden() {
510      match i {
511        Key::Enter => break,
512        Key::Backspace => {
513          if let Some(i) = r.get_content().pop() {
514            let mut content = r.get_content();
515            content.pop();
516            r.set_content(content);
517            self.draw_layer(&r);
518          } else {
519            self.raw_move_next();
520            continue;
521          }
522        },
523        Key::Alpha(c) => {
524          let mut content = r.get_content();
525          content.push(c);
526          r.set_content(content);
527          self.draw_layer(&r);
528        },
529        _ => continue,
530      }
531    }
532    r.get_content()
533  }
534
535  /// Asks the user for input, however the input is masked by a series of `mask` to hide the input.
536  pub fn mask(&self, prefix: String, mask: char) -> String {
537    self.out(prefix);
538    let mut r = Layer::new(self.raw_posx(), self.raw_posy());
539    let mut s = String::new();
540    while let Some(i) = self.get_char_hidden() {
541      match i {
542        Key::Enter => break,
543        Key::Backspace => {
544          if let Some(i) = r.get_content().pop() {
545            let mut content = r.get_content();
546            content.pop();
547            r.set_content(content);
548            self.draw_layer(&r);
549            s.pop();
550          } else {
551            self.raw_move_next();
552            continue;
553          }
554        },
555        Key::Alpha(c) => {
556          let mut content = r.get_content();
557          content.push(mask);
558          s.push(c);
559          r.set_content(content);
560          self.draw_layer(&r);
561        },
562        _ => continue,
563      }
564    }
565    s
566  }
567
568  /// Asks a y/n question to the user, returning a boolean (true if yes).
569  /// 
570  /// The `suffix` parameter must be specified like "exampley/examplen" (must contain a '/')
571  /// The `default` parameter is the default highlighted y/n
572  /// 
573  /// You can choose a yes or a no using the left and right arrow keys.
574  /// # Examples
575  /// ```
576  /// t.yesno("y/n".into(), true)
577  /// ```
578  /// Outputs: (Y/n)
579  pub fn yesno(&self, suffix: String, default: bool) -> bool {
580    let yn: Vec<String> = suffix.split('/').map(|s| String::from(s)).collect();
581    if yn.len() == 1 {
582      panic!("Expected a '/' character separating a yes no question!");
583    }
584    let mut ynl = Layer::new(self.raw_posx(), self.raw_posy());
585    let mut y = yn[0].clone();
586    let mut n = yn[1].clone();
587    match default {
588      true => { y = y.to_uppercase(); },
589      false => { n = n.to_uppercase(); },
590    }
591    ynl.set_content(format!("({}/{})", y.clone(), n.clone()));
592    self.draw_layer(&ynl);
593    let mut ret = default;
594    while let Some(i) = self.get_char_hidden() {
595      match i {
596        Key::Enter => break,
597        Key::ArrowRight => { ret = false; },
598        Key::ArrowLeft => { ret = true; },
599        _ => continue
600      }
601      match ret {
602        true => { y = { n = n.to_lowercase(); y.to_uppercase() } },
603        false => { n = { y = y.to_lowercase(); n.to_uppercase() } },
604      }
605      ynl.set_content(format!("({}/{})", y.clone(), n.clone()));
606      self.draw_layer(&ynl);
607    }
608    ret
609  }
610
611  /// Gives the user choices between strings.
612  /// Takes in a `prefix` to be added as a prefix on the current choice, and vector `strs` as the choices.
613  /// The result output is a list in which options can be highlighted by using the arrow keys.
614  /// # Examples
615  /// ```
616  /// let x = t.choices("-> ".into(), vec!["c1".into(), "c22".into(), "c333".into(), "c4444".into()]);
617  /// t.outln(x);
618  /// ```
619  /// Output if selected was `c2`: `c2`
620  pub fn choices(&self, prefix: String, strs: Vec<String>) -> String {
621    self.outbr();
622    let mut layers: Vec<Layer> = vec![];
623    for (i , str) in strs.iter().enumerate() {
624      let mut l = Layer::new(self.raw_posx(), self.raw_posy() + i as i32);
625      l.inner_content = str.clone();
626      if i == 0 {
627        l.set_content(format!("{}{}", prefix.clone(), l.inner_content.clone()));
628      } else { l.inner_to_outer(); }
629      self.draw_layer_static(&l);
630      layers.push(l);
631    }
632
633    self.raw_move_offset(0, layers.len() as i32);
634    let mut y = 0;
635    while let Some(i) = self.get_char_hidden() {
636      match i {
637        Key::Enter => break,
638        Key::ArrowDown => {
639          if (y < layers.len() - 1) {
640            y += 1;
641          }
642        },
643        Key::ArrowUp => {
644          if (y > 0) {
645            y -= 1;
646          }
647        }
648        _ => continue
649      }
650      for (i, l) in layers.iter_mut().enumerate() {
651        if i == y {
652          l.set_content(format!("{}{}", prefix.clone(), l.inner_content.clone()));
653        } else { l.inner_to_outer(); }
654        self.draw_layer_static(&l);
655      }
656    }
657    
658    layers[y].inner_content.clone()
659  }
660}