Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions embedded-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ macros = ["embedded-cli-macros"]
autocomplete = ["embedded-cli-macros/autocomplete"]
help = ["embedded-cli-macros/help"]
history = []
del_is_bs = []

[dependencies]
embedded-cli-macros = { version = "0.2.1", path = "../embedded-cli-macros", optional = true }
Expand Down
6 changes: 6 additions & 0 deletions embedded-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ where
self.writer.flush_bytes(codes::DELETE_CHAR)?;
}
}
ControlInput::Delete => {
if editor.cursor() < editor.len() {
editor.remove();
self.writer.flush_bytes(codes::DELETE_CHAR)?;
}
}
ControlInput::Down =>
{
#[cfg(feature = "history")]
Expand Down
1 change: 1 addition & 0 deletions embedded-cli/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub const TABULATION: u8 = 0x09;
pub const LINE_FEED: u8 = 0x0A;
pub const CARRIAGE_RETURN: u8 = 0x0D;
pub const ESCAPE: u8 = 0x1B;
pub const DELETE: u8 = 0x7F;

pub const CRLF: &str = "\r\n";

Expand Down
39 changes: 32 additions & 7 deletions embedded-cli/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{codes, utf8::Utf8Accum};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ControlInput {
Backspace,
Delete,
Down,
Enter,
Back,
Expand Down Expand Up @@ -50,7 +51,7 @@ impl InputGenerator {
let last_byte = self.last_byte;
self.last_byte = byte;
if self.flags.contains(Flags::CSI_STARTED) {
self.process_csi(byte).map(Input::Control)
self.process_csi(byte, last_byte).map(Input::Control)
} else if last_byte == codes::ESCAPE && byte == b'[' {
self.flags.set(Flags::CSI_STARTED, true);
None
Expand All @@ -59,15 +60,16 @@ impl InputGenerator {
}
}

fn process_csi(&mut self, byte: u8) -> Option<ControlInput> {
fn process_csi(&mut self, byte: u8, last_byte: u8) -> Option<ControlInput> {
// skip all parameter bytes and process only last byte in CSI sequence
if (0x40..=0x7E).contains(&byte) {
self.flags.set(Flags::CSI_STARTED, false);
let control = match byte {
b'A' => ControlInput::Up,
b'B' => ControlInput::Down,
b'C' => ControlInput::Forward,
b'D' => ControlInput::Back,
let control = match (last_byte, byte) {
(_, b'A') => ControlInput::Up,
(_, b'B') => ControlInput::Down,
(_, b'C') => ControlInput::Forward,
(_, b'D') => ControlInput::Back,
(b'3', b'~') => ControlInput::Delete,
_ => return None,
};
Some(control)
Expand All @@ -79,6 +81,10 @@ impl InputGenerator {
fn process_single(&mut self, byte: u8, last_byte: u8) -> Option<Input<'_>> {
let control = match byte {
codes::BACKSPACE => ControlInput::Backspace,
#[cfg(feature = "del_is_bs")]
codes::DELETE => ControlInput::Backspace,
#[cfg(not(feature = "del_is_bs"))]
codes::DELETE => ControlInput::Delete,

// ignore \r if \n already received (and converted to Enter)
codes::CARRIAGE_RETURN if last_byte != codes::LINE_FEED => ControlInput::Enter,
Expand Down Expand Up @@ -109,6 +115,7 @@ mod tests {
#[case(b"\x1B[24B", ControlInput::Down)]
#[case(b"\x1B[C", ControlInput::Forward)]
#[case(b"\x1B[D", ControlInput::Back)]
#[case(b"\x1B[3~", ControlInput::Delete)]
fn process_csi_control(#[case] bytes: &[u8], #[case] expected: ControlInput) {
let mut accum = InputGenerator::new();

Expand All @@ -134,6 +141,24 @@ mod tests {
)
}

#[test]
#[cfg(feature = "del_is_bs")]
fn process_delete_as_bs() {
assert_eq!(
InputGenerator::new().accept(crate::codes::DELETE),
Some(Input::Control(ControlInput::Backspace))
)
}

#[test]
#[cfg(not(feature = "del_is_bs"))]
fn process_delete_as_del() {
assert_eq!(
InputGenerator::new().accept(crate::codes::DELETE),
Some(Input::Control(ControlInput::Delete))
)
}

#[test]
fn process_crlf() {
let mut accum = InputGenerator::new();
Expand Down
24 changes: 24 additions & 0 deletions embedded-cli/tests/cli/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ fn delete_with_backspace() {
assert_terminal!(cli.terminal(), 2, vec!["$"]);
}

#[test]
Comment thread
funbiscuit marked this conversation as resolved.
fn delete_with_delete_key() {
let mut cli = CliWrapper::default();

cli.process_str("set");
assert_terminal!(cli.terminal(), 5, vec!["$ set"]);

cli.send_left();
cli.send_left();
assert_terminal!(cli.terminal(), 3, vec!["$ set"]);

// forward delete 'e'
cli.send_delete();
assert_terminal!(cli.terminal(), 3, vec!["$ st"]);

// forward delete 't'
cli.send_delete();
assert_terminal!(cli.terminal(), 3, vec!["$ s"]);

// try forward delete at the end of the editor (should do nothing)
cli.send_delete();
assert_terminal!(cli.terminal(), 3, vec!["$ s"]);
}

#[test]
fn move_insert() {
let mut cli = CliWrapper::default();
Expand Down
4 changes: 4 additions & 0 deletions embedded-cli/tests/cli/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@ impl<T: Autocomplete + Help + CommandConvert + Clone> CliWrapper<T> {
self.process_str("\x08")
}

pub fn send_delete(&mut self) {
self.process_str("\x1B[3~")
}

pub fn send_down(&mut self) {
self.process_str("\x1B[B")
}
Expand Down
Loading