aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2020-11-15 19:55:56 +0100
committerGitHub <noreply@github.com>2020-11-15 19:55:56 +0100
commit47954166a2fc7cc136043a66782a25d832196e4b (patch)
treef7da8ad572f6dee70b233528ebbf1d94176691b7 /src
parentfeat: add rustfmt, update action workflow to check for formatting (#45) (diff)
downloadsl0ppij-47954166a2fc7cc136043a66782a25d832196e4b.tar.gz
sl0ppij-47954166a2fc7cc136043a66782a25d832196e4b.tar.xz
sl0ppij-47954166a2fc7cc136043a66782a25d832196e4b.zip
feat(ux): initial layout engine (#50)
* prototype * refactor(layout): move stuff around * style(format): make rustfmt happy
Diffstat (limited to 'src')
-rw-r--r--src/layout.rs134
-rw-r--r--src/main.rs31
-rw-r--r--src/pty_bus.rs24
-rw-r--r--src/screen.rs62
-rw-r--r--src/terminal_pane/terminal_pane.rs7
-rw-r--r--src/tests/fakes.rs2
-rw-r--r--src/tests/fixtures/layouts/three-panes-with-nesting.yaml16
-rw-r--r--src/tests/integration/layouts.rs51
-rw-r--r--src/tests/integration/mod.rs1
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-2.snap24
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout-3.snap24
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__layouts__accepts_basic_layout.snap24
-rw-r--r--src/tests/possible_tty_inputs.rs4
-rw-r--r--src/tests/tty_inputs.rs23
14 files changed, 422 insertions, 5 deletions
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<u8>,
+) -> Vec<PositionAndSize> {
+ 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<u8>,
+) -> Vec<PositionAndSize> {
+ 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<PositionAndSize> {
+ let mut pane_positions: Vec<PositionAndSize> = vec![];
+ let percentages: Vec<u8> = 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<Layout>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub split_size: Option<SplitSize>,
+}
+
+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<PositionAndSize> {
+ 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<usize>,
-
+ #[structopt(short, long)]
+ /// Path to a layout yaml file
+ layout: Option<PathBuf>,
#[structopt(short, long)]
debug: bool,
}
@@ -140,7 +145,26 @@ pub fn start(mut os_input: Box<dyn OsApi>, 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<dyn OsApi>, 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<RawFd>)),
}
pub struct Screen {
@@ -115,6 +117,64 @@ impl Screen {
os_api,
}
}
+ pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec<RawFd>) {
+ 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<PathBuf>) -> (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<u16, Bytes> {
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<u16, Bytes> {
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 $ ",
+];