Properly echo control characters and tabs

This commit is contained in:
pjht 2024-09-19 19:49:10 -05:00
parent aa6e302baa
commit b1ca35040d
Signed by: pjht
GPG Key ID: 7B5F6AFBEC7EE78E
2 changed files with 113 additions and 29 deletions

14
echo_erase_kill_behavior Normal file
View 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

View File

@ -6,7 +6,7 @@ use std::{
os::mikros::{ipc, syscalls}, os::mikros::{ipc, syscalls},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, AtomicUsize, Ordering},
Arc, Arc,
}, },
}; };
@ -20,6 +20,7 @@ struct Pty {
output_buffer: Mutex<Vec<u8>>, output_buffer: Mutex<Vec<u8>>,
termios: Termios, termios: Termios,
output_paused: AtomicBool, output_paused: AtomicBool,
column: AtomicUsize,
} }
impl Pty { impl Pty {
@ -29,6 +30,7 @@ impl Pty {
output_buffer: Mutex::new(Vec::new()), output_buffer: Mutex::new(Vec::new()),
termios: Termios::default(), termios: Termios::default(),
output_paused: AtomicBool::new(false), output_paused: AtomicBool::new(false),
column: AtomicUsize::new(0),
} }
} }
@ -47,43 +49,60 @@ impl Pty {
if !self.termios.cflags.cread { if !self.termios.cflags.cread {
return Ok(()); return Ok(());
} }
let mut input_buffer = self.input_buffer.lock(); for &(mut ch) in data {
let mut output_buffer = self.output_buffer.lock();
for &(mut byte) in data {
if self.termios.iflags.istrip { 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; continue;
} else if byte == b'\r' && self.termios.iflags.icrnl { } else if ch == b'\r' && self.termios.iflags.icrnl {
byte = b'\n'; ch = b'\n';
} else if byte == b'\n' && self.termios.iflags.inlcr { } else if ch == b'\n' && self.termios.iflags.inlcr {
byte = b'\r'; ch = b'\r';
} }
if byte as char == self.termios.verase && self.termios.lflags.icanon { if ch as char == self.termios.verase && self.termios.lflags.icanon {
if self.termios.lflags.echoe { let buf_last = self.input_buffer.lock().last().copied();
output_buffer.push(byte); 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);
} }
if input_buffer.last() != Some(&b'\n') {
input_buffer.pop();
} }
} else if byte as char == self.termios.vkill && self.termios.lflags.icanon { } else if ch as char == self.termios.vkill && self.termios.lflags.icanon {
while input_buffer.last().is_some() && input_buffer.last() != Some(&b'\n') { let buf_last = self.input_buffer.lock().last().copied();
if self.termios.lflags.echok { if let Some(erased_ch) = buf_last {
output_buffer.push(8); 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.vstop && self.termios.iflags.ixon {
self.output_paused.store(true, Ordering::Relaxed); 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.termios.iflags.ixany && self.output_paused.load(Ordering::Relaxed))
{ {
self.output_paused.store(false, Ordering::Relaxed); self.output_paused.store(false, Ordering::Relaxed);
} else { } else {
input_buffer.push(byte); self.input_buffer.lock().push(ch);
if self.termios.lflags.echo || (byte == b'\n' && self.termios.lflags.echonl) { if self.termios.lflags.echo || (ch == b'\n' && self.termios.lflags.echonl) {
output_buffer.push(byte); 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 { for &byte in data {
if self.termios.oflags.opost { if self.termios.oflags.opost {
if byte == b'\n' && self.termios.oflags.onlcr { if byte == b'\n' && self.termios.oflags.onlcr {
if self.column.load(Ordering::Relaxed) != 0 {
output_buffer.push(b'\r'); output_buffer.push(b'\r');
}
output_buffer.push(b'\n'); output_buffer.push(b'\n');
self.column.store(0, Ordering::Relaxed);
} else if byte == b'\r' && self.termios.oflags.ocrnl { } else if byte == b'\r' && self.termios.oflags.ocrnl {
output_buffer.push(b'\n'); output_buffer.push(b'\n');
if self.termios.oflags.onlret {
self.column.store(0, Ordering::Relaxed);
}
} else { } else {
output_buffer.push(byte); output_buffer.push(byte);
} }
} else if (byte == b'\n' && self.termios.oflags.onlret) || byte == b'\r' {
self.column.store(0, Ordering::Relaxed);
} }
} }
Ok(()) 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)] #[derive(Clone)]