diff --git a/src/main.rs b/src/main.rs index 88abb7fb..3ccf6c9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,14 +88,14 @@ pub fn get_terminal_size_using_fd(fd: RawFd) -> Winsize { winsize } -pub fn set_terminal_size_using_fd(fd: RawFd, ws: &Winsize) { +pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) { // TODO: do this with the nix ioctl use libc::ioctl; use libc::TIOCSWINSZ; let winsize = Winsize { - ws_row: ws.ws_row, - ws_col: ws.ws_col, + ws_col: columns, + ws_row: rows, ws_xpixel: 0, ws_ypixel: 0, }; @@ -117,7 +117,7 @@ fn spawn_terminal (ws: &Winsize) -> (RawFd, RawFd) { // TODO: why does $SHELL not work? // Command::new("$SHELL").spawn().expect("failed to spawn"); set_baud_rate(0); - set_terminal_size_using_fd(0, &ws); + set_terminal_size_using_fd(0, ws.ws_col, ws.ws_row); Command::new("/usr/bin/fish").spawn().expect("failed to spawn"); ::std::thread::sleep(std::time::Duration::from_millis(300000)); panic!("I am secondary, why?!"); @@ -133,14 +133,19 @@ fn spawn_terminal (ws: &Winsize) -> (RawFd, RawFd) { (pid_primary, pid_secondary) } +struct TerminalCharacter { + pub character: char, + pub ansi_code: Option, +} + struct TerminalOutput { pub characters: Vec, // we use a vec rather than a string here because one char can take up multiple characters in a string + pub display_rows: u16, + pub display_cols: u16, current_index_in_characters: usize, cursor_position: usize, newline_indices: HashSet, linebreak_indices: HashSet, - display_rows: u16, - display_cols: u16, unhandled_ansi_codes: HashMap, } @@ -157,6 +162,14 @@ impl TerminalOutput { unhandled_ansi_codes: HashMap::new() } } + pub fn reduce_width(&mut self, count: u16) { + self.display_cols -= count; + self.reflow_lines(); + } + pub fn increase_width(&mut self, count: u16) { + self.display_cols += count; + self.reflow_lines(); + } pub fn set_size(&mut self, ws: &Winsize) { let orig_cols = self.display_cols; self.display_rows = ws.ws_row; @@ -178,27 +191,51 @@ impl TerminalOutput { x += 1; } } - pub fn read_buffer_as_lines (&mut self) -> String { - let mut output = String::new(); - - for (i, c) in self.characters.iter().enumerate() { - if self.newline_indices.contains(&i) || self.linebreak_indices.contains(&i) { - output.push('\r'); - output.push('\n'); - } + pub fn read_buffer_as_lines (&mut self) -> Vec { + let mut output: VecDeque = VecDeque::new(); + let mut i = self.characters.len(); + let mut current_line = String::new(); + let mut number_of_control_chars_in_line = 0; + loop { + i -= 1; + let character = self.characters.get(i).unwrap(); + current_line.insert(0, *character); if let Some(code) = self.unhandled_ansi_codes.get(&i) { - output.push_str(code); + current_line.insert_str(0, code); + number_of_control_chars_in_line += code.len(); + } + if self.newline_indices.contains(&i) || self.linebreak_indices.contains(&i) { + // pad line + for _ in current_line.chars().count() - number_of_control_chars_in_line..self.display_cols as usize { + current_line.push(' '); + } + output.push_front(current_line.clone()); + current_line.clear(); + number_of_control_chars_in_line = 0; + } + if i == 0 || output.len() == self.display_rows as usize { + break; } - output.push(*c); } - - self.current_index_in_characters = self.characters.len(); + if output.len() < self.display_rows as usize { + let mut empty_line = String::new(); + for _ in 0..self.display_cols { + empty_line.push(' '); + } + for _ in output.len()..self.display_rows as usize { + output.push_front(empty_line.clone()); + } + } + Vec::from(output) + } + pub fn cursor_position_in_last_line (&self) -> usize { if self.cursor_position < self.characters.len() { let start_of_last_line = self.index_of_beginning_of_last_line(); let difference_from_last_newline = self.cursor_position - start_of_last_line; - output.push_str(&format!("\r\u{1b}[{}C", difference_from_last_newline)); + difference_from_last_newline + } else { + self.display_cols as usize } - output } fn index_of_beginning_of_last_line (&self) -> usize { let last_newline_index = if self.newline_indices.is_empty() { @@ -338,17 +375,19 @@ impl vte::Perform for TerminalOutput { } else if c == 'K' { // clear line (0 => right, 1 => left, 2 => all) if params[0] == 0 { for i in self.cursor_position..self.characters.len() { - self.characters[i] = ' '; self.unhandled_ansi_codes.remove(&i); + // TODO: also remove line breaks? } + self.characters.truncate(self.cursor_position + 1); } // TODO: implement 1 and 2 } else if c == 'J' { // clear all (0 => below, 1 => above, 2 => all, 3 => saved) if params[0] == 0 { for i in self.cursor_position..self.characters.len() { - self.characters[i] = ' '; self.unhandled_ansi_codes.remove(&i); + // TODO: also remove line breaks? } + self.characters.truncate(self.cursor_position + 1); } // TODO: implement 1, 2, and 3 } @@ -400,22 +439,45 @@ fn split_horizontally_with_gap (rect: &Winsize) -> (Winsize, Winsize) { (first_rect, second_rect) } +fn render (terminal1_output: &mut TerminalOutput, terminal2_output: &mut TerminalOutput, full_screen_ws: &Winsize, terminal1_is_active: bool) { + let left_terminal_lines = terminal1_output.read_buffer_as_lines(); + let right_terminal_lines = terminal2_output.read_buffer_as_lines(); + let mut data_lines = String::new(); + for i in 0..full_screen_ws.ws_row { + let mut line = String::new(); + line.push_str(left_terminal_lines.get(i as usize).unwrap()); + line.push_str("\u{1b}[m"); // clear style + line.push('|'); + line.push_str(right_terminal_lines.get(i as usize).unwrap()); + data_lines.push_str(&line); + } + let left_terminal_cursor_position = terminal1_output.cursor_position_in_last_line(); + let right_terminal_cursor_position = terminal2_output.cursor_position_in_last_line(); + print!("\u{1b}c"); // clear screen + if terminal1_is_active { + data_lines.push_str(&format!("\r\u{1b}[{}C", left_terminal_cursor_position)); + } else { + data_lines.push_str(&format!("\r\u{1b}[{}C", right_terminal_cursor_position + (terminal1_output.display_cols + 1) as usize)); + } + ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); + ::std::io::stdout().flush().expect("could not flush"); + // [24;80H +} + fn main() { let mut active_threads = vec![]; // TODO (render two terminals side by side): - // * create 2 fds, each 1/2 the size of the screen (with 1 space in the middle) - DONE - // * join both stdout_handlers into a render loop - // * render loop should read_from_pid until both are None, then it should sleep for 50 ms - // * it should get the line output as a vector of padded lines - // * it should loop through the vector, join line (with 1 space in the middle) and - // print it to screen + // * make switching between terminals work - DONE + // * make resizing terminals work - DONE + // * reset styling in the pipe between terminals - DONE + // * make render loop only render the diff (with goto stuff printed to stdout) <=== TODO: CONTINUE HERE + // print!("\u{1b}[24;80H"); // this moves the cursor to row 24 column 80 let full_screen_ws = get_terminal_size_using_fd(0); let (first_terminal_ws, second_terminal_ws) = split_horizontally_with_gap(&full_screen_ws); let (first_terminal_pid, pid_secondary): (RawFd, RawFd) = spawn_terminal(&first_terminal_ws); let (second_terminal_pid, pid_secondary): (RawFd, RawFd) = spawn_terminal(&second_terminal_ws); let stdin = io::stdin(); - let mut temp_ws = first_terminal_ws.clone(); into_raw_mode(0); set_baud_rate(0); ::std::thread::sleep(std::time::Duration::from_millis(2000)); @@ -432,89 +494,149 @@ fn main() { .name("terminal_stdout_handler".to_string()) .spawn({ - let mut vte_parser = vte::Parser::new(); + let mut vte_parser_terminal1 = vte::Parser::new(); + let mut vte_parser_terminal2 = vte::Parser::new(); + let active_terminal = active_terminal.clone(); let terminal1_output = terminal1_output.clone(); - let first_terminal_ws = first_terminal_ws.clone(); - move || { - let mut buffer_has_unread_data = true; - loop { - match read_from_pid(first_terminal_pid) { - Some(read_bytes) => { - if DEBUGGING { - println!("\n\rread_bytes: {:?}", String::from_utf8(read_bytes.to_vec()).unwrap()); - } - for byte in read_bytes.iter() { - let first_terminal_ws = first_terminal_ws.lock().unwrap(); - let mut terminal1_output = terminal1_output.lock().unwrap(); - terminal1_output.set_size(&*first_terminal_ws); - vte_parser.advance(&mut *terminal1_output, *byte); - } - buffer_has_unread_data = true; - }, - None => { - if DEBUGGING { - buffer_has_unread_data = false; - } else if buffer_has_unread_data { - let mut terminal1_output = terminal1_output.lock().unwrap(); - let data_lines = terminal1_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); - buffer_has_unread_data = false; - } - ::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this - } - } - } - } - }) - .unwrap(), - ); - active_threads.push( - thread::Builder::new() - .name("terminal_stdout_handler2".to_string()) - .spawn({ - - let mut vte_parser = vte::Parser::new(); - let terminal2_output = terminal2_output.clone(); + let first_terminal_ws = first_terminal_ws.clone(); let second_terminal_ws = second_terminal_ws.clone(); move || { let mut buffer_has_unread_data = true; + { + // TODO: better + let first_terminal_ws = first_terminal_ws.lock().unwrap(); + let second_terminal_ws = second_terminal_ws.lock().unwrap(); + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); + terminal1_output.set_size(&first_terminal_ws); + terminal2_output.set_size(&second_terminal_ws); + } loop { - match read_from_pid(second_terminal_pid) { - Some(read_bytes) => { - if DEBUGGING { - println!("\n\rread_bytes: {:?}", String::from_utf8(read_bytes.to_vec()).unwrap()); + match (read_from_pid(first_terminal_pid), read_from_pid(second_terminal_pid)) { + (Some(first_terminal_read_bytes), Some(second_terminal_read_bytes)) => { +// println!("\r first_terminal_read_bytes: {:?}", String::from_utf8(first_terminal_read_bytes).unwrap()); +// ::std::process::exit(2); + let mut terminal1_output = terminal1_output.lock().unwrap(); + for byte in first_terminal_read_bytes.iter() { + vte_parser_terminal1.advance(&mut *terminal1_output, *byte); } - for byte in read_bytes.iter() { - let second_terminal_ws = second_terminal_ws.lock().unwrap(); - let mut terminal2_output = terminal2_output.lock().unwrap(); - terminal2_output.set_size(&*second_terminal_ws); - vte_parser.advance(&mut *terminal2_output, *byte); + let mut terminal2_output = terminal2_output.lock().unwrap(); + for byte in second_terminal_read_bytes.iter() { + vte_parser_terminal2.advance(&mut *terminal2_output, *byte); } buffer_has_unread_data = true; - }, - None => { - if DEBUGGING { - buffer_has_unread_data = false; - } else if buffer_has_unread_data { - let mut terminal2_output = terminal2_output.lock().unwrap(); - let data_lines = terminal2_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); + } + (Some(first_terminal_read_bytes), None) => { + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); + for byte in first_terminal_read_bytes.iter() { + vte_parser_terminal1.advance(&mut *terminal1_output, *byte); + } + let active_terminal = active_terminal.lock().unwrap(); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + // buffer_has_unread_data = true; + } + (None, Some(second_terminal_read_bytes)) => { + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); + for byte in second_terminal_read_bytes.iter() { + vte_parser_terminal2.advance(&mut *terminal2_output, *byte); + } + let active_terminal = active_terminal.lock().unwrap(); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + // buffer_has_unread_data = true; + } + (None, None) => { + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); + if buffer_has_unread_data { + let active_terminal = active_terminal.lock().unwrap(); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); buffer_has_unread_data = false; } ::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this } } + + +// match read_from_pid(first_terminal_pid) { +// Some(read_bytes) => { +// if DEBUGGING { +// println!("\n\rread_bytes: {:?}", String::from_utf8(read_bytes.to_vec()).unwrap()); +// } +// for byte in read_bytes.iter() { +// let first_terminal_ws = first_terminal_ws.lock().unwrap(); +// let mut terminal1_output = terminal1_output.lock().unwrap(); +// terminal1_output.set_size(&*first_terminal_ws); +// vte_parser.advance(&mut *terminal1_output, *byte); +// } +// buffer_has_unread_data = true; +// }, +// None => { +// if DEBUGGING { +// buffer_has_unread_data = false; +// } else if buffer_has_unread_data { +// let mut terminal1_output = terminal1_output.lock().unwrap(); +// let data_lines = terminal1_output.read_buffer_as_lines(); +// print!("\u{1b}c"); // clear screen +// ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); +// ::std::io::stdout().flush().expect("could not flush"); +// buffer_has_unread_data = false; +// } +// ::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this +// } +// } } } }) .unwrap(), ); +// active_threads.push( +// thread::Builder::new() +// .name("terminal_stdout_handler2".to_string()) +// .spawn({ +// +// let mut vte_parser = vte::Parser::new(); +// +// let terminal2_output = terminal2_output.clone(); +// let second_terminal_ws = second_terminal_ws.clone(); +// move || { +// let mut buffer_has_unread_data = true; +// loop { +// match read_from_pid(second_terminal_pid) { +// Some(read_bytes) => { +// if DEBUGGING { +// println!("\n\rread_bytes: {:?}", String::from_utf8(read_bytes.to_vec()).unwrap()); +// } +// for byte in read_bytes.iter() { +// let second_terminal_ws = second_terminal_ws.lock().unwrap(); +// let mut terminal2_output = terminal2_output.lock().unwrap(); +// terminal2_output.set_size(&*second_terminal_ws); +// vte_parser.advance(&mut *terminal2_output, *byte); +// } +// buffer_has_unread_data = true; +// }, +// None => { +// if DEBUGGING { +// buffer_has_unread_data = false; +// } else if buffer_has_unread_data { +// let mut terminal2_output = terminal2_output.lock().unwrap(); +// let data_lines = terminal2_output.read_buffer_as_lines(); +// print!("\u{1b}c"); // clear screen +// ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); +// ::std::io::stdout().flush().expect("could not flush"); +// buffer_has_unread_data = false; +// } +// ::std::thread::sleep(std::time::Duration::from_millis(50)); // TODO: adjust this +// } +// } +// } +// } +// }) +// .unwrap(), +// ); loop { let mut buffer = [0; 1]; @@ -522,75 +644,37 @@ fn main() { let mut handle = stdin.lock(); handle.read(&mut buffer).expect("failed to read stdin"); if buffer[0] == 10 { // ctrl-j + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); let active_terminal = active_terminal.lock().unwrap(); - temp_ws.ws_col -= 10; - if *active_terminal == first_terminal_pid { - let mut first_terminal_ws = first_terminal_ws.lock().unwrap(); - *first_terminal_ws = temp_ws; - - let mut terminal1_output = terminal1_output.lock().unwrap(); - terminal1_output.set_size(&*first_terminal_ws); - let data_lines = terminal1_output.read_buffer_as_lines(); - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); - set_terminal_size_using_fd(*active_terminal, &temp_ws); - } else { - let mut second_terminal_ws = second_terminal_ws.lock().unwrap(); - *second_terminal_ws = temp_ws; - - let mut terminal2_output = terminal2_output.lock().unwrap(); - terminal2_output.set_size(&*second_terminal_ws); - let data_lines = terminal2_output.read_buffer_as_lines(); - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); - set_terminal_size_using_fd(*active_terminal, &temp_ws); - } + terminal1_output.reduce_width(10); + terminal2_output.increase_width(10); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + set_terminal_size_using_fd(first_terminal_pid, terminal1_output.display_cols, terminal1_output.display_rows); + set_terminal_size_using_fd(second_terminal_pid, terminal2_output.display_cols, terminal2_output.display_rows); continue; } else if buffer[0] == 11 { // ctrl-k + let mut terminal1_output = terminal1_output.lock().unwrap(); + let mut terminal2_output = terminal2_output.lock().unwrap(); let active_terminal = active_terminal.lock().unwrap(); - temp_ws.ws_col += 10; - if *active_terminal == first_terminal_pid { - let mut first_terminal_ws = first_terminal_ws.lock().unwrap(); - *first_terminal_ws = temp_ws; - - set_terminal_size_using_fd(*active_terminal, &temp_ws); - let mut terminal1_output = terminal1_output.lock().unwrap(); - terminal1_output.set_size(&*first_terminal_ws); - let data_lines = terminal1_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); - - } else { - let mut second_terminal_ws = second_terminal_ws.lock().unwrap(); - *second_terminal_ws = temp_ws; - - let mut terminal2_output = terminal2_output.lock().unwrap(); - terminal2_output.set_size(&*second_terminal_ws); - let data_lines = terminal2_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); - set_terminal_size_using_fd(*active_terminal, &temp_ws); - } + terminal1_output.increase_width(10); + terminal2_output.reduce_width(10); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); + set_terminal_size_using_fd(first_terminal_pid, terminal1_output.display_cols, terminal1_output.display_rows); + set_terminal_size_using_fd(second_terminal_pid, terminal2_output.display_cols, terminal2_output.display_rows); continue; } else if buffer[0] == 16 { // ctrl-p let mut active_terminal = active_terminal.lock().unwrap(); if *active_terminal == first_terminal_pid { *active_terminal = second_terminal_pid; + let mut terminal1_output = terminal1_output.lock().unwrap(); let mut terminal2_output = terminal2_output.lock().unwrap(); - let data_lines = terminal2_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); } else { *active_terminal = first_terminal_pid; - let mut terminal1_output = terminal1_output.lock().unwrap(); - let data_lines = terminal1_output.read_buffer_as_lines(); - print!("\u{1b}c"); // clear screen - ::std::io::stdout().write_all(&data_lines.as_bytes()).expect("cannot write to stdout"); - ::std::io::stdout().flush().expect("could not flush"); + let mut terminal2_output = terminal2_output.lock().unwrap(); + render(&mut *terminal1_output, &mut *terminal2_output, &full_screen_ws, *active_terminal == first_terminal_pid); } continue; }