@@ -42,6 +42,17 @@ use crate::keymap::Keymap;
4242use crate :: theme:: Theme ;
4343use crate :: tui;
4444
45+ /// Which divider the user is dragging.
46+ #[ derive( Debug , Clone , Copy ) ]
47+ enum DragTarget {
48+ /// Vertical divider between tree panel and detail/command panels.
49+ Tree ,
50+ /// Horizontal divider between detail panel and command panel.
51+ Detail ,
52+ /// Vertical divider in connections layout.
53+ ConnTree ,
54+ }
55+
4556/// Backend for a connection tab — either live LDAP or offline/example.
4657enum TabBackend {
4758 Live ( Arc < Mutex < LdapConnection > > ) ,
@@ -54,6 +65,7 @@ struct ConnectionTab {
5465 label : String ,
5566 host : String ,
5667 server_type : String ,
68+ subschema_dn : Option < String > ,
5769 backend : TabBackend ,
5870 directory_tree : DirectoryTree ,
5971 schema : Option < SchemaCache > ,
@@ -116,6 +128,12 @@ pub struct App {
116128 conn_tree_area : Option < Rect > ,
117129 conn_form_area : Option < Rect > ,
118130
131+ // Resizable panel splits (percentages, 10..=90)
132+ tree_split_pct : u16 , // tree panel width as % of content area
133+ detail_split_pct : u16 , // detail panel height as % of right area
134+ conn_tree_split_pct : u16 , // connections tree width as % of content area
135+ drag_target : Option < DragTarget > ,
136+
119137 // Async communication
120138 action_tx : tokio:: sync:: mpsc:: UnboundedSender < Action > ,
121139 action_rx : tokio:: sync:: mpsc:: UnboundedReceiver < Action > ,
@@ -166,6 +184,10 @@ impl App {
166184 layout_bar_area : None ,
167185 conn_tree_area : None ,
168186 conn_form_area : None ,
187+ tree_split_pct : 25 ,
188+ detail_split_pct : 75 ,
189+ conn_tree_split_pct : 30 ,
190+ drag_target : None ,
169191 action_tx,
170192 action_rx,
171193 }
@@ -245,6 +267,7 @@ impl App {
245267 label : "Example Directory" . to_string ( ) ,
246268 host : "contoso.example" . to_string ( ) ,
247269 server_type : "Active Directory (Example)" . to_string ( ) ,
270+ subschema_dn : None ,
248271 backend : TabBackend :: Offline ( offline) ,
249272 directory_tree : DirectoryTree :: new ( base_dn. clone ( ) ) ,
250273 schema : Some ( schema) ,
@@ -280,16 +303,16 @@ impl App {
280303 }
281304
282305 // Read RootDSE to detect server type and auto-discover base DN
283- let server_type_str = match conn. read_root_dse ( ) . await {
306+ let ( server_type_str, subschema_dn ) = match conn. read_root_dse ( ) . await {
284307 Ok ( root_dse) => {
285308 let st = root_dse. server_type . to_string ( ) ;
286309 self . command_panel
287310 . push_message ( format ! ( "Server type: {}" , st) ) ;
288- st
311+ ( st , root_dse . subschema_subentry )
289312 }
290313 Err ( e) => {
291314 debug ! ( "RootDSE read failed (non-fatal): {}" , e) ;
292- "LDAP" . to_string ( )
315+ ( "LDAP" . to_string ( ) , None )
293316 }
294317 } ;
295318
@@ -310,6 +333,7 @@ impl App {
310333 label : label. clone ( ) ,
311334 host,
312335 server_type : server_type_str,
336+ subschema_dn,
313337 backend : TabBackend :: Live ( connection) ,
314338 directory_tree,
315339 schema : None ,
@@ -539,9 +563,10 @@ impl App {
539563 }
540564 TabBackend :: Live ( connection) => {
541565 let connection = connection. clone ( ) ;
566+ let subschema_dn = tab. subschema_dn . clone ( ) ;
542567 tokio:: spawn ( async move {
543568 let mut conn = connection. lock ( ) . await ;
544- match conn. load_schema ( None ) . await {
569+ match conn. load_schema ( subschema_dn . as_deref ( ) ) . await {
545570 Ok ( schema) => {
546571 let _ = tx. send ( Action :: SchemaLoaded ( conn_id, Box :: new ( schema) ) ) ;
547572 }
@@ -980,14 +1005,21 @@ impl App {
9801005 Ok ( ( ) )
9811006 }
9821007
983- fn handle_mouse ( & self , mouse : crossterm:: event:: MouseEvent ) -> Action {
984- // Only handle click events, ignore popups
1008+ fn handle_mouse ( & mut self , mouse : crossterm:: event:: MouseEvent ) -> Action {
1009+ // Popups block mouse events; also clear any drag
9851010 if self . popup_active ( ) {
1011+ self . drag_target = None ;
9861012 return Action :: None ;
9871013 }
9881014
9891015 match mouse. kind {
9901016 MouseEventKind :: Down ( crossterm:: event:: MouseButton :: Left ) => {
1017+ // Check if click is on a panel divider (start drag)
1018+ if let Some ( target) = self . divider_hit ( mouse. column , mouse. row ) {
1019+ self . drag_target = Some ( target) ;
1020+ return Action :: None ;
1021+ }
1022+
9911023 let pos = Rect :: new ( mouse. column , mouse. row , 1 , 1 ) ;
9921024
9931025 // Check layout bar clicks
@@ -1035,10 +1067,101 @@ impl App {
10351067 }
10361068 Action :: None
10371069 }
1070+ MouseEventKind :: Drag ( crossterm:: event:: MouseButton :: Left ) => {
1071+ if let Some ( target) = self . drag_target {
1072+ self . apply_drag ( target, mouse. column , mouse. row ) ;
1073+ }
1074+ Action :: None
1075+ }
1076+ MouseEventKind :: Up ( _) => {
1077+ self . drag_target = None ;
1078+ Action :: None
1079+ }
10381080 _ => Action :: None ,
10391081 }
10401082 }
10411083
1084+ /// Check if a mouse position is on (or within 1 cell of) a panel divider.
1085+ fn divider_hit ( & self , col : u16 , row : u16 ) -> Option < DragTarget > {
1086+ match self . active_layout {
1087+ ActiveLayout :: Browser => {
1088+ // Vertical divider: right edge of tree panel
1089+ if let Some ( tree) = self . tree_area {
1090+ let divider_col = tree. x + tree. width ;
1091+ if col. abs_diff ( divider_col) <= 1
1092+ && row >= tree. y
1093+ && row < tree. y + tree. height
1094+ {
1095+ return Some ( DragTarget :: Tree ) ;
1096+ }
1097+ }
1098+ // Horizontal divider: bottom edge of detail panel
1099+ if let Some ( detail) = self . detail_area {
1100+ let divider_row = detail. y + detail. height ;
1101+ if row. abs_diff ( divider_row) <= 1
1102+ && col >= detail. x
1103+ && col < detail. x + detail. width
1104+ {
1105+ return Some ( DragTarget :: Detail ) ;
1106+ }
1107+ }
1108+ }
1109+ ActiveLayout :: Connections => {
1110+ // Vertical divider: right edge of connections tree
1111+ if let Some ( ct) = self . conn_tree_area {
1112+ let divider_col = ct. x + ct. width ;
1113+ if col. abs_diff ( divider_col) <= 1
1114+ && row >= ct. y
1115+ && row < ct. y + ct. height
1116+ {
1117+ return Some ( DragTarget :: ConnTree ) ;
1118+ }
1119+ }
1120+ }
1121+ }
1122+ None
1123+ }
1124+
1125+ /// Update split percentages based on the current drag position.
1126+ fn apply_drag ( & mut self , target : DragTarget , col : u16 , row : u16 ) {
1127+ // We need a reference area to compute the percentage from pixel position.
1128+ match target {
1129+ DragTarget :: Tree => {
1130+ if let ( Some ( tree) , Some ( detail) ) = ( self . tree_area , self . detail_area ) {
1131+ let total_w = ( tree. width + detail. width ) as u32 ;
1132+ if total_w == 0 {
1133+ return ;
1134+ }
1135+ let offset = col. saturating_sub ( tree. x ) as u32 ;
1136+ let pct = ( ( offset * 100 ) / total_w) as u16 ;
1137+ self . tree_split_pct = pct. clamp ( 10 , 90 ) ;
1138+ }
1139+ }
1140+ DragTarget :: Detail => {
1141+ if let ( Some ( detail) , Some ( cmd) ) = ( self . detail_area , self . command_area ) {
1142+ let total_h = ( detail. height + cmd. height ) as u32 ;
1143+ if total_h == 0 {
1144+ return ;
1145+ }
1146+ let offset = row. saturating_sub ( detail. y ) as u32 ;
1147+ let pct = ( ( offset * 100 ) / total_h) as u16 ;
1148+ self . detail_split_pct = pct. clamp ( 10 , 90 ) ;
1149+ }
1150+ }
1151+ DragTarget :: ConnTree => {
1152+ if let ( Some ( ct) , Some ( cf) ) = ( self . conn_tree_area , self . conn_form_area ) {
1153+ let total_w = ( ct. width + cf. width ) as u32 ;
1154+ if total_w == 0 {
1155+ return ;
1156+ }
1157+ let offset = col. saturating_sub ( ct. x ) as u32 ;
1158+ let pct = ( ( offset * 100 ) / total_w) as u16 ;
1159+ self . conn_tree_split_pct = pct. clamp ( 10 , 90 ) ;
1160+ }
1161+ }
1162+ }
1163+ }
1164+
10421165 async fn process_action ( & mut self , action : Action ) {
10431166 match action {
10441167 Action :: Quit => {
@@ -1724,17 +1847,19 @@ impl App {
17241847 ActiveLayout :: Browser => {
17251848 self . tab_area = Some ( layout_bar_area) ;
17261849
1727- // Horizontal: tree (25%) | right panels (75%)
1850+ // Horizontal: tree | right panels (draggable split)
1851+ let tp = self . tree_split_pct ;
17281852 let horizontal =
1729- Layout :: horizontal ( [ Constraint :: Percentage ( 25 ) , Constraint :: Percentage ( 75 ) ] )
1853+ Layout :: horizontal ( [ Constraint :: Percentage ( tp ) , Constraint :: Percentage ( 100 - tp ) ] )
17301854 . split ( content_area) ;
17311855
17321856 let tree_area = horizontal[ 0 ] ;
17331857 let right_area = horizontal[ 1 ] ;
17341858
1735- // Right side: detail (75%) | command (25%)
1859+ // Right side: detail | command (draggable split)
1860+ let dp = self . detail_split_pct ;
17361861 let right_vertical =
1737- Layout :: vertical ( [ Constraint :: Percentage ( 75 ) , Constraint :: Percentage ( 25 ) ] )
1862+ Layout :: vertical ( [ Constraint :: Percentage ( dp ) , Constraint :: Percentage ( 100 - dp ) ] )
17381863 . split ( right_area) ;
17391864
17401865 let detail_area = right_vertical[ 0 ] ;
@@ -1783,9 +1908,10 @@ impl App {
17831908 let panels_area = conn_vertical[ 0 ] ;
17841909 let conn_status_area = conn_vertical[ 1 ] ;
17851910
1786- // Horizontal: connections tree (30%) | connection form (70%)
1911+ // Horizontal: connections tree | connection form (draggable split)
1912+ let cp = self . conn_tree_split_pct ;
17871913 let horizontal =
1788- Layout :: horizontal ( [ Constraint :: Percentage ( 30 ) , Constraint :: Percentage ( 70 ) ] )
1914+ Layout :: horizontal ( [ Constraint :: Percentage ( cp ) , Constraint :: Percentage ( 100 - cp ) ] )
17891915 . split ( panels_area) ;
17901916
17911917 let conn_tree_area = horizontal[ 0 ] ;
0 commit comments