2024-08-22 22:25:05 +10:00
use itertools ::Itertools ;
2024-08-23 23:37:58 +10:00
use std ::{ collections ::BTreeMap , rc ::Rc } ;
2024-08-22 22:25:05 +10:00
2024-10-05 19:28:39 +10:00
use crate ::FrameId ;
2024-08-22 22:25:05 +10:00
#[ derive(PartialEq, Eq, Debug, Clone) ]
pub enum TermSplit {
Term {
2024-10-05 19:28:39 +10:00
frame : FrameId ,
2024-08-22 22:25:05 +10:00
} ,
Horizontal {
2024-08-23 23:37:58 +10:00
left : Rc < TermSplit > ,
right : Rc < TermSplit > ,
2024-08-22 22:25:05 +10:00
} ,
Vertical {
2024-08-23 23:37:58 +10:00
top : Rc < TermSplit > ,
bottom : Rc < TermSplit > ,
2024-08-22 22:25:05 +10:00
} ,
}
impl TermSplit {
2024-10-05 19:28:39 +10:00
fn collect_term_frames ( & self , into : & mut BTreeMap < FrameId , usize > ) {
2024-08-22 22:25:05 +10:00
match self {
TermSplit ::Term { frame } = > {
into . entry ( frame . clone ( ) )
. and_modify ( | v | * v + = 1 )
. or_insert ( 1 ) ;
}
TermSplit ::Horizontal { left , right } = > {
left . collect_term_frames ( into ) ;
right . collect_term_frames ( into ) ;
}
TermSplit ::Vertical { top , bottom } = > {
top . collect_term_frames ( into ) ;
bottom . collect_term_frames ( into ) ;
}
}
}
pub fn validate ( & self ) -> Result < ( ) , String > {
2024-10-05 19:28:39 +10:00
let mut frame_count : BTreeMap < FrameId , usize > = BTreeMap ::new ( ) ;
2024-08-22 22:25:05 +10:00
self . collect_term_frames ( & mut frame_count ) ;
let duplicate_terminal = frame_count
. iter ( )
. filter_map ( | ( k , v ) | {
if * v > 1 {
Some ( format! ( " Terminal {} " , k . 0 ) )
} else {
None
}
} )
. join ( " , " ) ;
if ! duplicate_terminal . is_empty ( ) {
Err ( format! (
" Attempt to create layout that duplicates reference to: {} " ,
duplicate_terminal
) ) ? ;
}
Ok ( ( ) )
}
2024-08-23 23:37:58 +10:00
pub fn modify_at_pathstr < F > ( & self , pathstr : & str , mod_with : F ) -> Result < Self , String >
2024-08-22 22:25:05 +10:00
where
2024-08-23 23:37:58 +10:00
F : FnOnce ( & TermSplit ) -> Result < Self , String > ,
2024-08-22 22:25:05 +10:00
{
self . modify_at_pathstr_vec ( & pathstr . chars ( ) . collect ::< Vec < char > > ( ) , mod_with )
}
2024-08-23 23:37:58 +10:00
fn modify_at_pathstr_vec < F > ( & self , pathstr : & [ char ] , mod_with : F ) -> Result < Self , String >
2024-08-22 22:25:05 +10:00
where
2024-08-23 23:37:58 +10:00
F : FnOnce ( & TermSplit ) -> Result < Self , String > ,
2024-08-22 22:25:05 +10:00
{
match self {
TermSplit ::Horizontal { left , right } = > match pathstr . split_first ( ) {
None = > mod_with ( self ) ,
2024-08-23 23:37:58 +10:00
Some ( ( 'l' , path_rest ) ) = > Ok ( TermSplit ::Horizontal {
left : left . modify_at_pathstr_vec ( path_rest , mod_with ) ? . into ( ) ,
right : right . clone ( )
} ) ,
Some ( ( 'r' , path_rest ) ) = > Ok ( TermSplit ::Horizontal {
left : left . clone ( ) ,
right : right . modify_at_pathstr_vec ( path_rest , mod_with ) ? . into ( )
} ) ,
2024-08-22 22:25:05 +10:00
Some ( ( c , path_rest ) ) = > Err ( format! ( " In split path, found {} before {} , which was unexpected for a horizontal split " , c , path_rest . iter ( ) . collect ::< String > ( ) ) )
} ,
TermSplit ::Vertical { top , bottom } = > match pathstr . split_first ( ) {
None = > mod_with ( self ) ,
2024-08-23 23:37:58 +10:00
Some ( ( 't' , path_rest ) ) = > Ok ( TermSplit ::Vertical {
top : top . modify_at_pathstr_vec ( path_rest , mod_with ) ? . into ( ) ,
bottom : bottom . clone ( )
} ) ,
Some ( ( 'b' , path_rest ) ) = > Ok ( TermSplit ::Vertical {
top : top . clone ( ) ,
bottom : bottom . modify_at_pathstr_vec ( path_rest , mod_with ) ? . into ( )
} ) ,
2024-08-22 22:25:05 +10:00
Some ( ( c , path_rest ) ) = > Err ( format! ( " In split path, found {} before {} , which was unexpected for a vertical split " , c , path_rest . iter ( ) . collect ::< String > ( ) ) )
} ,
TermSplit ::Term { .. } = > match pathstr . split_first ( ) {
None = > mod_with ( self ) ,
Some ( _ ) = > Err ( format! ( " In split path, found trailing junk {} after addressing terminal " , pathstr . iter ( ) . collect ::< String > ( ) ) )
}
}
}
2024-10-05 19:28:39 +10:00
pub fn hsplit ( & self , pathstr : & str , new_frame : FrameId ) -> Result < TermSplit , String > {
2024-08-23 23:37:58 +10:00
let new = self . modify_at_pathstr ( pathstr , move | n | {
Ok ( TermSplit ::Horizontal {
2024-08-22 22:25:05 +10:00
left : n . clone ( ) . into ( ) ,
right : TermSplit ::Term { frame : new_frame } . into ( ) ,
2024-08-23 23:37:58 +10:00
} )
2024-08-22 22:25:05 +10:00
} ) ? ;
new . validate ( ) ? ;
2024-08-23 23:37:58 +10:00
Ok ( new )
2024-08-22 22:25:05 +10:00
}
2024-10-05 19:28:39 +10:00
pub fn vsplit ( & self , pathstr : & str , new_frame : FrameId ) -> Result < TermSplit , String > {
2024-08-23 23:37:58 +10:00
let new = self . modify_at_pathstr ( pathstr , move | n | {
Ok ( TermSplit ::Vertical {
2024-08-22 22:25:05 +10:00
top : n . clone ( ) . into ( ) ,
bottom : TermSplit ::Term { frame : new_frame } . into ( ) ,
2024-08-23 23:37:58 +10:00
} )
2024-08-22 22:25:05 +10:00
} ) ? ;
new . validate ( ) ? ;
2024-08-23 23:37:58 +10:00
Ok ( new )
2024-08-22 22:25:05 +10:00
}
2024-08-23 23:37:58 +10:00
pub fn join ( & self , pathstr : & str ) -> Result < TermSplit , String > {
2024-08-22 22:25:05 +10:00
self . modify_at_pathstr ( pathstr , move | n | match n {
TermSplit ::Term { .. } = > {
Err ( " Can only join vertical or horizontal splits, not a terminal " . to_owned ( ) )
}
2024-08-23 23:37:58 +10:00
TermSplit ::Horizontal { left , .. } = > Ok ( ( * * left ) . clone ( ) ) ,
TermSplit ::Vertical { top , .. } = > Ok ( ( * * top ) . clone ( ) ) ,
2024-08-22 22:25:05 +10:00
} )
}
}
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn validate_accepts_valid ( ) {
use TermSplit ::* ;
assert_eq! (
( Vertical {
2024-10-05 19:28:39 +10:00
top : Term { frame : FrameId ( 1 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
bottom : Horizontal {
2024-10-05 19:28:39 +10:00
left : Term { frame : FrameId ( 2 ) } . into ( ) ,
right : Term { frame : FrameId ( 3 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( )
} )
. validate ( ) ,
Ok ( ( ) )
) ;
}
#[ test ]
fn validate_rejects_duplicate ( ) {
use TermSplit ::* ;
assert_eq! (
( Vertical {
2024-10-05 19:28:39 +10:00
top : Term { frame : FrameId ( 1 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
bottom : Horizontal {
2024-10-05 19:28:39 +10:00
left : Term { frame : FrameId ( 1 ) } . into ( ) ,
right : Term { frame : FrameId ( 3 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( )
} )
. validate ( ) ,
Err ( " Attempt to create layout that duplicates reference to: Terminal 1 " . to_owned ( ) )
) ;
assert_eq! (
( Vertical {
2024-10-05 19:28:39 +10:00
top : Term { frame : FrameId ( 42 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
bottom : Horizontal {
2024-10-05 19:28:39 +10:00
left : Term { frame : FrameId ( 1 ) } . into ( ) ,
right : Term { frame : FrameId ( 42 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( )
} )
. validate ( ) ,
Err ( " Attempt to create layout that duplicates reference to: Terminal 42 " . to_owned ( ) )
) ;
}
#[ test ]
fn modify_at_pathstr_works ( ) {
use TermSplit ::* ;
2024-10-05 19:28:39 +10:00
let t = Term { frame : FrameId ( 1 ) } ;
2024-08-22 22:25:05 +10:00
assert_eq! (
2024-10-05 19:28:39 +10:00
t . modify_at_pathstr ( " " , | _v | { Ok ( Term { frame : FrameId ( 2 ) } ) } ) ,
Ok ( Term { frame : FrameId ( 2 ) } )
2024-08-22 22:25:05 +10:00
) ;
assert_eq! (
2024-10-05 19:28:39 +10:00
t . modify_at_pathstr ( " tlr " , | _v | { Ok ( Term { frame : FrameId ( 2 ) } ) } ) ,
2024-08-22 22:25:05 +10:00
Err ( " In split path, found trailing junk tlr after addressing terminal " . to_owned ( ) )
) ;
2024-09-12 20:42:41 +10:00
let t = Vertical {
2024-08-22 22:25:05 +10:00
top : Horizontal {
left : Horizontal {
2024-10-05 19:28:39 +10:00
left : Term { frame : FrameId ( 42 ) } . into ( ) ,
right : Term { frame : FrameId ( 64 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
2024-10-05 19:28:39 +10:00
right : Term { frame : FrameId ( 42 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
bottom : Vertical {
2024-10-05 19:28:39 +10:00
top : Term { frame : FrameId ( 43 ) } . into ( ) ,
bottom : Term { frame : FrameId ( 44 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
} ;
assert_eq! (
2024-10-05 19:28:39 +10:00
t . modify_at_pathstr ( " tlr " , | _v | { Ok ( Term { frame : FrameId ( 2 ) } ) } )
. and_then ( | t | t . modify_at_pathstr ( " bb " , | _v | { Ok ( Term { frame : FrameId ( 3 ) } ) } ) ) ,
2024-09-12 20:42:41 +10:00
Ok ( Vertical {
2024-08-22 22:25:05 +10:00
top : Horizontal {
left : Horizontal {
2024-10-05 19:28:39 +10:00
left : Term { frame : FrameId ( 42 ) } . into ( ) ,
right : Term { frame : FrameId ( 2 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
2024-10-05 19:28:39 +10:00
right : Term { frame : FrameId ( 42 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
bottom : Vertical {
2024-10-05 19:28:39 +10:00
top : Term { frame : FrameId ( 43 ) } . into ( ) ,
bottom : Term { frame : FrameId ( 3 ) } . into ( ) ,
2024-08-22 22:25:05 +10:00
}
. into ( ) ,
2024-09-12 20:42:41 +10:00
} )
2024-08-22 22:25:05 +10:00
) ;
}
}