nu_command/platform/term/
term_query.rs1use 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 while crossterm::event::poll(Duration::from_secs(0))
107 .map_err(|err| IoError::new(err, call.head, None))?
108 {
109 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 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 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}