Initial commit
This commit is contained in:
commit
ee1b595b81
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
30
Cargo.lock
generated
Normal file
30
Cargo.lock
generated
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "line_discipline"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"itertools",
|
||||||
|
"termios",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termios"
|
||||||
|
version = "0.1.0"
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "line_discipline"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
itertools = "0.13.0"
|
||||||
|
termios = { version = "0.1.0", path = "../termios" }
|
182
src/lib.rs
Normal file
182
src/lib.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
pub use termios::Termios;
|
||||||
|
|
||||||
|
pub struct LineDiscipline {
|
||||||
|
input_buffer: Vec<u8>,
|
||||||
|
output_buffer: Vec<u8>,
|
||||||
|
termios: Termios,
|
||||||
|
output_paused: bool,
|
||||||
|
column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LineDiscipline {
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
LineDiscipline {
|
||||||
|
input_buffer: Vec::new(),
|
||||||
|
output_buffer: Vec::new(),
|
||||||
|
termios: Termios::default(),
|
||||||
|
output_paused: false,
|
||||||
|
column: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_terminal_input(&mut self, data: &[u8]) {
|
||||||
|
if !self.termios.cflags.cread {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for &(mut ch) in data {
|
||||||
|
if self.termios.iflags.istrip {
|
||||||
|
ch &= 0x7F;
|
||||||
|
}
|
||||||
|
if ch == b'\r' && self.termios.iflags.igncr {
|
||||||
|
continue;
|
||||||
|
} 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 ch as char == self.termios.verase && self.termios.lflags.icanon {
|
||||||
|
let buf_last = self.input_buffer.last().copied();
|
||||||
|
if let Some(erased_ch) = buf_last {
|
||||||
|
let erased_ch = erased_ch as char;
|
||||||
|
if erased_ch != '\n' {
|
||||||
|
self.input_buffer.pop();
|
||||||
|
self.erase_char(erased_ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ch as char == self.termios.vkill && self.termios.lflags.icanon {
|
||||||
|
let buf_last = self.input_buffer.last().copied();
|
||||||
|
while let Some(erased_ch) = buf_last {
|
||||||
|
let erased_ch = erased_ch as char;
|
||||||
|
if erased_ch != '\n' {
|
||||||
|
self.input_buffer.pop();
|
||||||
|
self.erase_char(erased_ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ch as char == self.termios.vstop && self.termios.iflags.ixon {
|
||||||
|
self.output_paused = true;
|
||||||
|
} else if (ch as char == self.termios.vstart && self.termios.iflags.ixon)
|
||||||
|
|| (self.termios.iflags.ixany && self.output_paused)
|
||||||
|
{
|
||||||
|
self.output_paused = false;
|
||||||
|
} else {
|
||||||
|
self.input_buffer.push(ch);
|
||||||
|
if self.termios.lflags.echo || (ch == b'\n' && self.termios.lflags.echonl) {
|
||||||
|
if Self::is_nonprinting_control(ch as char) {
|
||||||
|
self.output_buffer.push(b'^');
|
||||||
|
self.output_buffer.push(ch + b'@');
|
||||||
|
self.column += 2;
|
||||||
|
} else if ch == b'\t' {
|
||||||
|
let curr_col = self.column;
|
||||||
|
let num_cols = 8 - (curr_col % 8);
|
||||||
|
for _ in 0..num_cols {
|
||||||
|
self.output_buffer.push(b' ');
|
||||||
|
}
|
||||||
|
self.column += num_cols;
|
||||||
|
} else {
|
||||||
|
self.output_buffer.push(ch);
|
||||||
|
self.column += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_program_output(&mut self, data: &[u8]) {
|
||||||
|
for &byte in data {
|
||||||
|
if self.termios.oflags.opost {
|
||||||
|
if byte == b'\n' && self.termios.oflags.onlcr {
|
||||||
|
if self.column != 0 {
|
||||||
|
self.output_buffer.push(b'\r');
|
||||||
|
}
|
||||||
|
self.output_buffer.push(b'\n');
|
||||||
|
self.column = 0;
|
||||||
|
} else if byte == b'\r' && self.termios.oflags.ocrnl {
|
||||||
|
self.output_buffer.push(b'\n');
|
||||||
|
if self.termios.oflags.onlret {
|
||||||
|
self.column = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.output_buffer.push(byte);
|
||||||
|
}
|
||||||
|
} else if (byte == b'\n' && self.termios.oflags.onlret) || byte == b'\r' {
|
||||||
|
self.column = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_terminal_output(&mut self) -> Vec<u8> {
|
||||||
|
if self.output_paused {
|
||||||
|
Vec::new()
|
||||||
|
} else {
|
||||||
|
std::mem::take(&mut self.output_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_program_input(&mut self) -> Vec<u8> {
|
||||||
|
if self.termios.lflags.icanon {
|
||||||
|
if let Some(last_full_line) = self.input_buffer.iter().positions(|&x| x == b'\n').last() {
|
||||||
|
let rem_buf = self.input_buffer.split_off(last_full_line + 1);
|
||||||
|
std::mem::replace(&mut self.input_buffer, rem_buf)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::mem::take(&mut self.input_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_nonprinting_control(ch: char) -> bool {
|
||||||
|
ch.is_ascii_control() && ch != '\r' && ch != '\n' && ch != '\t'
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn termios(&self) -> &Termios {
|
||||||
|
&self.termios
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_termios(&mut self, termios: Termios) {
|
||||||
|
self.termios = termios;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn erase_char(&mut self, ch: char) {
|
||||||
|
if !self.termios.lflags.echoe {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if Self::is_nonprinting_control(ch) {
|
||||||
|
self.output_buffer.push(8);
|
||||||
|
self.output_buffer.push(8);
|
||||||
|
self.column -= 2;
|
||||||
|
} else if ch == '\t' {
|
||||||
|
let curr_col = self.column;
|
||||||
|
let mut before_tab_cols = 0;
|
||||||
|
let mut after_tab = false;
|
||||||
|
for &byte in self.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 {
|
||||||
|
self.output_buffer.push(8);
|
||||||
|
}
|
||||||
|
self.column -= num_cols;
|
||||||
|
} else {
|
||||||
|
self.output_buffer.push(8);
|
||||||
|
self.column -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output_paused(&self) -> bool {
|
||||||
|
self.output_paused
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user