mirror of
https://github.com/nushell/nushell
synced 2025-01-12 21:29:07 +00:00
Add term query
, for querying information from terminals. (#14427)
## Related - #10150 - https://github.com/nushell/nushell/pull/10150#issuecomment-1721238336 - #10387 - https://github.com/nushell/nushell/pull/10387#issuecomment-1722228185 # Description `term query`: a command for querying information from the terminal. Prints the `$query`, and immediately starts reading raw bytes from stdin. The standard input will be read until the `terminator` sequence is encountered. The `terminator` is not removed from the output. It also stops on <kbd>Ctrl-C</kbd> with an error. ``` Usage: > term query {flags} <query> Flags: -h, --help: Display the help message for this command -t, --terminator (required parameter) <one_of(binary, string)>: stdin will be read until this sequence is encountered Parameters: query <one_of(binary, string)>: The query that will be printed to stdout ``` This was previously possible with `input` until #10150. `input` command's features such as cursor control, deleting input etc. are useful, but interfere with this use case. `term query` makes the following uses possible: ```nushell # get the terminal size with ansi escape codes def terminal-size [] { let response = term query (ansi size) --terminator 'R' # $response should look like this # Length: 9 (0x9) bytes | printable whitespace ascii_other non_ascii # 00000000: 1b 5b 33 38 3b 31 35 30 52 •[38;150R let sz = $response | bytes at 2..<-1 | decode # 38;150 # $sz should look like 38;150 let size = ($sz | split row ';' | each {into int}) # output in record syntax { rows: $size.0 columns: $size.1 } } ``` ```nushell # read clipboard content using OSC 52 term query $"(ansi --osc '52;c;?')(ansi st)" --terminator (ansi st) | bytes at 7..<-2 | decode | decode base64 | decode ``` # User-Facing Changes - added `ansi query` # Tests + Formatting - Integration tests should be added if possible.
This commit is contained in:
parent
4d3283e235
commit
32196cfe78
6 changed files with 182 additions and 2 deletions
|
@ -248,7 +248,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||
IsTerminal,
|
||||
Kill,
|
||||
Sleep,
|
||||
Term,
|
||||
TermSize,
|
||||
TermQuery,
|
||||
Whoami,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ mod input;
|
|||
mod is_terminal;
|
||||
mod kill;
|
||||
mod sleep;
|
||||
mod term_size;
|
||||
mod term;
|
||||
#[cfg(unix)]
|
||||
mod ulimit;
|
||||
mod whoami;
|
||||
|
@ -19,7 +19,7 @@ pub use input::InputListen;
|
|||
pub use is_terminal::IsTerminal;
|
||||
pub use kill::Kill;
|
||||
pub use sleep::Sleep;
|
||||
pub use term_size::TermSize;
|
||||
pub use term::{Term, TermQuery, TermSize};
|
||||
#[cfg(unix)]
|
||||
pub use ulimit::ULimit;
|
||||
pub use whoami::Whoami;
|
||||
|
|
7
crates/nu-command/src/platform/term/mod.rs
Normal file
7
crates/nu-command/src/platform/term/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod term_;
|
||||
mod term_query;
|
||||
mod term_size;
|
||||
|
||||
pub use term_::Term;
|
||||
pub use term_query::TermQuery;
|
||||
pub use term_size::TermSize;
|
34
crates/nu-command/src/platform/term/term_.rs
Normal file
34
crates/nu-command/src/platform/term/term_.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use nu_engine::{command_prelude::*, get_full_help};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Term;
|
||||
|
||||
impl Command for Term {
|
||||
fn name(&self) -> &str {
|
||||
"term"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("term")
|
||||
.category(Category::Platform)
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Commands for querying information about the terminal."
|
||||
}
|
||||
|
||||
fn extra_description(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
137
crates/nu-command/src/platform/term/term_query.rs
Normal file
137
crates/nu-command/src/platform/term/term_query.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use std::{
|
||||
io::{Read, Write},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
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 removed from the output.
|
||||
|
||||
If `terminator` is not supplied, input will be read until Ctrl-C is pressed."
|
||||
}
|
||||
|
||||
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(
|
||||
"terminator",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
|
||||
"Terminator sequence for the expected reply.",
|
||||
Some('t'),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get cursor position.",
|
||||
example: r#"term query (ansi cursor_position) --terminator 'R'"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Get terminal background color.",
|
||||
example: r#"term query $'(ansi osc)10;?(ansi st)' --terminator (ansi st)"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Read clipboard content on terminals supporting OSC-52.",
|
||||
example: r#"term query $'(ansi osc)52;c;?(ansi st)' --terminator (ansi st)"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let query: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?;
|
||||
|
||||
crossterm::terminal::enable_raw_mode()?;
|
||||
|
||||
// clear terminal events
|
||||
while crossterm::event::poll(Duration::from_secs(0))? {
|
||||
// If there's an event, read it to remove it from the queue
|
||||
let _ = crossterm::event::read()?;
|
||||
}
|
||||
|
||||
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)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
let out = if let Some(terminator) = terminator {
|
||||
loop {
|
||||
if let Err(err) = stdin.read_exact(&mut b) {
|
||||
break Err(ShellError::from(err));
|
||||
}
|
||||
|
||||
if b[0] == CTRL_C {
|
||||
break Err(ShellError::Interrupted { span: call.head });
|
||||
}
|
||||
|
||||
buf.push(b[0]);
|
||||
|
||||
if buf.ends_with(&terminator) {
|
||||
break Ok(Value::Binary {
|
||||
val: buf,
|
||||
internal_span: call.head,
|
||||
}
|
||||
.into_pipeline_data());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loop {
|
||||
if let Err(err) = stdin.read_exact(&mut b) {
|
||||
break Err(ShellError::from(err));
|
||||
}
|
||||
|
||||
if b[0] == CTRL_C {
|
||||
break Ok(Value::Binary {
|
||||
val: buf,
|
||||
internal_span: call.head,
|
||||
}
|
||||
.into_pipeline_data());
|
||||
}
|
||||
|
||||
buf.push(b[0]);
|
||||
}
|
||||
};
|
||||
crossterm::terminal::disable_raw_mode()?;
|
||||
out
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue