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