use std::{ io::{Read, Write}, time::Duration, }; use nu_engine::command_prelude::*; use nu_protocol::shell_error::io::IoError; const CTRL_C: u8 = 3; #[derive(Clone)] pub struct TermQuery; impl Command for TermQuery { fn name(&self) -> &str { "term query" } fn description(&self) -> &str { "Query the terminal for information." } fn extra_description(&self) -> &str { "Print the given query, and read the immediate result from stdin. The standard input will be read right after `query` is printed, and consumed until the `terminator` sequence is encountered. The `terminator` is not included in the output. If `terminator` is not supplied, input will be read until Ctrl-C is pressed. If `prefix` is supplied, input's initial bytes will be validated against it. The `prefix` is not included in the output." } fn signature(&self) -> Signature { Signature::build("term query") .category(Category::Platform) .input_output_types(vec![(Type::Nothing, Type::Binary)]) .allow_variants_without_examples(true) .required( "query", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), "The query that will be printed to stdout.", ) .named( "prefix", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), "Prefix sequence for the expected reply.", Some('p'), ) .named( "terminator", SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), "Terminator sequence for the expected reply.", Some('t'), ) .switch( "keep", "Include prefix and terminator in the output.", Some('k'), ) } fn examples(&self) -> Vec { vec![ Example { description: "Get cursor position.", example: r#"term query (ansi cursor_position) --prefix (ansi csi) --terminator 'R'"#, result: None, }, Example { description: "Get terminal background color.", example: r#"term query $'(ansi osc)10;?(ansi st)' --prefix $'(ansi osc)10;' --terminator (ansi st)"#, result: None, }, Example { description: "Get terminal background color. (some terminals prefer `char bel` rather than `ansi st` as string terminator)", example: r#"term query $'(ansi osc)10;?(char bel)' --prefix $'(ansi osc)10;' --terminator (char bel)"#, result: None, }, Example { description: "Read clipboard content on terminals supporting OSC-52.", example: r#"term query $'(ansi osc)52;c;?(ansi st)' --prefix $'(ansi osc)52;c;' --terminator (ansi st)"#, result: None, }, ] } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { let query: Vec = call.req(engine_state, stack, 0)?; let keep = call.has_flag(engine_state, stack, "keep")?; let prefix: Option> = call.get_flag(engine_state, stack, "prefix")?; let prefix = prefix.unwrap_or_default(); let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; crossterm::terminal::enable_raw_mode() .map_err(|err| IoError::new(err.kind(), call.head, None))?; scopeguard::defer! { let _ = crossterm::terminal::disable_raw_mode(); } // clear terminal events while crossterm::event::poll(Duration::from_secs(0)) .map_err(|err| IoError::new(err.kind(), call.head, None))? { // If there's an event, read it to remove it from the queue let _ = crossterm::event::read() .map_err(|err| IoError::new(err.kind(), call.head, None))?; } let mut b = [0u8; 1]; let mut buf = vec![]; let mut stdin = std::io::stdin().lock(); { let mut stdout = std::io::stdout().lock(); stdout .write_all(&query) .map_err(|err| IoError::new(err.kind(), call.head, None))?; stdout .flush() .map_err(|err| IoError::new(err.kind(), call.head, None))?; } // Validate and skip prefix for bc in prefix { stdin .read_exact(&mut b) .map_err(|err| IoError::new(err.kind(), call.head, None))?; if b[0] != bc { return Err(ShellError::GenericError { error: "Input did not begin with expected sequence".into(), msg: "".into(), span: None, help: Some("Try running without `--prefix` and inspecting the output.".into()), inner: vec![], }); } if keep { buf.push(b[0]); } } if let Some(terminator) = terminator { loop { stdin .read_exact(&mut b) .map_err(|err| IoError::new(err.kind(), call.head, None))?; if b[0] == CTRL_C { return Err(ShellError::InterruptedByUser { span: Some(call.head), }); } buf.push(b[0]); if buf.ends_with(&terminator) { if !keep { // Remove terminator buf.drain((buf.len() - terminator.len())..); } break; } } } else { loop { stdin .read_exact(&mut b) .map_err(|err| IoError::new(err.kind(), call.head, None))?; if b[0] == CTRL_C { break; } buf.push(b[0]); } }; Ok(Value::binary(buf, call.head).into_pipeline_data()) } }