mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-27 14:34:32 +03:00
fix(layout): total layout percent 100 validation (#57)
* Fix for issue #52 * Added missing fixtures. * Added missing validation. * Moved layout creation and validation to the Layout struct. * Ran cargo fmt. * Added creation of tmp folder if needed. * Code review edit. * Code review edit. * Fancied code up. * PR request change. * PR code review. * Merge from upstream/main. * Merge from upstream/main.
This commit is contained in:
parent
6a9ecc42a8
commit
fd1f1ce697
@ -1,5 +1,7 @@
|
|||||||
use crate::terminal_pane::PositionAndSize;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{fs::File, io::prelude::*, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::terminal_pane::PositionAndSize;
|
||||||
|
|
||||||
fn split_space_to_parts_vertically(
|
fn split_space_to_parts_vertically(
|
||||||
space_to_split: &PositionAndSize,
|
space_to_split: &PositionAndSize,
|
||||||
@ -65,12 +67,17 @@ fn split_space(space_to_split: &PositionAndSize, layout: &Layout) -> Vec<Positio
|
|||||||
.parts
|
.parts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|part| {
|
.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"
|
let split_size = part.split_size.as_ref();
|
||||||
match split_size {
|
match split_size {
|
||||||
SplitSize::Percent(percent) => *percent,
|
Some(SplitSize::Percent(percent)) => *percent,
|
||||||
|
None => {
|
||||||
|
// TODO: if there is no split size, it should get the remaining "free space"
|
||||||
|
panic!("Please enter the percentage of the screen part");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let split_parts = match layout.direction {
|
let split_parts = match layout.direction {
|
||||||
Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages),
|
Direction::Vertical => split_space_to_parts_vertically(space_to_split, percentages),
|
||||||
Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, percentages),
|
Direction::Horizontal => split_space_to_parts_horizontally(space_to_split, percentages),
|
||||||
@ -87,6 +94,34 @@ fn split_space(space_to_split: &PositionAndSize, layout: &Layout) -> Vec<Positio
|
|||||||
pane_positions
|
pane_positions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_layout_percentage_total(layout: &Layout) -> bool {
|
||||||
|
let total_percentages: u8 = layout
|
||||||
|
.parts
|
||||||
|
.iter()
|
||||||
|
.map(|part| {
|
||||||
|
let split_size = part.split_size.as_ref();
|
||||||
|
match split_size {
|
||||||
|
Some(SplitSize::Percent(percent)) => *percent,
|
||||||
|
None => {
|
||||||
|
// TODO: if there is no split size, it should get the remaining "free space"
|
||||||
|
panic!("Please enter the percentage of the screen part");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
if total_percentages != 100 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in layout.parts.iter() {
|
||||||
|
if part.parts.len() > 0 {
|
||||||
|
return validate_layout_percentage_total(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
@ -108,6 +143,28 @@ pub struct Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
|
pub fn new(layout_path: PathBuf) -> Self {
|
||||||
|
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()
|
||||||
|
));
|
||||||
|
layout.validate();
|
||||||
|
|
||||||
|
layout
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) {
|
||||||
|
if !validate_layout_percentage_total(&self) {
|
||||||
|
panic!("The total percent for each part should equal 100.");
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn total_panes(&self) -> usize {
|
pub fn total_panes(&self) -> usize {
|
||||||
let mut total_panes = 0;
|
let mut total_panes = 0;
|
||||||
total_panes += self.parts.len();
|
total_panes += self.parts.len();
|
||||||
|
42
src/main.rs
42
src/main.rs
@ -18,7 +18,6 @@ use std::sync::mpsc::{channel, Receiver, Sender};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml;
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
use crate::command_is_executing::CommandIsExecuting;
|
use crate::command_is_executing::CommandIsExecuting;
|
||||||
@ -61,14 +60,14 @@ pub struct Opt {
|
|||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let opts = Opt::from_args();
|
let opts = Opt::from_args();
|
||||||
if opts.split.is_some() {
|
if let Some(split_dir) = opts.split {
|
||||||
match opts.split {
|
match split_dir {
|
||||||
Some('h') => {
|
'h' => {
|
||||||
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
||||||
let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap();
|
let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap();
|
||||||
stream.write_all(&api_command).unwrap();
|
stream.write_all(&api_command).unwrap();
|
||||||
}
|
}
|
||||||
Some('v') => {
|
'v' => {
|
||||||
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
||||||
let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap();
|
let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap();
|
||||||
stream.write_all(&api_command).unwrap();
|
stream.write_all(&api_command).unwrap();
|
||||||
@ -79,9 +78,8 @@ pub fn main() {
|
|||||||
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
||||||
let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap();
|
let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap();
|
||||||
stream.write_all(&api_command).unwrap();
|
stream.write_all(&api_command).unwrap();
|
||||||
} else if opts.open_file.is_some() {
|
} else if let Some(file_to_open) = opts.open_file {
|
||||||
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
let mut stream = UnixStream::connect(MOSAIC_IPC_PIPE).unwrap();
|
||||||
let file_to_open = opts.open_file.unwrap();
|
|
||||||
let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap();
|
let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap();
|
||||||
stream.write_all(&api_command).unwrap();
|
stream.write_all(&api_command).unwrap();
|
||||||
} else {
|
} else {
|
||||||
@ -99,7 +97,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
|
|||||||
|
|
||||||
let command_is_executing = CommandIsExecuting::new();
|
let command_is_executing = CommandIsExecuting::new();
|
||||||
|
|
||||||
let _ = delete_log_dir();
|
delete_log_dir().unwrap();
|
||||||
delete_log_file().unwrap();
|
delete_log_file().unwrap();
|
||||||
|
|
||||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||||
@ -130,6 +128,10 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
|
|||||||
os_input.clone(),
|
os_input.clone(),
|
||||||
opts.debug,
|
opts.debug,
|
||||||
);
|
);
|
||||||
|
let maybe_layout = opts.layout.and_then(|layout_path| {
|
||||||
|
let layout = Layout::new(layout_path);
|
||||||
|
Some(layout)
|
||||||
|
});
|
||||||
|
|
||||||
active_threads.push(
|
active_threads.push(
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
@ -137,26 +139,12 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: Opt) {
|
|||||||
.spawn({
|
.spawn({
|
||||||
let mut command_is_executing = command_is_executing.clone();
|
let mut command_is_executing = command_is_executing.clone();
|
||||||
move || {
|
move || {
|
||||||
match opts.layout {
|
if let Some(layout) = maybe_layout {
|
||||||
Some(layout_path) => {
|
pty_bus.spawn_terminals_for_layout(layout);
|
||||||
use std::fs::File;
|
} else {
|
||||||
let mut layout_file = File::open(&layout_path)
|
pty_bus.spawn_terminal_vertically(None);
|
||||||
.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 {
|
loop {
|
||||||
let event = pty_bus
|
let event = pty_bus
|
||||||
.receive_pty_instructions
|
.receive_pty_instructions
|
||||||
|
16
src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml
vendored
Normal file
16
src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
direction: Horizontal
|
||||||
|
parts:
|
||||||
|
- direction: Vertical
|
||||||
|
parts:
|
||||||
|
- direction: Horizontal
|
||||||
|
split_size:
|
||||||
|
Percent: 20
|
||||||
|
- direction: Horizontal
|
||||||
|
split_size:
|
||||||
|
Percent: 50
|
||||||
|
split_size:
|
||||||
|
Percent: 80
|
||||||
|
- direction: Vertical
|
||||||
|
split_size:
|
||||||
|
Percent: 20
|
16
src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml
vendored
Normal file
16
src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
direction: Horizontal
|
||||||
|
parts:
|
||||||
|
- direction: Vertical
|
||||||
|
parts:
|
||||||
|
- direction: Horizontal
|
||||||
|
split_size:
|
||||||
|
Percent: 20
|
||||||
|
- direction: Horizontal
|
||||||
|
split_size:
|
||||||
|
Percent: 90
|
||||||
|
split_size:
|
||||||
|
Percent: 80
|
||||||
|
- direction: Vertical
|
||||||
|
split_size:
|
||||||
|
Percent: 20
|
@ -1,4 +1,5 @@
|
|||||||
use ::insta::assert_snapshot;
|
use ::insta::assert_snapshot;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::terminal_pane::PositionAndSize;
|
use crate::terminal_pane::PositionAndSize;
|
||||||
use crate::tests::fakes::FakeInputOutput;
|
use crate::tests::fakes::FakeInputOutput;
|
||||||
@ -20,11 +21,11 @@ pub fn accepts_basic_layout() {
|
|||||||
};
|
};
|
||||||
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
fake_input_output.add_terminal_input(&[COMMAND_TOGGLE, COMMAND_TOGGLE, QUIT]);
|
fake_input_output.add_terminal_input(&[COMMAND_TOGGLE, COMMAND_TOGGLE, QUIT]);
|
||||||
use std::path::PathBuf;
|
|
||||||
let mut opts = Opt::default();
|
let mut opts = Opt::default();
|
||||||
opts.layout = Some(PathBuf::from(
|
opts.layout = Some(PathBuf::from(
|
||||||
"src/tests/fixtures/layouts/three-panes-with-nesting.yaml",
|
"src/tests/fixtures/layouts/three-panes-with-nesting.yaml",
|
||||||
));
|
));
|
||||||
|
|
||||||
start(Box::new(fake_input_output.clone()), opts);
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
let output_frames = fake_input_output
|
let output_frames = fake_input_output
|
||||||
.stdout_writer
|
.stdout_writer
|
||||||
@ -49,3 +50,43 @@ pub fn accepts_basic_layout() {
|
|||||||
assert_snapshot!(next_to_last_snapshot);
|
assert_snapshot!(next_to_last_snapshot);
|
||||||
assert_snapshot!(last_snapshot);
|
assert_snapshot!(last_snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "The total percent for each part should equal 100.")]
|
||||||
|
pub fn should_throw_for_more_than_100_percent_total() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[QUIT]);
|
||||||
|
|
||||||
|
let mut opts = Opt::default();
|
||||||
|
opts.layout = Some(PathBuf::from(
|
||||||
|
"src/tests/fixtures/layouts/parts-total-more-than-100-percent.yaml",
|
||||||
|
));
|
||||||
|
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "The total percent for each part should equal 100.")]
|
||||||
|
pub fn should_throw_for_less_than_100_percent_total() {
|
||||||
|
let fake_win_size = PositionAndSize {
|
||||||
|
columns: 121,
|
||||||
|
rows: 20,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
let mut fake_input_output = get_fake_os_input(&fake_win_size);
|
||||||
|
fake_input_output.add_terminal_input(&[QUIT]);
|
||||||
|
|
||||||
|
let mut opts = Opt::default();
|
||||||
|
opts.layout = Some(PathBuf::from(
|
||||||
|
"src/tests/fixtures/layouts/parts-total-less-than-100-percent.yaml",
|
||||||
|
));
|
||||||
|
|
||||||
|
start(Box::new(fake_input_output.clone()), opts);
|
||||||
|
}
|
||||||
|
@ -38,7 +38,6 @@ pub fn delete_log_dir() -> io::Result<()> {
|
|||||||
if fs::metadata(MOSAIC_TMP_LOG_DIR).is_ok() {
|
if fs::metadata(MOSAIC_TMP_LOG_DIR).is_ok() {
|
||||||
fs::remove_dir_all(MOSAIC_TMP_LOG_DIR)?;
|
fs::remove_dir_all(MOSAIC_TMP_LOG_DIR)?;
|
||||||
}
|
}
|
||||||
fs::create_dir_all(MOSAIC_TMP_LOG_DIR)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user