From e11a03078008afcaeeecb48b86d13c73a55d45d1 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 23 Jan 2022 16:09:39 -0600 Subject: [PATCH] capture keyboard event (#832) * capture keyboard event * try a different strategy - still not working right * fixed up --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/input_keys.rs | 151 +++++++++++++++++++ crates/nu-command/src/platform/mod.rs | 2 + 3 files changed, 154 insertions(+) create mode 100644 crates/nu-command/src/platform/input_keys.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 74c10310..cf67a2a0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -174,6 +174,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { AnsiStrip, Clear, Input, + InputKeys, Kill, Sleep, TermSize, diff --git a/crates/nu-command/src/platform/input_keys.rs b/crates/nu-command/src/platform/input_keys.rs new file mode 100644 index 00000000..bd50d278 --- /dev/null +++ b/crates/nu-command/src/platform/input_keys.rs @@ -0,0 +1,151 @@ +use crossterm::QueueableCommand; +use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value, +}; +use std::io::{stdout, Write}; + +#[derive(Clone)] +pub struct InputKeys; + +impl Command for InputKeys { + fn name(&self) -> &str { + "input-keys" + } + + fn usage(&self) -> &str { + "Get input from the user." + } + + fn signature(&self) -> Signature { + Signature::build("input-keys").category(Category::Platform) + } + + fn run( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + _call: &Call, + _input: PipelineData, + ) -> Result { + println!("Type any key combination to see key details. Press ESC to abort."); + + match print_events(stack) { + Ok(v) => Ok(v.into_pipeline_data()), + Err(e) => { + terminal::disable_raw_mode()?; + Err(ShellError::LabeledError( + "Error with input".to_string(), + e.to_string(), + )) + } + } + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Type and see key event codes", + example: "input-keys", + result: None, + }] + } +} + +pub fn print_events(stack: &mut Stack) -> Result { + let config = stack.get_config()?; + + stdout().flush()?; + terminal::enable_raw_mode()?; + let mut stdout = std::io::BufWriter::new(std::io::stderr()); + + loop { + let event = crossterm::event::read()?; + if event == Event::Key(KeyCode::Esc.into()) { + break; + } + // stdout.queue(crossterm::style::Print(format!("event: {:?}", &event)))?; + // stdout.queue(crossterm::style::Print("\r\n"))?; + + // Get a record + let v = print_events_helper(event)?; + // Print out the record + let o = match v { + Value::Record { cols, vals, .. } => cols + .iter() + .zip(vals.iter()) + .map(|(x, y)| format!("{}: {}", x, y.into_string("", &config))) + .collect::>() + .join(", "), + + _ => "".to_string(), + }; + stdout.queue(crossterm::style::Print(o))?; + stdout.queue(crossterm::style::Print("\r\n"))?; + stdout.flush()?; + } + terminal::disable_raw_mode()?; + + Ok(Value::nothing(Span::test_data())) +} + +// this fn is totally ripped off from crossterm's examples +// it's really a diagnostic routine to see if crossterm is +// even seeing the events. if you press a key and no events +// are printed, it's a good chance your terminal is eating +// those events. +fn print_events_helper(event: Event) -> Result { + if let Event::Key(KeyEvent { code, modifiers }) = event { + match code { + KeyCode::Char(c) => { + let record = Value::Record { + cols: vec![ + "char".into(), + "code".into(), + "modifier".into(), + "flags".into(), + ], + vals: vec![ + Value::string(format!("{}", c), Span::test_data()), + Value::string(format!("{:#08x}", u32::from(c)), Span::test_data()), + Value::string(format!("{:?}", modifiers), Span::test_data()), + Value::string(format!("{:#08b}", modifiers), Span::test_data()), + ], + span: Span::test_data(), + }; + Ok(record) + } + _ => { + let record = Value::Record { + cols: vec!["code".into(), "modifier".into(), "flags".into()], + vals: vec![ + Value::string(format!("{:?}", code), Span::test_data()), + Value::string(format!("{:?}", modifiers), Span::test_data()), + Value::string(format!("{:#08b}", modifiers), Span::test_data()), + ], + span: Span::test_data(), + }; + Ok(record) + } + } + } else { + let record = Value::Record { + cols: vec!["event".into()], + vals: vec![Value::string(format!("{:?}", event), Span::test_data())], + span: Span::test_data(), + }; + Ok(record) + } +} + +#[cfg(test)] +mod tests { + use super::InputKeys; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(InputKeys {}) + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index d18209ba..af8954c5 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,6 +1,7 @@ mod ansi; mod clear; mod input; +mod input_keys; mod kill; mod sleep; mod term_size; @@ -8,6 +9,7 @@ mod term_size; pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; pub use input::Input; +pub use input_keys::InputKeys; pub use kill::Kill; pub use sleep::Sleep; pub use term_size::TermSize;