diff --git a/src/main.rs b/src/main.rs index 7356c47f..13c1d0e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ mod os_input_output; use ::std::fmt::{self, Display, Formatter}; use std::cmp::max; use std::io::{Read, Write}; -use std::collections::{VecDeque, HashMap}; +use std::collections::{VecDeque, HashMap, BTreeMap}; use nix::pty::Winsize; use std::os::unix::io::RawFd; use ::std::thread; @@ -729,7 +729,7 @@ struct Screen { full_screen_ws: Winsize, vertical_separator: TerminalCharacter, // TODO: better horizontal_separator: TerminalCharacter, // TODO: better - terminals: HashMap, + terminals: BTreeMap, // BTreeMap because we need a predictable order when changing focus active_terminal: Option, os_api: Box, } @@ -743,7 +743,7 @@ impl Screen { full_screen_ws: full_screen_ws.clone(), vertical_separator: TerminalCharacter::new('│').ansi_code(String::from("\u{1b}[m")), // TODO: better horizontal_separator: TerminalCharacter::new('─').ansi_code(String::from("\u{1b}[m")), // TODO: better - terminals: HashMap::new(), + terminals: BTreeMap::new(), active_terminal: None, os_api, } @@ -853,9 +853,11 @@ impl Screen { self.os_api.tcdrain(*active_terminal_id).expect("failed to drain terminal"); } } - fn get_active_terminal_cursor_position(&self) -> usize { + fn get_active_terminal_cursor_position(&self) -> (usize, usize) { // (x, y) let active_terminal = &self.get_active_terminal().unwrap(); - active_terminal.x_coords as usize + active_terminal.cursor_position_in_last_line() + let x = active_terminal.x_coords as usize + active_terminal.cursor_position_in_last_line(); + let y = active_terminal.y_coords + active_terminal.display_rows - 1; + (x, y as usize) } pub fn render (&mut self) { let mut stdout = self.os_api.get_stdout_writer(); @@ -885,8 +887,8 @@ impl Screen { stdout.write_all(&vte_output.as_bytes()).expect("cannot write to stdout"); } } - let active_terminal_cursor_position = self.get_active_terminal_cursor_position(); - let goto_cursor_position = format!("\r\u{1b}[{}C", active_terminal_cursor_position); + let (cursor_position_x, cursor_position_y) = self.get_active_terminal_cursor_position(); + let goto_cursor_position = format!("\u{1b}[{};{}H\u{1b}[m", cursor_position_y + 1, cursor_position_x + 1); // goto row/col stdout.write_all(&goto_cursor_position.as_bytes()).expect("cannot write to stdout"); stdout.flush().expect("could not flush"); } @@ -921,24 +923,106 @@ impl Screen { Some(ids) } } + fn terminal_ids_directly_above_with_same_left_alignment(&self, id: &RawFd) -> Option> { + let mut ids = vec![]; + let terminal_to_check = self.terminals.get(id).unwrap(); + for (pid, terminal) in self.terminals.iter() { + if terminal.x_coords == terminal_to_check.x_coords && terminal.y_coords + terminal.display_rows + 1 == terminal_to_check.y_coords { + ids.push(*pid); + } + } + if ids.is_empty() { + None + } else { + Some(ids) + } + } + fn terminal_ids_directly_below_with_same_left_alignment(&self, id: &RawFd) -> Option> { + let mut ids = vec![]; + let terminal_to_check = self.terminals.get(id).unwrap(); + for (pid, terminal) in self.terminals.iter() { + if terminal.x_coords == terminal_to_check.x_coords && terminal.y_coords == terminal_to_check.y_coords + terminal_to_check.display_rows + 1 { + ids.push(*pid); + } + } + if ids.is_empty() { + None + } else { + Some(ids) + } + } + fn terminal_ids_directly_above_with_same_right_alignment(&self, id: &RawFd) -> Option> { + let mut ids = vec![]; + let terminal_to_check = self.terminals.get(id).unwrap(); + for (pid, terminal) in self.terminals.iter() { + if terminal.x_coords + terminal.display_cols == terminal_to_check.x_coords + terminal_to_check.display_cols && terminal.y_coords + terminal.display_rows + 1 == terminal_to_check.y_coords { + ids.push(*pid); + } + } + if ids.is_empty() { + None + } else { + Some(ids) + } + } + fn terminal_ids_directly_below_with_same_right_alignment(&self, id: &RawFd) -> Option> { + let mut ids = vec![]; + let terminal_to_check = self.terminals.get(id).unwrap(); + for (pid, terminal) in self.terminals.iter() { + if terminal.x_coords + terminal.display_cols == terminal_to_check.x_coords + terminal_to_check.display_cols && terminal_to_check.y_coords + terminal_to_check.display_rows + 1 == terminal.y_coords { + ids.push(*pid); + } + } + if ids.is_empty() { + None + } else { + Some(ids) + } + } pub fn resize_left (&mut self) { // TODO: find out by how much we actually reduced and only reduce by that much let count = 10; if let Some(active_terminal_id) = self.get_active_terminal_id() { let terminals_to_the_left = self.terminal_ids_directly_left_of(&active_terminal_id); let terminals_to_the_right = self.terminal_ids_directly_right_of(&active_terminal_id); + let terminals_above_left_aligned = self.terminal_ids_directly_above_with_same_left_alignment(&active_terminal_id); + let terminals_below_left_aligned = self.terminal_ids_directly_below_with_same_left_alignment(&active_terminal_id); + let terminals_above_right_aligned = self.terminal_ids_directly_above_with_same_right_alignment(&active_terminal_id); + let terminals_below_right_aligned = self.terminal_ids_directly_below_with_same_right_alignment(&active_terminal_id); match (terminals_to_the_left, terminals_to_the_right) { - (Some(terminals_to_the_left), Some(_terminals_to_the_right)) => { + (Some(_terminals_to_the_left), Some(terminals_to_the_right)) => { let active_terminal = self.terminals.get_mut(&active_terminal_id).unwrap(); - active_terminal.increase_width_left(count); + active_terminal.reduce_width_left(count); self.os_api.set_terminal_size_using_fd( active_terminal.pid, active_terminal.display_cols, active_terminal.display_rows ); - for terminal_id in terminals_to_the_left { + if let Some(terminals_above) = terminals_above_right_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_right_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + for terminal_id in terminals_to_the_right { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); - terminal.reduce_width_left(count); + terminal.increase_width_left(count); self.os_api.set_terminal_size_using_fd( terminal.pid, terminal.display_cols, @@ -954,6 +1038,28 @@ impl Screen { active_terminal.display_cols, active_terminal.display_rows ); + if let Some(terminals_above) = terminals_above_left_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_left_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } for terminal_id in terminals_to_the_left { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); terminal.reduce_width_left(count); @@ -972,6 +1078,28 @@ impl Screen { active_terminal.display_cols, active_terminal.display_rows ); + if let Some(terminals_above) = terminals_above_right_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_right_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_left(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } for terminal_id in terminals_to_the_right { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); terminal.increase_width_left(count); @@ -991,6 +1119,10 @@ impl Screen { if let Some(active_terminal_id) = self.get_active_terminal_id() { let terminals_to_the_left = self.terminal_ids_directly_left_of(&active_terminal_id); let terminals_to_the_right = self.terminal_ids_directly_right_of(&active_terminal_id); + let terminals_above_left_aligned = self.terminal_ids_directly_above_with_same_left_alignment(&active_terminal_id); + let terminals_below_left_aligned = self.terminal_ids_directly_below_with_same_left_alignment(&active_terminal_id); + let terminals_above_right_aligned = self.terminal_ids_directly_above_with_same_right_alignment(&active_terminal_id); + let terminals_below_right_aligned = self.terminal_ids_directly_below_with_same_right_alignment(&active_terminal_id); match (terminals_to_the_left, terminals_to_the_right) { (Some(_terminals_to_the_left), Some(terminals_to_the_right)) => { let active_terminal = self.terminals.get_mut(&active_terminal_id).unwrap(); @@ -1000,6 +1132,28 @@ impl Screen { active_terminal.display_cols, active_terminal.display_rows ); + if let Some(terminals_above) = terminals_above_right_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_right_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } for terminal_id in terminals_to_the_right { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); terminal.reduce_width_right(count); @@ -1018,6 +1172,28 @@ impl Screen { active_terminal.display_cols, active_terminal.display_rows ); + if let Some(terminals_above) = terminals_above_left_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_left_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.reduce_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } for terminal_id in terminals_to_the_left { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); terminal.increase_width_right(count); @@ -1036,6 +1212,28 @@ impl Screen { active_terminal.display_cols, active_terminal.display_rows ); + if let Some(terminals_above) = terminals_above_left_aligned { + for terminal_id in terminals_above { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } + if let Some(terminals_below) = terminals_below_left_aligned { + for terminal_id in terminals_below { + let terminal = self.terminals.get_mut(&terminal_id).unwrap(); + terminal.increase_width_right(count); + self.os_api.set_terminal_size_using_fd( + terminal.pid, + terminal.display_cols, + terminal.display_rows + ); + } + } for terminal_id in terminals_to_the_right { let terminal = self.terminals.get_mut(&terminal_id).unwrap(); terminal.reduce_width_right(count); @@ -1054,18 +1252,15 @@ impl Screen { if self.terminals.is_empty() { return; } - let active_terminal = self.get_active_terminal().unwrap(); - let mut first_terminal = active_terminal.pid; - for (terminal_pid, terminal_output) in self.terminals.iter() { - if terminal_output.x_coords == 0 { - first_terminal = terminal_output.pid - } else if active_terminal.x_coords + active_terminal.display_cols == terminal_output.x_coords - 1 { - self.active_terminal = Some(*terminal_pid); - self.render(); - return; - } + let active_terminal_id = self.get_active_terminal_id().unwrap(); + let terminal_ids: Vec = self.terminals.keys().copied().collect(); // TODO: better, no allocations + let first_terminal = terminal_ids.get(0).unwrap(); + let active_terminal_id_position = terminal_ids.iter().position(|id| id == &active_terminal_id).unwrap(); + if let Some(next_terminal) = terminal_ids.get(active_terminal_id_position + 1) { + self.active_terminal = Some(*next_terminal); + } else { + self.active_terminal = Some(*first_terminal); } - self.active_terminal = Some(first_terminal); self.render(); } } @@ -1228,15 +1423,19 @@ pub fn start(mut os_input: Box) { let mut buffer = [0; 1]; stdin.read(&mut buffer).expect("failed to read stdin"); if buffer[0] == 10 { // ctrl-j - send_screen_instructions.send(ScreenInstruction::ResizeLeft).unwrap(); + todo!(); } else if buffer[0] == 11 { // ctrl-k - send_screen_instructions.send(ScreenInstruction::ResizeRight).unwrap(); + todo!(); } else if buffer[0] == 16 { // ctrl-p send_screen_instructions.send(ScreenInstruction::MoveFocus).unwrap(); } else if buffer[0] == 8 { // ctrl-h - send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally).unwrap(); + send_screen_instructions.send(ScreenInstruction::ResizeLeft).unwrap(); + } else if buffer[0] == 12 { // ctrl-l + send_screen_instructions.send(ScreenInstruction::ResizeRight).unwrap(); } else if buffer[0] == 14 { // ctrl-n send_pty_instructions.send(PtyInstruction::SpawnTerminalVertically).unwrap(); + } else if buffer[0] == 2 { // ctrl-b + send_pty_instructions.send(PtyInstruction::SpawnTerminalHorizontally).unwrap(); } else if buffer[0] == 17 { // ctrl-q send_screen_instructions.send(ScreenInstruction::Quit).unwrap(); send_pty_instructions.send(PtyInstruction::Quit).unwrap(); diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index a9893dcc..cd526ea7 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -148,7 +148,7 @@ impl OsApi for FakeInputOutput { Box::new((*self).clone()) } fn get_stdin_reader(&self) -> Box { - let mut input_chars = vec![0, 1, 2]; + let mut input_chars = vec![0]; if let Some(input_to_add) = self.input_to_add.lock().unwrap().as_ref() { for byte in input_to_add { input_chars.push(*byte); diff --git a/src/tests/integration.rs b/src/tests/integration.rs index 828db5ed..843c3012 100644 --- a/src/tests/integration.rs +++ b/src/tests/integration.rs @@ -105,7 +105,7 @@ pub fn split_terminals_horizontally() { // TODO: this test is a little flaky - there's some sort of race condition that makes the // second terminal in the split appear blank sometimes - fix this let mut fake_input_output = get_fake_os_input(); - fake_input_output.add_terminal_input(&[8, 17]); // split-horizontally and quit (ctrl-n + ctrl-q) + fake_input_output.add_terminal_input(&[2, 17]); // split-horizontally and quit (ctrl-b + ctrl-q) start(Box::new(fake_input_output.clone())); let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();