Fortran · 11838 bytes Raw Blame History
1 module tab_manager_mod
2 use terminal_mod
3 use pty_mod
4 use parser_mod
5 use pane_mod
6 use layout_mod
7 implicit none
8 private
9
10 public :: tab_t, tab_manager_t
11 public :: tab_manager_init, tab_manager_destroy
12 public :: tab_manager_add, tab_manager_close
13 public :: tab_manager_switch, tab_manager_next, tab_manager_prev
14 public :: tab_manager_has_tabs, tab_manager_get_active_term, tab_manager_get_active_pty
15 public :: tab_manager_get_active_pane
16 public :: tab_manager_split_pane_v, tab_manager_split_pane_h
17 public :: tab_manager_close_pane
18 public :: tab_manager_navigate_pane
19 public :: tab_manager_recalculate_layout
20
21 ! Maximum number of tabs supported
22 integer, parameter :: MAX_TABS = 32
23
24 type :: tab_t
25 type(pane_t), allocatable :: panes(:)
26 integer :: active_pane = 1
27 integer :: pane_count = 0
28 character(len=256) :: title = ''
29 logical :: active = .false.
30 end type tab_t
31
32 type :: tab_manager_t
33 type(tab_t), allocatable :: tabs(:)
34 integer :: active_index = 0
35 integer :: count = 0
36 integer :: bar_height = 28 ! pixels
37 integer :: term_rows = 24
38 integer :: term_cols = 80
39 ! Stored for layout recalculation
40 integer :: cell_width = 10
41 integer :: cell_height = 18
42 end type tab_manager_t
43
44 contains
45
46 ! Initialize tab manager with one tab
47 subroutine tab_manager_init(mgr, rows, cols)
48 type(tab_manager_t), intent(inout) :: mgr
49 integer, intent(in) :: rows, cols
50
51 mgr%term_rows = rows
52 mgr%term_cols = cols
53 mgr%count = 0
54 mgr%active_index = 0
55
56 allocate(mgr%tabs(MAX_TABS))
57
58 ! Create the first tab
59 call tab_manager_add(mgr)
60 end subroutine tab_manager_init
61
62 ! Destroy tab manager and all tabs
63 subroutine tab_manager_destroy(mgr)
64 type(tab_manager_t), intent(inout) :: mgr
65 integer :: i, j
66
67 do i = 1, mgr%count
68 do j = 1, mgr%tabs(i)%pane_count
69 call pane_destroy(mgr%tabs(i)%panes(j))
70 end do
71 if (allocated(mgr%tabs(i)%panes)) deallocate(mgr%tabs(i)%panes)
72 end do
73
74 if (allocated(mgr%tabs)) deallocate(mgr%tabs)
75 mgr%count = 0
76 mgr%active_index = 0
77 end subroutine tab_manager_destroy
78
79 ! Add a new tab with a single pane
80 subroutine tab_manager_add(mgr)
81 type(tab_manager_t), intent(inout) :: mgr
82 integer :: idx
83 character(len=32) :: title_buf
84
85 if (mgr%count >= MAX_TABS) then
86 print *, "Warning: Maximum number of tabs reached"
87 return
88 end if
89
90 idx = mgr%count + 1
91
92 ! Allocate panes array for this tab
93 allocate(mgr%tabs(idx)%panes(MAX_PANES))
94 mgr%tabs(idx)%pane_count = 0
95 mgr%tabs(idx)%active_pane = 1
96
97 ! Create the first pane in this tab
98 mgr%tabs(idx)%pane_count = 1
99 call pane_init(mgr%tabs(idx)%panes(1), mgr%term_rows, mgr%term_cols)
100
101 if (.not. mgr%tabs(idx)%panes(1)%pty%active) then
102 print *, "Error: Could not open PTY for new tab"
103 call pane_destroy(mgr%tabs(idx)%panes(1))
104 deallocate(mgr%tabs(idx)%panes)
105 return
106 end if
107
108 ! Mark pane as active
109 mgr%tabs(idx)%panes(1)%active = .true.
110
111 ! Set default title
112 write(title_buf, '(A,I0)') 'Tab ', idx
113 mgr%tabs(idx)%title = trim(title_buf)
114
115 ! Mark as active and update state
116 mgr%tabs(idx)%active = .true.
117 mgr%count = idx
118
119 ! Switch to the new tab
120 call tab_manager_switch(mgr, idx)
121 end subroutine tab_manager_add
122
123 ! Close a tab by index (closes all panes in the tab)
124 subroutine tab_manager_close(mgr, idx)
125 type(tab_manager_t), intent(inout) :: mgr
126 integer, intent(in) :: idx
127 integer :: i, j
128
129 if (idx < 1 .or. idx > mgr%count) return
130
131 ! Destroy all panes in this tab
132 do j = 1, mgr%tabs(idx)%pane_count
133 call pane_destroy(mgr%tabs(idx)%panes(j))
134 end do
135 if (allocated(mgr%tabs(idx)%panes)) deallocate(mgr%tabs(idx)%panes)
136
137 ! Shift remaining tabs down
138 do i = idx, mgr%count - 1
139 mgr%tabs(i) = mgr%tabs(i + 1)
140 end do
141
142 mgr%count = mgr%count - 1
143
144 ! Update active index if needed
145 if (mgr%count == 0) then
146 mgr%active_index = 0
147 else if (mgr%active_index > mgr%count) then
148 mgr%active_index = mgr%count
149 else if (mgr%active_index >= idx .and. mgr%active_index > 1) then
150 mgr%active_index = mgr%active_index - 1
151 end if
152
153 ! Update active flags
154 do i = 1, mgr%count
155 mgr%tabs(i)%active = (i == mgr%active_index)
156 end do
157 end subroutine tab_manager_close
158
159 ! Split the active pane vertically (side-by-side)
160 subroutine tab_manager_split_pane_v(mgr)
161 type(tab_manager_t), intent(inout) :: mgr
162 integer :: tab_idx, pane_idx, new_idx
163
164 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
165 tab_idx = mgr%active_index
166 pane_idx = mgr%tabs(tab_idx)%active_pane
167
168 new_idx = layout_split_vertical(mgr%tabs(tab_idx)%panes, &
169 mgr%tabs(tab_idx)%pane_count, &
170 pane_idx, &
171 mgr%term_rows, mgr%term_cols)
172
173 if (new_idx > 0) then
174 ! Switch focus to the new pane
175 mgr%tabs(tab_idx)%panes(pane_idx)%active = .false.
176 mgr%tabs(tab_idx)%panes(new_idx)%active = .true.
177 mgr%tabs(tab_idx)%active_pane = new_idx
178 end if
179 end subroutine tab_manager_split_pane_v
180
181 ! Split the active pane horizontally (stacked)
182 subroutine tab_manager_split_pane_h(mgr)
183 type(tab_manager_t), intent(inout) :: mgr
184 integer :: tab_idx, pane_idx, new_idx
185
186 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
187 tab_idx = mgr%active_index
188 pane_idx = mgr%tabs(tab_idx)%active_pane
189
190 new_idx = layout_split_horizontal(mgr%tabs(tab_idx)%panes, &
191 mgr%tabs(tab_idx)%pane_count, &
192 pane_idx, &
193 mgr%term_rows, mgr%term_cols)
194
195 if (new_idx > 0) then
196 ! Switch focus to the new pane
197 mgr%tabs(tab_idx)%panes(pane_idx)%active = .false.
198 mgr%tabs(tab_idx)%panes(new_idx)%active = .true.
199 mgr%tabs(tab_idx)%active_pane = new_idx
200 end if
201 end subroutine tab_manager_split_pane_h
202
203 ! Close the active pane in the active tab
204 ! Returns .true. if a pane was closed, .false. if tab should be closed
205 subroutine tab_manager_close_pane(mgr, should_close_tab)
206 type(tab_manager_t), intent(inout) :: mgr
207 logical, intent(out) :: should_close_tab
208 integer :: tab_idx, pane_idx, new_active
209
210 should_close_tab = .false.
211
212 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
213 tab_idx = mgr%active_index
214
215 ! If only one pane, signal to close the tab instead
216 if (mgr%tabs(tab_idx)%pane_count <= 1) then
217 should_close_tab = .true.
218 return
219 end if
220
221 pane_idx = mgr%tabs(tab_idx)%active_pane
222
223 ! Determine new active pane before removal
224 if (pane_idx > 1) then
225 new_active = pane_idx - 1
226 else
227 new_active = 1
228 end if
229
230 ! Remove the pane
231 call layout_remove_pane(mgr%tabs(tab_idx)%panes, &
232 mgr%tabs(tab_idx)%pane_count, &
233 pane_idx)
234
235 ! Update active pane
236 if (new_active > mgr%tabs(tab_idx)%pane_count) then
237 new_active = mgr%tabs(tab_idx)%pane_count
238 end if
239 mgr%tabs(tab_idx)%active_pane = new_active
240
241 ! Update active flags
242 do pane_idx = 1, mgr%tabs(tab_idx)%pane_count
243 mgr%tabs(tab_idx)%panes(pane_idx)%active = (pane_idx == new_active)
244 end do
245 end subroutine tab_manager_close_pane
246
247 ! Navigate to a pane in the given direction
248 subroutine tab_manager_navigate_pane(mgr, direction)
249 type(tab_manager_t), intent(inout) :: mgr
250 integer, intent(in) :: direction
251 integer :: tab_idx, neighbor_idx, old_active
252
253 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
254 tab_idx = mgr%active_index
255
256 if (mgr%tabs(tab_idx)%pane_count < 2) return
257
258 neighbor_idx = layout_find_neighbor(mgr%tabs(tab_idx)%panes, &
259 mgr%tabs(tab_idx)%pane_count, &
260 mgr%tabs(tab_idx)%active_pane, &
261 direction)
262
263 if (neighbor_idx > 0) then
264 old_active = mgr%tabs(tab_idx)%active_pane
265 mgr%tabs(tab_idx)%panes(old_active)%active = .false.
266 mgr%tabs(tab_idx)%panes(neighbor_idx)%active = .true.
267 mgr%tabs(tab_idx)%active_pane = neighbor_idx
268 end if
269 end subroutine tab_manager_navigate_pane
270
271 ! Recalculate layout for all panes in the active tab
272 subroutine tab_manager_recalculate_layout(mgr, x, y, w, h, cell_w, cell_h)
273 type(tab_manager_t), intent(inout) :: mgr
274 integer, intent(in) :: x, y, w, h
275 integer, intent(in) :: cell_w, cell_h
276 integer :: tab_idx
277
278 ! Store cell dimensions for later use
279 mgr%cell_width = cell_w
280 mgr%cell_height = cell_h
281
282 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
283 tab_idx = mgr%active_index
284
285 call layout_recalculate(mgr%tabs(tab_idx)%panes, &
286 mgr%tabs(tab_idx)%pane_count, &
287 x, y, w, h, cell_w, cell_h)
288 end subroutine tab_manager_recalculate_layout
289
290 ! Switch to a specific tab
291 subroutine tab_manager_switch(mgr, idx)
292 type(tab_manager_t), intent(inout) :: mgr
293 integer, intent(in) :: idx
294 integer :: i
295
296 if (idx < 1 .or. idx > mgr%count) return
297
298 ! Update active flags
299 do i = 1, mgr%count
300 mgr%tabs(i)%active = (i == idx)
301 end do
302
303 mgr%active_index = idx
304 end subroutine tab_manager_switch
305
306 ! Switch to next tab (wrapping)
307 subroutine tab_manager_next(mgr)
308 type(tab_manager_t), intent(inout) :: mgr
309 integer :: next_idx
310
311 if (mgr%count <= 1) return
312
313 next_idx = mgr%active_index + 1
314 if (next_idx > mgr%count) next_idx = 1
315
316 call tab_manager_switch(mgr, next_idx)
317 end subroutine tab_manager_next
318
319 ! Switch to previous tab (wrapping)
320 subroutine tab_manager_prev(mgr)
321 type(tab_manager_t), intent(inout) :: mgr
322 integer :: prev_idx
323
324 if (mgr%count <= 1) return
325
326 prev_idx = mgr%active_index - 1
327 if (prev_idx < 1) prev_idx = mgr%count
328
329 call tab_manager_switch(mgr, prev_idx)
330 end subroutine tab_manager_prev
331
332 ! Check if there are any tabs
333 function tab_manager_has_tabs(mgr) result(has)
334 type(tab_manager_t), intent(in) :: mgr
335 logical :: has
336
337 has = mgr%count > 0
338 end function tab_manager_has_tabs
339
340 ! Get pointer to active pane
341 function tab_manager_get_active_pane(mgr) result(pane_ptr)
342 type(tab_manager_t), intent(in), target :: mgr
343 type(pane_t), pointer :: pane_ptr
344 integer :: tab_idx, pane_idx
345
346 pane_ptr => null()
347
348 if (mgr%active_index < 1 .or. mgr%active_index > mgr%count) return
349 tab_idx = mgr%active_index
350
351 pane_idx = mgr%tabs(tab_idx)%active_pane
352 if (pane_idx < 1 .or. pane_idx > mgr%tabs(tab_idx)%pane_count) return
353
354 pane_ptr => mgr%tabs(tab_idx)%panes(pane_idx)
355 end function tab_manager_get_active_pane
356
357 ! Get pointer to active terminal (through active pane)
358 function tab_manager_get_active_term(mgr) result(term_ptr)
359 type(tab_manager_t), intent(in), target :: mgr
360 type(terminal_t), pointer :: term_ptr
361 type(pane_t), pointer :: pane_ptr
362
363 term_ptr => null()
364 pane_ptr => tab_manager_get_active_pane(mgr)
365 if (associated(pane_ptr)) then
366 term_ptr => pane_ptr%term
367 end if
368 end function tab_manager_get_active_term
369
370 ! Get pointer to active PTY (through active pane)
371 function tab_manager_get_active_pty(mgr) result(pty_ptr)
372 type(tab_manager_t), intent(in), target :: mgr
373 type(pty_t), pointer :: pty_ptr
374 type(pane_t), pointer :: pane_ptr
375
376 pty_ptr => null()
377 pane_ptr => tab_manager_get_active_pane(mgr)
378 if (associated(pane_ptr)) then
379 pty_ptr => pane_ptr%pty
380 end if
381 end function tab_manager_get_active_pty
382
383 end module tab_manager_mod
384