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::{generic::GenericError, 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: "term query (ansi cursor_position) --prefix (ansi csi) --terminator 'R'",
69 result: None,
70 },
71 Example {
72 description: "Get terminal background color.",
73 example: "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: "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: "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::Generic(
136 GenericError::new_internal("Input did not begin with expected sequence", "")
137 .with_help("Try running without `--prefix` and inspecting the output."),
138 ));
139 }
140 if keep {
141 buf.push(b[0]);
142 }
143 }
144
145 if let Some(terminator) = terminator {
146 loop {
147 stdin
148 .read_exact(&mut b)
149 .map_err(|err| IoError::new(err, call.head, None))?;
150
151 if b[0] == CTRL_C {
152 return Err(ShellError::Interrupted { span: call.head });
153 }
154
155 buf.push(b[0]);
156
157 if buf.ends_with(&terminator) {
158 if !keep {
159 buf.drain((buf.len() - terminator.len())..);
161 }
162 break;
163 }
164 }
165 } else {
166 loop {
167 stdin
168 .read_exact(&mut b)
169 .map_err(|err| IoError::new(err, call.head, None))?;
170
171 if b[0] == CTRL_C {
172 break;
173 }
174
175 buf.push(b[0]);
176 }
177 };
178
179 Ok(Value::binary(buf, call.head).into_pipeline_data())
180 }
181}