Fortran · 8401 bytes Raw Blame History
1 ! Tab Manager Module for Sniffly
2 ! Manages multiple tabs with independent scan states and navigation history
3 module tab_manager
4 use, intrinsic :: iso_c_binding
5 use iso_fortran_env, only: int64
6 use types, only: file_node
7 implicit none
8 private
9
10 public :: tab_state, tabs, active_tab_index, num_tabs, MAX_TABS, MAX_HISTORY
11 public :: init_tab_manager, create_tab, close_tab, switch_to_tab
12 public :: get_active_tab, get_tab, get_path_basename
13
14 ! Constants
15 integer, parameter :: MAX_TABS = 5
16 integer, parameter :: MAX_HISTORY = 50
17 integer, parameter :: MAX_PATH_DEPTH = 100
18
19 ! Scan state (embedded in each tab)
20 type :: queue_entry
21 character(len=512) :: path
22 integer :: node_index
23 integer :: parent_id
24 integer :: depth
25 end type queue_entry
26
27 type :: scan_state_type
28 logical :: active
29 integer :: total_dirs_found
30 integer :: dirs_scanned
31 real :: progress
32 type(queue_entry), dimension(10000) :: queue
33 integer :: queue_head
34 integer :: queue_tail
35 integer :: queue_size
36 type(file_node), pointer :: root => null()
37 character(len=512) :: root_path
38 end type scan_state_type
39
40 ! Per-tab state encapsulation
41 type :: tab_state
42 ! Identity
43 integer :: tab_id
44 character(len=256) :: label ! Tab display name
45
46 ! Path and navigation
47 character(len=512) :: scan_path
48 character(len=512), dimension(MAX_HISTORY) :: nav_history
49 integer :: nav_history_count
50 integer :: nav_history_pos
51 logical :: navigating_history
52
53 ! Synthetic navigation (for future use)
54 logical :: pending_synthetic_nav
55 character(len=512) :: pending_synthetic_child_name
56 integer :: synthetic_nav_attempts
57
58 ! Tree view state
59 type(file_node), pointer :: root_node => null()
60 type(file_node), pointer :: current_view_node => null()
61 logical :: has_data
62 integer :: selected_index
63
64 ! Layout state
65 logical :: layout_calculated
66 integer :: last_width
67 integer :: last_height
68
69 ! View settings
70 logical :: show_file_extensions
71 logical :: use_age_based_coloring
72 logical :: show_allocated_size
73 logical :: show_hidden_files
74 logical :: use_cushion_shading
75
76 ! Path navigation breadcrumb
77 character(len=256), dimension(MAX_PATH_DEPTH) :: path_names
78 integer :: path_depth
79
80 ! Scan state
81 type(scan_state_type) :: scan_state
82 integer(c_int) :: scan_idle_id
83 end type tab_state
84
85 ! Global tab management
86 type(tab_state), dimension(MAX_TABS), save, target :: tabs
87 integer, save :: active_tab_index = 1
88 integer, save :: num_tabs = 0
89 integer, save :: next_tab_id = 1
90
91 contains
92
93 ! Initialize tab manager (call once at startup)
94 subroutine init_tab_manager()
95 active_tab_index = 1
96 num_tabs = 0
97 next_tab_id = 1
98 print *, "Tab manager initialized"
99 end subroutine init_tab_manager
100
101 ! Create a new tab with given path
102 function create_tab(path) result(tab_index)
103 character(len=*), intent(in) :: path
104 integer :: tab_index
105
106 ! Check if we've reached the limit
107 if (num_tabs >= MAX_TABS) then
108 print *, "ERROR: Maximum tabs reached (", MAX_TABS, ")"
109 tab_index = -1
110 return
111 end if
112
113 ! Increment tab count
114 num_tabs = num_tabs + 1
115 tab_index = num_tabs
116
117 ! Initialize tab state
118 tabs(tab_index)%tab_id = next_tab_id
119 next_tab_id = next_tab_id + 1
120
121 tabs(tab_index)%scan_path = trim(path)
122 tabs(tab_index)%label = get_path_basename(path)
123
124 ! Initialize navigation history
125 tabs(tab_index)%nav_history_count = 0
126 tabs(tab_index)%nav_history_pos = 0
127 tabs(tab_index)%navigating_history = .false.
128
129 ! Initialize synthetic nav
130 tabs(tab_index)%pending_synthetic_nav = .false.
131 tabs(tab_index)%pending_synthetic_child_name = ""
132 tabs(tab_index)%synthetic_nav_attempts = 0
133
134 ! Initialize tree pointers
135 tabs(tab_index)%root_node => null()
136 tabs(tab_index)%current_view_node => null()
137 tabs(tab_index)%has_data = .false.
138 tabs(tab_index)%selected_index = 0
139
140 ! Initialize layout
141 tabs(tab_index)%layout_calculated = .false.
142 tabs(tab_index)%last_width = 0
143 tabs(tab_index)%last_height = 0
144
145 ! Initialize view settings (defaults)
146 tabs(tab_index)%show_file_extensions = .true.
147 tabs(tab_index)%use_age_based_coloring = .false.
148 tabs(tab_index)%show_allocated_size = .false.
149 tabs(tab_index)%show_hidden_files = .true.
150 tabs(tab_index)%use_cushion_shading = .true.
151
152 ! Initialize path navigation
153 tabs(tab_index)%path_depth = 0
154
155 ! Initialize scan state
156 tabs(tab_index)%scan_state%active = .false.
157 tabs(tab_index)%scan_state%total_dirs_found = 0
158 tabs(tab_index)%scan_state%dirs_scanned = 0
159 tabs(tab_index)%scan_state%progress = 0.0
160 tabs(tab_index)%scan_state%queue_head = 1
161 tabs(tab_index)%scan_state%queue_tail = 1
162 tabs(tab_index)%scan_state%queue_size = 0
163 tabs(tab_index)%scan_state%root => null()
164 tabs(tab_index)%scan_state%root_path = ""
165 tabs(tab_index)%scan_idle_id = 0_c_int
166
167 print *, "Created tab ", tab_index, " for path: ", trim(path)
168 end function create_tab
169
170 ! Close a tab (with cleanup)
171 subroutine close_tab(tab_index)
172 integer, intent(in) :: tab_index
173 integer :: i
174
175 if (tab_index < 1 .or. tab_index > num_tabs) then
176 print *, "ERROR: Invalid tab index: ", tab_index
177 return
178 end if
179
180 ! Prevent closing last tab
181 if (num_tabs == 1) then
182 print *, "ERROR: Cannot close last tab"
183 return
184 end if
185
186 print *, "Closing tab ", tab_index
187
188 ! TODO: Cancel any active scan for this tab
189 ! TODO: Deallocate file tree recursively
190
191 ! Shift tabs down to fill gap
192 do i = tab_index, num_tabs - 1
193 tabs(i) = tabs(i + 1)
194 end do
195
196 ! Decrement count
197 num_tabs = num_tabs - 1
198
199 ! Adjust active tab index if needed
200 if (active_tab_index == tab_index) then
201 ! Closed active tab - activate next tab (or previous if was last)
202 if (active_tab_index > num_tabs) then
203 active_tab_index = num_tabs
204 end if
205 print *, "Switched to tab ", active_tab_index
206 else if (active_tab_index > tab_index) then
207 ! Closed tab before active - adjust index
208 active_tab_index = active_tab_index - 1
209 end if
210 end subroutine close_tab
211
212 ! Switch to a different tab
213 subroutine switch_to_tab(tab_index)
214 integer, intent(in) :: tab_index
215
216 if (tab_index < 1 .or. tab_index > num_tabs) then
217 print *, "ERROR: Invalid tab index: ", tab_index
218 return
219 end if
220
221 if (tab_index == active_tab_index) then
222 ! Already on this tab
223 return
224 end if
225
226 print *, "Switching from tab ", active_tab_index, " to tab ", tab_index
227
228 ! Just update the index - actual UI update happens in caller
229 active_tab_index = tab_index
230 end subroutine switch_to_tab
231
232 ! Get pointer to active tab
233 function get_active_tab() result(tab)
234 type(tab_state), pointer :: tab
235 if (num_tabs == 0) then
236 tab => null()
237 else
238 tab => tabs(active_tab_index)
239 end if
240 end function get_active_tab
241
242 ! Get pointer to specific tab
243 function get_tab(tab_index) result(tab)
244 integer, intent(in) :: tab_index
245 type(tab_state), pointer :: tab
246 if (tab_index < 1 .or. tab_index > num_tabs) then
247 tab => null()
248 else
249 tab => tabs(tab_index)
250 end if
251 end function get_tab
252
253 ! Helper: Extract basename from path
254 function get_path_basename(path) result(basename)
255 character(len=*), intent(in) :: path
256 character(len=256) :: basename
257 integer :: last_slash
258
259 ! Special case: empty path (new empty tabs)
260 if (len_trim(path) == 0) then
261 basename = "Empty"
262 return
263 end if
264
265 ! Find last slash
266 last_slash = index(trim(path), "/", back=.true.)
267
268 if (last_slash == 0) then
269 ! No slash - use whole path
270 basename = trim(path)
271 else if (last_slash == len_trim(path)) then
272 ! Trailing slash - find previous slash
273 last_slash = index(trim(path(1:len_trim(path)-1)), "/", back=.true.)
274 if (last_slash == 0) then
275 basename = trim(path(1:len_trim(path)-1))
276 else
277 basename = trim(path(last_slash+1:len_trim(path)-1))
278 end if
279 else
280 ! Normal case
281 basename = trim(path(last_slash+1:))
282 end if
283
284 ! Special case: root
285 if (len_trim(basename) == 0) then
286 basename = "/"
287 end if
288 end function get_path_basename
289
290 end module tab_manager
291