Initial commit
This commit is contained in:
commit
53bc25a596
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" }
|
185
src/lib.rs
Normal file
185
src/lib.rs
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user