From 47954166a2fc7cc136043a66782a25d832196e4b Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Sun, 15 Nov 2020 19:55:56 +0100 Subject: [PATCH] feat(ux): initial layout engine (#50) * prototype * refactor(layout): move stuff around * style(format): make rustfmt happy --- Cargo.lock | 1 + Cargo.toml | 1 + src/layout.rs | 134 ++++++++++++++++++ src/main.rs | 31 +++- src/pty_bus.rs | 24 ++++ src/screen.rs | 62 +++++++- src/terminal_pane/terminal_pane.rs | 7 + src/tests/fakes.rs | 2 +- .../layouts/three-panes-with-nesting.yaml | 16 +++ src/tests/integration/layouts.rs | 51 +++++++ src/tests/integration/mod.rs | 1 + ...tion__layouts__accepts_basic_layout-2.snap | 24 ++++ ...tion__layouts__accepts_basic_layout-3.snap | 24 ++++ ...ration__layouts__accepts_basic_layout.snap | 24 ++++ src/tests/possible_tty_inputs.rs | 4 +- src/tests/tty_inputs.rs | 23 +++ 16 files changed, 424 insertions(+), 5 deletions(-) create mode 100644 src/layout.rs create mode 100644 src/tests/fixtures/layouts/three-panes-with-nesting.yaml create mode 100644 src/tests/integration/layouts.rs create mode 100644 src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-2.snap create mode 100644 src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-3.snap create mode 100644 src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout.snap diff --git a/Cargo.lock b/Cargo.lock index 9667bb05..8221a311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -457,6 +457,7 @@ dependencies = [ "libc", "nix", "serde", + "serde_yaml", "signal-hook", "structopt", "termios", diff --git a/Cargo.toml b/Cargo.toml index 31424fb3..e6222f05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ futures = "0.3.5" serde = { version = "1.0", features = ["derive"] } bincode = "1.3.1" structopt = "0.3" +serde_yaml = "0.8" [dependencies.async-std] version = "1.3.0" diff --git a/src/layout.rs b/src/layout.rs new file mode 100644 index 00000000..e80c76b2 --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,134 @@ +use crate::terminal_pane::PositionAndSize; +use serde::{Deserialize, Serialize}; + +fn _debug_log_to_file(message: String) { + use std::fs::OpenOptions; + use std::io::prelude::*; + let mut file = OpenOptions::new() + .append(true) + .create(true) + .open("/tmp/mosaic-log.txt") + .unwrap(); + file.write_all(message.as_bytes()).unwrap(); + file.write_all("\n".as_bytes()).unwrap(); +} + +fn split_space_to_parts_vertically( + space_to_split: &PositionAndSize, + percentages: Vec, +) -> Vec { + let mut split_parts = vec![]; + let mut current_x_position = space_to_split.x; + let width = space_to_split.columns - (percentages.len() - 1); // minus space for gaps + for percentage in percentages.iter() { + let columns = (width as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + split_parts.push(PositionAndSize { + x: current_x_position, + y: space_to_split.y, + columns, + rows: space_to_split.rows, + }); + current_x_position += columns + 1; // 1 for gap + } + let total_width = split_parts + .iter() + .fold(0, |total_width, part| total_width + part.columns); + if total_width < width { + // we have some extra space left, let's add it to the last part + let last_part_index = split_parts.len() - 1; + let mut last_part = split_parts.get_mut(last_part_index).unwrap(); + last_part.columns += width - total_width; + } + split_parts +} + +fn split_space_to_parts_horizontally( + space_to_split: &PositionAndSize, + percentages: Vec, +) -> Vec { + let mut split_parts = vec![]; + let mut current_y_position = space_to_split.y; + let height = space_to_split.rows - (percentages.len() - 1); // minus space for gaps + for percentage in percentages.iter() { + let rows = (height as f32 * (*percentage as f32 / 100.0)) as usize; // TODO: round properly + split_parts.push(PositionAndSize { + x: space_to_split.x, + y: current_y_position, + columns: space_to_split.columns, + rows, + }); + current_y_position += rows + 1; // 1 for gap + } + let total_height = split_parts + .iter() + .fold(0, |total_height, part| total_height + part.rows); + if total_height < height { + // we have some extra space left, let's add it to the last part + let last_part_index = split_parts.len() - 1; + let mut last_part = split_parts.get_mut(last_part_index).unwrap(); + last_part.rows += height - total_height; + } + split_parts +} + +fn split_space(space_to_split: &PositionAndSize, layout: &Layout) -> Vec { + let mut pane_positions: Vec = vec![]; + let percentages: Vec = layout + .parts + .iter() + .map(|part| { + let split_size = part.split_size.as_ref().unwrap(); // TODO: if there is no split size, it should get the remaining "free space" + match split_size { + SplitSize::Percent(percent) => *percent, + } + }) + .collect(); + let split_parts = match layout.direction { + Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages), + Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, percentages), + }; + for (i, part) in layout.parts.iter().enumerate() { + let part_position_and_size = split_parts.get(i).unwrap(); + if part.parts.len() > 0 { + let mut part_positions = split_space(&part_position_and_size, part); + pane_positions.append(&mut part_positions); + } else { + pane_positions.push(*part_position_and_size); + } + } + pane_positions +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Direction { + Horizontal, + Vertical, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SplitSize { + Percent(u8), // 1 to 100 +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Layout { + pub direction: Direction, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub parts: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub split_size: Option, +} + +impl Layout { + pub fn total_panes(&self) -> usize { + let mut total_panes = 0; + total_panes += self.parts.len(); + for part in self.parts.iter() { + total_panes += part.total_panes(); + } + total_panes + } + pub fn position_panes_in_space(&self, space: &PositionAndSize) -> Vec { + split_space(space, &self) + } +} diff --git a/src/main.rs b/src/main.rs index b964efa7..315e8eb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod tests; mod boundaries; +mod layout; mod os_input_output; mod pty_bus; mod screen; @@ -14,8 +15,10 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; use serde::{Deserialize, Serialize}; +use serde_yaml; use structopt::StructOpt; +use crate::layout::Layout; use crate::os_input_output::{get_os_input, OsApi}; use crate::pty_bus::{PtyBus, PtyInstruction, VteEvent}; use crate::screen::{Screen, ScreenInstruction}; @@ -43,7 +46,9 @@ pub struct Opt { #[structopt(long)] /// Maximum panes on screen, caution: opening more panes will close old ones max_panes: Option, - + #[structopt(short, long)] + /// Path to a layout yaml file + layout: Option, #[structopt(short, long)] debug: bool, } @@ -140,7 +145,26 @@ pub fn start(mut os_input: Box, opts: Opt) { .name("pty".to_string()) .spawn({ move || { - pty_bus.spawn_terminal_vertically(None); + match opts.layout { + Some(layout_path) => { + use std::fs::File; + let mut layout_file = File::open(&layout_path) + .expect(&format!("cannot find layout {}", layout_path.display())); + let mut layout = String::new(); + layout_file.read_to_string(&mut layout).expect(&format!( + "could not read layout {}", + layout_path.display() + )); + let layout: Layout = serde_yaml::from_str(&layout).expect(&format!( + "could not parse layout {}", + layout_path.display() + )); + pty_bus.spawn_terminals_for_layout(layout); + } + None => { + pty_bus.spawn_terminal_vertically(None); + } + } loop { let event = pty_bus .receive_pty_instructions @@ -230,6 +254,9 @@ pub fn start(mut os_input: Box, opts: Opt) { ScreenInstruction::ToggleActiveTerminalFullscreen => { screen.toggle_active_terminal_fullscreen(); } + ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => { + screen.apply_layout(layout, new_pane_pids) + } ScreenInstruction::Quit => { break; } diff --git a/src/pty_bus.rs b/src/pty_bus.rs index 9f75e4b8..fed8f7f7 100644 --- a/src/pty_bus.rs +++ b/src/pty_bus.rs @@ -9,6 +9,7 @@ use ::std::time::{Duration, Instant}; use ::vte; use std::path::PathBuf; +use crate::layout::Layout; use crate::os_input_output::OsApi; use crate::ScreenInstruction; @@ -317,6 +318,29 @@ impl PtyBus { .send(ScreenInstruction::HorizontalSplit(pid_primary)) .unwrap(); } + pub fn spawn_terminals_for_layout(&mut self, layout: Layout) { + let total_panes = layout.total_panes(); + let mut new_pane_pids = vec![]; + for _ in 0..total_panes { + let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None); + self.id_to_child_pid.insert(pid_primary, pid_secondary); + new_pane_pids.push(pid_primary); + } + &self + .send_screen_instructions + .send(ScreenInstruction::ApplyLayout(( + layout, + new_pane_pids.clone(), + ))); + for id in new_pane_pids { + stream_terminal_bytes( + id, + self.send_screen_instructions.clone(), + self.os_input.clone(), + self.debug_to_file, + ); + } + } pub fn close_pane(&mut self, id: RawFd) { let child_pid = self.id_to_child_pid.get(&id).unwrap(); self.os_input.kill(*child_pid).unwrap(); diff --git a/src/screen.rs b/src/screen.rs index a433f02b..777b7935 100644 --- a/src/screen.rs +++ b/src/screen.rs @@ -5,9 +5,10 @@ use std::os::unix::io::RawFd; use std::sync::mpsc::{Receiver, Sender}; use crate::boundaries::Boundaries; +use crate::layout::Layout; use crate::os_input_output::OsApi; use crate::pty_bus::{PtyInstruction, VteEvent}; -use crate::terminal_pane::TerminalPane; +use crate::terminal_pane::{PositionAndSize, TerminalPane}; use crate::AppInstruction; /* @@ -80,6 +81,7 @@ pub enum ScreenInstruction { CloseFocusedPane, ToggleActiveTerminalFullscreen, ClosePane(RawFd), + ApplyLayout((Layout, Vec)), } pub struct Screen { @@ -115,6 +117,64 @@ impl Screen { os_api, } } + pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec) { + self.panes_to_hide.clear(); + // TODO: this should be an attribute on Screen instead of full_screen_ws + let free_space = PositionAndSize { + x: 0, + y: 0, + rows: self.full_screen_ws.ws_row as usize, + columns: self.full_screen_ws.ws_col as usize, + }; + let positions_in_layout = layout.position_panes_in_space(&free_space); + let mut positions_and_size = positions_in_layout.iter(); + for (pid, terminal_pane) in self.terminals.iter_mut() { + match positions_and_size.next() { + Some(position_and_size) => { + terminal_pane.reset_size_and_position_override(); + terminal_pane.change_size_p(&position_and_size); + self.os_api.set_terminal_size_using_fd( + *pid, + position_and_size.columns as u16, + position_and_size.rows as u16, + ); + } + None => { + // we filled the entire layout, no room for this pane + // TODO: handle active terminal + self.panes_to_hide.insert(*pid); + } + } + } + let mut new_pids = new_pids.iter(); + for position_and_size in positions_and_size { + // there are still panes left to fill, use the pids we received in this method + let pid = new_pids.next().unwrap(); // if this crashes it means we got less pids than there are panes in this layout + let mut new_terminal = TerminalPane::new( + *pid, + self.full_screen_ws.clone(), + position_and_size.x, + position_and_size.y, + ); + new_terminal.change_size_p(position_and_size); + self.os_api.set_terminal_size_using_fd( + new_terminal.pid, + new_terminal.get_columns() as u16, + new_terminal.get_rows() as u16, + ); + self.terminals.insert(*pid, new_terminal); + } + for unused_pid in new_pids { + // this is a bit of a hack and happens because we don't have any central location that + // can query the screen as to how many panes it needs to create a layout + // fixing this will require a bit of an architecture change + self.send_pty_instructions + .send(PtyInstruction::ClosePane(*unused_pid)) + .unwrap(); + } + self.active_terminal = Some(*self.terminals.iter().next().unwrap().0); + self.render(); + } pub fn new_pane(&mut self, pid: RawFd) { self.close_down_to_max_terminals(); if self.terminals.is_empty() { diff --git a/src/terminal_pane/terminal_pane.rs b/src/terminal_pane/terminal_pane.rs index 3e0cdd6b..fc1cc207 100644 --- a/src/terminal_pane/terminal_pane.rs +++ b/src/terminal_pane/terminal_pane.rs @@ -17,6 +17,7 @@ pub struct PositionAndSize { pub columns: usize, } +#[derive(Debug)] pub struct TerminalPane { pub pid: RawFd, pub scroll: Scroll, @@ -134,6 +135,12 @@ impl TerminalPane { self.reflow_lines(); self.should_render = true; } + pub fn change_size_p(&mut self, position_and_size: &PositionAndSize) { + self.position_and_size = *position_and_size; + self.reflow_lines(); + self.should_render = true; + } + // TODO: merge these two methods pub fn change_size(&mut self, ws: &Winsize) { self.position_and_size.columns = ws.ws_col as usize; self.position_and_size.rows = ws.ws_row as usize; diff --git a/src/tests/fakes.rs b/src/tests/fakes.rs index 109424cf..37d1bf79 100644 --- a/src/tests/fakes.rs +++ b/src/tests/fakes.rs @@ -143,7 +143,7 @@ impl OsApi for FakeInputOutput { .push(IoEvent::UnsetRawMode(pid)); } fn spawn_terminal(&mut self, _file_to_open: Option) -> (RawFd, RawFd) { - let next_terminal_id = { self.read_buffers.lock().unwrap().keys().len() as RawFd + 1 }; + let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1; self.add_terminal(next_terminal_id); (next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here } diff --git a/src/tests/fixtures/layouts/three-panes-with-nesting.yaml b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml new file mode 100644 index 00000000..f1e0dd7e --- /dev/null +++ b/src/tests/fixtures/layouts/three-panes-with-nesting.yaml @@ -0,0 +1,16 @@ +--- +direction: Horizontal +parts: + - direction: Vertical + parts: + - direction: Horizontal + split_size: + Percent: 20 + - direction: Horizontal + split_size: + Percent: 80 + split_size: + Percent: 80 + - direction: Vertical + split_size: + Percent: 20 diff --git a/src/tests/integration/layouts.rs b/src/tests/integration/layouts.rs new file mode 100644 index 00000000..2ab0375e --- /dev/null +++ b/src/tests/integration/layouts.rs @@ -0,0 +1,51 @@ +use ::insta::assert_snapshot; +use ::nix::pty::Winsize; + +use crate::tests::fakes::FakeInputOutput; +use crate::tests::utils::commands::QUIT; +use crate::tests::utils::get_output_frame_snapshots; +use crate::{start, Opt}; + +fn get_fake_os_input(fake_win_size: &Winsize) -> FakeInputOutput { + FakeInputOutput::new(fake_win_size.clone()) +} + +#[test] +pub fn accepts_basic_layout() { + let fake_win_size = Winsize { + ws_col: 121, + ws_row: 20, + ws_xpixel: 0, + ws_ypixel: 0, + }; + let mut fake_input_output = get_fake_os_input(&fake_win_size); + fake_input_output.add_terminal_input(&[QUIT]); + use std::path::PathBuf; + let mut opts = Opt::default(); + opts.layout = Some(PathBuf::from( + "src/tests/fixtures/layouts/three-panes-with-nesting.yaml", + )); + start(Box::new(fake_input_output.clone()), opts); + let output_frames = fake_input_output + .stdout_writer + .output_frames + .lock() + .unwrap(); + let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size); + + let snapshot_count = snapshots.len(); + let first_snapshot = snapshots.get(0).unwrap(); + let next_to_last_snapshot = snapshots.get(snapshot_count - 2).unwrap(); + let last_snapshot = snapshots.last().unwrap(); + // here we only test the first, next to last and last snapshot because there's a race condition + // with the other snapshots. Namely all the terminals are created asynchronously and read in an + // async task, so we have no way to guarantee the order in which their bytes will be read, and + // it doesn't really matter in this context. We just want to see that the layout is initially + // created properly and that in the end it's populated properly with its content + // + // we read the next to last as well as the last, because the last includes the "Bye from + // Mosaic" message, and we also want to make sure things are fine before that + assert_snapshot!(first_snapshot); + assert_snapshot!(next_to_last_snapshot); + assert_snapshot!(last_snapshot); +} diff --git a/src/tests/integration/mod.rs b/src/tests/integration/mod.rs index 677af578..bcd78ad9 100644 --- a/src/tests/integration/mod.rs +++ b/src/tests/integration/mod.rs @@ -1,6 +1,7 @@ pub mod basic; pub mod close_pane; pub mod compatibility; +pub mod layouts; pub mod resize_down; pub mod resize_left; pub mod resize_right; diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-2.snap new file mode 100644 index 00000000..548e6394 --- /dev/null +++ b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-2.snap @@ -0,0 +1,24 @@ +--- +source: src/tests/integration/layouts.rs +expression: next_to_last_snapshot +--- +line6-bbbbbbbbbbbbbbbbbb│line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line7-bbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line8-bbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line9-bbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line10-bbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line11-bbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line12-bbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line13-bbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line14-bbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line15-bbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line16-bbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line17-bbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line18-bbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line19-bbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +prompt $ █ │prompt $ +────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-3.snap b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-3.snap new file mode 100644 index 00000000..f22002f3 --- /dev/null +++ b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-3.snap @@ -0,0 +1,24 @@ +--- +source: src/tests/integration/layouts.rs +expression: last_snapshot +--- +line7-bbbbbbbbbbbbbbbbbb│line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line8-bbbbbbbbbbbbbbbbbb│line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line9-bbbbbbbbbbbbbbbbbb│line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line10-bbbbbbbbbbbbbbbbb│line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line11-bbbbbbbbbbbbbbbbb│line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line12-bbbbbbbbbbbbbbbbb│line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line13-bbbbbbbbbbbbbbbbb│line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line14-bbbbbbbbbbbbbbbbb│line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line15-bbbbbbbbbbbbbbbbb│line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line16-bbbbbbbbbbbbbbbbb│line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line17-bbbbbbbbbbbbbbbbb│line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line18-bbbbbbbbbbbbbbbbb│line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +line19-bbbbbbbbbbbbbbbbb│line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +prompt $ │prompt $ +────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── +line17-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line18-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +line19-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +prompt $ +Bye from Mosaic!█ diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout.snap b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout.snap new file mode 100644 index 00000000..9f6550c2 --- /dev/null +++ b/src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout.snap @@ -0,0 +1,24 @@ +--- +source: src/tests/integration/layouts.rs +expression: first_snapshot +--- +█ │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ +────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────── + + + + diff --git a/src/tests/possible_tty_inputs.rs b/src/tests/possible_tty_inputs.rs index 6730591f..3d22bab6 100644 --- a/src/tests/possible_tty_inputs.rs +++ b/src/tests/possible_tty_inputs.rs @@ -1,6 +1,6 @@ use crate::tests::tty_inputs::{ COL_10, COL_121, COL_14, COL_15, COL_19, COL_20, COL_24, COL_29, COL_30, COL_34, COL_39, - COL_40, COL_50, COL_60, COL_70, COL_90, + COL_40, COL_50, COL_60, COL_70, COL_90, COL_96, }; use std::collections::HashMap; use std::fs; @@ -65,6 +65,7 @@ pub fn get_possible_tty_inputs() -> HashMap { let col_60_bytes = Bytes::new().content_from_str(&COL_60); let col_70_bytes = Bytes::new().content_from_str(&COL_70); let col_90_bytes = Bytes::new().content_from_str(&COL_90); + let col_96_bytes = Bytes::new().content_from_str(&COL_96); let col_121_bytes = Bytes::new().content_from_str(&COL_121); possible_inputs.insert(10, col_10_bytes); possible_inputs.insert(14, col_14_bytes); @@ -81,6 +82,7 @@ pub fn get_possible_tty_inputs() -> HashMap { possible_inputs.insert(60, col_60_bytes); possible_inputs.insert(70, col_70_bytes); possible_inputs.insert(90, col_90_bytes); + possible_inputs.insert(96, col_96_bytes); possible_inputs.insert(121, col_121_bytes); possible_inputs } diff --git a/src/tests/tty_inputs.rs b/src/tests/tty_inputs.rs index 485585d3..387724cc 100644 --- a/src/tests/tty_inputs.rs +++ b/src/tests/tty_inputs.rs @@ -364,3 +364,26 @@ pub const COL_90: [&str; 20] = [ "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", "prompt $ ", ]; + +pub const COL_96: [&str; 20] = [ + "line1-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line2-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line3-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line4-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line5-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line6-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line7-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line8-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line9-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line10-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line11-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line12-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line13-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line14-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line15-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line16-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line17-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line18-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "line19-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n", + "prompt $ ", +];