Skip to main content

nu_command/platform/term/
term_query.rs

1use std::{
2    io::{Read, Write},
3    time::Duration,
4};
5
6use crate::platform::RawModeGuard;
7use nu_engine::command_prelude::*;
8use nu_protocol::shell_error::{generic::GenericError, io::IoError};
9
10const CTRL_C: u8 = 3;
11
12#[derive(Clone)]
13pub struct TermQuery;
14
15impl Command for TermQuery {
16    fn name(&self) -> &str {
17        "term query"
18    }
19
20    fn description(&self) -> &str {
21        "Query the terminal for information."
22    }
23
24    fn extra_description(&self) -> &str {
25        "Print the given query, and read the immediate result from stdin.
26
27The standard input will be read right after `query` is printed, and consumed until the `terminator`
28sequence is encountered. The `terminator` is not included in the output.
29
30If `terminator` is not supplied, input will be read until Ctrl-C is pressed.
31
32If `prefix` is supplied, input's initial bytes will be validated against it.
33The `prefix` is not included in the output."
34    }
35
36    fn signature(&self) -> Signature {
37        Signature::build("term query")
38            .category(Category::Platform)
39            .input_output_types(vec![(Type::Nothing, Type::Binary)])
40            .allow_variants_without_examples(true)
41            .required(
42                "query",
43                SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
44                "The query that will be printed to stdout.",
45            )
46            .named(
47                "prefix",
48                SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
49                "Prefix sequence for the expected reply.",
50                Some('p'),
51            )
52            .named(
53                "terminator",
54                SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
55                "Terminator sequence for the expected reply.",
56                Some('t'),
57            )
58            .switch(
59                "keep",
60                "Include prefix and terminator in the output.",
61                Some('k'),
62            )
63    }
64
65    fn examples(&self) -> Vec<Example<'_>> {
66        vec![
67            Example {
68                description: "Get cursor position.",
69                example: "term query (ansi cursor_position) --prefix (ansi csi) --terminator 'R'",
70                result: None,
71            },
72            Example {
73                description: "Get terminal background color.",
74                example: "term query $'(ansi osc)10;?(ansi st)' --prefix $'(ansi osc)10;' --terminator (ansi st)",
75                result: None,
76            },
77            Example {
78                description: "Get terminal background color. (some terminals prefer `char bel` rather than `ansi st` as string terminator).",
79                example: "term query $'(ansi osc)10;?(char bel)' --prefix $'(ansi osc)10;' --terminator (char bel)",
80                result: None,
81            },
82            Example {
83                description: "Read clipboard content on terminals supporting OSC-52.",
84                example: "term query $'(ansi osc)52;c;?(ansi st)' --prefix $'(ansi osc)52;c;' --terminator (ansi st)",
85                result: None,
86            },
87        ]
88    }
89
90    fn run(
91        &self,
92        engine_state: &EngineState,
93        stack: &mut Stack,
94        call: &Call,
95        _input: PipelineData,
96    ) -> Result<PipelineData, ShellError> {
97        let query: Vec<u8> = call.req(engine_state, stack, 0)?;
98        let keep = call.has_flag(engine_state, stack, "keep")?;
99        let prefix: Option<Vec<u8>> = call.get_flag(engine_state, stack, "prefix")?;
100        let prefix = prefix.unwrap_or_default();
101        let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?;
102
103        let _raw_mode = RawModeGuard::acquire(stack, call.head)?;
104
105        // clear terminal events
106        while crossterm::event::poll(Duration::from_secs(0))
107            .map_err(|err| IoError::new(err, call.head, None))?
108        {
109            // If there's an event, read it to remove it from the queue
110            let _ = crossterm::event::read().map_err(|err| IoError::new(err, call.head, None))?;
111        }
112
113        let mut b = [0u8; 1];
114        let mut buf = vec![];
115        let mut stdin = std::io::stdin().lock();
116
117        {
118            let mut stdout = std::io::stdout().lock();
119            stdout
120                .write_all(&query)
121                .map_err(|err| IoError::new(err, call.head, None))?;
122            stdout
123                .flush()
124                .map_err(|err| IoError::new(err, call.head, None))?;
125        }
126
127        // Validate and skip prefix
128        for bc in prefix {
129            stdin
130                .read_exact(&mut b)
131                .map_err(|err| IoError::new(err, call.head, None))?;
132            if b[0] != bc {
133                return Err(ShellError::Generic(
134                    GenericError::new_internal("Input did not begin with expected sequence", "")
135                        .with_help("Try running without `--prefix` and inspecting the output."),
136                ));
137            }
138            if keep {
139                buf.push(b[0]);
140            }
141        }
142
143        if let Some(terminator) = terminator {
144            loop {
145                stdin
146                    .read_exact(&mut b)
147                    .map_err(|err| IoError::new(err, call.head, None))?;
148
149                if b[0] == CTRL_C {
150                    return Err(ShellError::Interrupted { span: call.head });
151                }
152
153                buf.push(b[0]);
154
155                if buf.ends_with(&terminator) {
156                    if !keep {
157                        // Remove terminator
158                        buf.drain((buf.len() - terminator.len())..);
159                    }
160                    break;
161                }
162            }
163        } else {
164            loop {
165                stdin
166                    .read_exact(&mut b)
167                    .map_err(|err| IoError::new(err, call.head, None))?;
168
169                if b[0] == CTRL_C {
170                    break;
171                }
172
173                buf.push(b[0]);
174            }
175        };
176
177        Ok(Value::binary(buf, call.head).into_pipeline_data())
178    }
179}