zql_cli/core/pager.rs
1use crate::regex;
2use std::collections::VecDeque;
3use std::process::{Child, Command, Stdio};
4
5pub struct Pager {
6 command: String,
7 args: VecDeque<String>,
8 failed: bool,
9}
10
11impl Pager {
12 pub fn new(pager: String) -> Self {
13 let regex = regex!(r"\bless\b");
14 let mut args = pager.split_whitespace().map(String::from).collect::<VecDeque<_>>();
15 let command = args.pop_front().unwrap_or_else(|| String::from("less"));
16 if regex.is_match(&command) {
17 // -e or --quit-at-eof
18 // Causes less to automatically exit the second time it
19 // reaches end-of-file. By default, the only way to exit
20 // less is via the "q" command.
21 // -F or --quit-if-one-screen
22 // Causes less to automatically exit if the entire file
23 // can be displayed on the first screen.
24 // -Q or --QUIET or --SILENT
25 // Causes totally "quiet" operation: the terminal bell is
26 // never rung. If the terminal has a "visual bell", it is
27 // used in all cases where the terminal bell would have
28 // been rung.
29 // -R or --RAW-CONTROL-CHARS
30 // Like -r, but only ANSI "color" escape sequences and OSC 8
31 // hyperlink sequences are output in "raw" form. Unlike -r,
32 // the screen appearance is maintained correctly, provided
33 // that there are no escape sequences in the file other than
34 // these types of escape sequences. Color escape sequences
35 // are only supported when the color is changed within one
36 // line, not across lines. In other words, the beginning
37 // of each line is assumed to be normal (non-colored),
38 // regardless of any escape sequences in previous lines.
39 // For the purpose of keeping track of screen appearance,
40 // these escape sequences are assumed to not move the cursor.
41 // -X or --no-init
42 // Disables sending the termcap initialization and
43 // deinitialization strings to the terminal. This is
44 // sometimes desirable if the deinitialization string
45 // does something unnecessary, like clearing the screen.
46 args.push_back(String::from("-eFQRX"));
47 }
48 Self { command, args, failed: false }
49 }
50
51 pub fn spawn_child(&mut self) -> Option<Child> {
52 if !self.failed {
53 let child = spawn_child(&self.command, &self.args);
54 self.failed = child.is_none();
55 child
56 } else {
57 None
58 }
59 }
60}
61
62fn spawn_child(command: &str, args: &VecDeque<String>) -> Option<Child> {
63 Command::new(command).args(args).stdin(Stdio::piped()).spawn().ok()
64}
65
66#[cfg(test)]
67mod tests {
68 use crate::core::pager::Pager;
69 use pretty_assertions::assert_eq;
70
71 #[test]
72 fn test_empty_command_is_set_to_less() {
73 let pager = Pager::new(String::from(" "));
74 assert_eq!(pager.command, "less");
75 assert_eq!(pager.args, vec!["-eFQRX"]);
76 }
77
78 #[test]
79 fn test_options_are_extended_for_less() {
80 let pager = Pager::new(String::from(" less "));
81 assert_eq!(pager.command, "less");
82 assert_eq!(pager.args, vec!["-eFQRX"]);
83
84 let pager = Pager::new(String::from(" less -abc -def "));
85 assert_eq!(pager.command, "less");
86 assert_eq!(pager.args, vec!["-abc", "-def", "-eFQRX"]);
87 }
88
89 #[test]
90 fn test_options_not_extended_for_more() {
91 let pager = Pager::new(String::from(" more "));
92 assert_eq!(pager.command, "more");
93 assert_eq!(pager.args, Vec::<String>::new());
94
95 let pager = Pager::new(String::from(" more -abc -def "));
96 assert_eq!(pager.command, "more");
97 assert_eq!(pager.args, vec!["-abc", "-def"]);
98 }
99}