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
|
128
src/main.rs
128
src/main.rs
@ -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 input_buffer.last() != Some(&b'\n') {
|
if erased_ch != '\n' {
|
||||||
input_buffer.pop();
|
self.input_buffer.lock().pop();
|
||||||
}
|
self.erase_char(erased_ch);
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
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);
|
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 {
|
||||||
output_buffer.push(b'\r');
|
if self.column.load(Ordering::Relaxed) != 0 {
|
||||||
|
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)]
|
||||||
@ -180,7 +250,7 @@ impl file_rpc::Server for Serv {
|
|||||||
fn size(&self, _fd: u64) -> Option<u64> {
|
fn size(&self, _fd: u64) -> Option<u64> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dup(&self, fd: u64) -> Option<u64> {
|
fn dup(&self, fd: u64) -> Option<u64> {
|
||||||
Some(fd)
|
Some(fd)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user