Initial commit

This commit is contained in:
pjht 2024-09-23 14:44:16 -05:00
commit 53bc25a596
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
4 changed files with 224 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

30
Cargo.lock generated Normal file
View 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
View 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" }

185
src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
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,
}
}
/// Processes input from the terminal and returns data to be read by the program plus any data
/// to be sent (if xon/xoff flow control is used)
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;
}
}
}
}
}
/// Processes output from the program and returns data to write to the terminal
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
}
}