Properly echo control characters and tabs
This commit is contained in:
parent
aa6e302baa
commit
b1ca35040d
14
echo_erase_kill_behavior
Normal file
14
echo_erase_kill_behavior
Normal file
@ -0,0 +1,14 @@
|
||||
echoctl: Control echoes give literal ^ + printable
|
||||
-echoctl: Control echoes give raw control character
|
||||
|
||||
-echoe -echoprt: Backspace gives ^?
|
||||
echoe -echoprt: Backspace gives character erase
|
||||
(-)echoe echoprt: Backspace gives \ if last press wasn't Backspce, then erased characters, and / when non-backspace pressed or end of line erased
|
||||
|
||||
|
||||
crtkill (-)echoe -echok: Kill gives ^U
|
||||
crtkill -echoe echok: Kill gives ^U and newline (N_TTY comment says no newline, bug?)
|
||||
crtkill echoe echok: Kill erases line with backspace
|
||||
|
||||
-crtkill -echok: Kill gives ^U
|
||||
-crtkill echok: Kill gives ^U and newline
|
126
src/main.rs
126
src/main.rs
@ -6,7 +6,7 @@ use std::{
|
||||
os::mikros::{ipc, syscalls},
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
@ -20,6 +20,7 @@ struct Pty {
|
||||
output_buffer: Mutex<Vec<u8>>,
|
||||
termios: Termios,
|
||||
output_paused: AtomicBool,
|
||||
column: AtomicUsize,
|
||||
}
|
||||
|
||||
impl Pty {
|
||||
@ -29,6 +30,7 @@ impl Pty {
|
||||
output_buffer: Mutex::new(Vec::new()),
|
||||
termios: Termios::default(),
|
||||
output_paused: AtomicBool::new(false),
|
||||
column: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,43 +49,60 @@ impl Pty {
|
||||
if !self.termios.cflags.cread {
|
||||
return Ok(());
|
||||
}
|
||||
let mut input_buffer = self.input_buffer.lock();
|
||||
let mut output_buffer = self.output_buffer.lock();
|
||||
for &(mut byte) in data {
|
||||
for &(mut ch) in data {
|
||||
if self.termios.iflags.istrip {
|
||||
byte &= 0x7F;
|
||||
ch &= 0x7F;
|
||||
}
|
||||
if byte == b'\r' && self.termios.iflags.igncr {
|
||||
if ch == b'\r' && self.termios.iflags.igncr {
|
||||
continue;
|
||||
} else if byte == b'\r' && self.termios.iflags.icrnl {
|
||||
byte = b'\n';
|
||||
} else if byte == b'\n' && self.termios.iflags.inlcr {
|
||||
byte = b'\r';
|
||||
} else if ch == b'\r' && self.termios.iflags.icrnl {
|
||||
ch = b'\n';
|
||||
} else if ch == b'\n' && self.termios.iflags.inlcr {
|
||||
ch = b'\r';
|
||||
}
|
||||
if byte as char == self.termios.verase && self.termios.lflags.icanon {
|
||||
if self.termios.lflags.echoe {
|
||||
output_buffer.push(byte);
|
||||
}
|
||||
if input_buffer.last() != Some(&b'\n') {
|
||||
input_buffer.pop();
|
||||
}
|
||||
} else if byte as char == self.termios.vkill && self.termios.lflags.icanon {
|
||||
while input_buffer.last().is_some() && input_buffer.last() != Some(&b'\n') {
|
||||
if self.termios.lflags.echok {
|
||||
output_buffer.push(8);
|
||||
if ch as char == self.termios.verase && self.termios.lflags.icanon {
|
||||
let buf_last = self.input_buffer.lock().last().copied();
|
||||
if let Some(erased_ch) = buf_last {
|
||||
let erased_ch = erased_ch as char;
|
||||
if erased_ch != '\n' {
|
||||
self.input_buffer.lock().pop();
|
||||
self.erase_char(erased_ch);
|
||||
}
|
||||
input_buffer.pop();
|
||||
}
|
||||
} else if byte as char == self.termios.vstop && self.termios.iflags.ixon {
|
||||
} else if ch as char == self.termios.vkill && self.termios.lflags.icanon {
|
||||
let buf_last = self.input_buffer.lock().last().copied();
|
||||
if let Some(erased_ch) = buf_last {
|
||||
let erased_ch = erased_ch as char;
|
||||
if erased_ch != '\n' {
|
||||
self.input_buffer.lock().pop();
|
||||
self.erase_char(erased_ch);
|
||||
}
|
||||
}
|
||||
} else if ch as char == self.termios.vstop && self.termios.iflags.ixon {
|
||||
self.output_paused.store(true, Ordering::Relaxed);
|
||||
} else if (byte as char == self.termios.vstart && self.termios.iflags.ixon)
|
||||
} else if (ch as char == self.termios.vstart && self.termios.iflags.ixon)
|
||||
|| (self.termios.iflags.ixany && self.output_paused.load(Ordering::Relaxed))
|
||||
{
|
||||
self.output_paused.store(false, Ordering::Relaxed);
|
||||
} else {
|
||||
input_buffer.push(byte);
|
||||
if self.termios.lflags.echo || (byte == b'\n' && self.termios.lflags.echonl) {
|
||||
output_buffer.push(byte);
|
||||
self.input_buffer.lock().push(ch);
|
||||
if self.termios.lflags.echo || (ch == b'\n' && self.termios.lflags.echonl) {
|
||||
let mut output_buffer = self.output_buffer.lock();
|
||||
if Self::is_nonprinting_control(ch as char) {
|
||||
output_buffer.push(b'^');
|
||||
output_buffer.push(ch + b'@');
|
||||
self.column.fetch_add(2, Ordering::Relaxed);
|
||||
} else if ch == b'\t' {
|
||||
let curr_col = self.column.load(Ordering::Relaxed);
|
||||
let num_cols = 8 - (curr_col % 8);
|
||||
for _ in 0..num_cols {
|
||||
output_buffer.push(b' ');
|
||||
}
|
||||
self.column.fetch_add(num_cols, Ordering::Relaxed);
|
||||
} else {
|
||||
output_buffer.push(ch);
|
||||
self.column.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,17 +127,68 @@ impl Pty {
|
||||
for &byte in data {
|
||||
if self.termios.oflags.opost {
|
||||
if byte == b'\n' && self.termios.oflags.onlcr {
|
||||
output_buffer.push(b'\r');
|
||||
if self.column.load(Ordering::Relaxed) != 0 {
|
||||
output_buffer.push(b'\r');
|
||||
}
|
||||
output_buffer.push(b'\n');
|
||||
self.column.store(0, Ordering::Relaxed);
|
||||
} else if byte == b'\r' && self.termios.oflags.ocrnl {
|
||||
output_buffer.push(b'\n');
|
||||
if self.termios.oflags.onlret {
|
||||
self.column.store(0, Ordering::Relaxed);
|
||||
}
|
||||
} else {
|
||||
output_buffer.push(byte);
|
||||
}
|
||||
} else if (byte == b'\n' && self.termios.oflags.onlret) || byte == b'\r' {
|
||||
self.column.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_nonprinting_control(ch: char) -> bool {
|
||||
ch.is_ascii_control() && ch != '\r' && ch != '\n' && ch != '\t'
|
||||
}
|
||||
|
||||
fn erase_char(&self, ch: char) {
|
||||
if !self.termios.lflags.echoe {
|
||||
return;
|
||||
}
|
||||
let mut output_buffer = self.output_buffer.lock();
|
||||
if Self::is_nonprinting_control(ch) {
|
||||
output_buffer.push(8);
|
||||
output_buffer.push(8);
|
||||
self.column.fetch_sub(2, Ordering::Relaxed);
|
||||
} else if ch == '\t' {
|
||||
let curr_col = self.column.load(Ordering::Relaxed);
|
||||
let mut before_tab_cols = 0;
|
||||
let mut after_tab = false;
|
||||
let input_buffer = self.input_buffer.lock();
|
||||
for &byte in input_buffer.iter().rev() {
|
||||
if Self::is_nonprinting_control(byte as char) {
|
||||
before_tab_cols += 2;
|
||||
} else if byte == b'\t' {
|
||||
after_tab = true;
|
||||
break;
|
||||
} else {
|
||||
before_tab_cols += 1;
|
||||
}
|
||||
}
|
||||
let num_cols = if after_tab {
|
||||
8 - before_tab_cols
|
||||
} else {
|
||||
curr_col - before_tab_cols
|
||||
};
|
||||
for _ in 0..num_cols {
|
||||
output_buffer.push(8);
|
||||
}
|
||||
self.column.fetch_sub(num_cols, Ordering::Relaxed);
|
||||
} else {
|
||||
output_buffer.push(8);
|
||||
self.column.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
Loading…
Reference in New Issue
Block a user