Fortran · 15043 bytes Raw Blame History
1 ! Welcome menu module
2 ! Displays favorites and recents lists for quick workspace access
3
4 module welcome_menu_module
5 use iso_fortran_env, only: int32
6 use terminal_io_module, only: terminal_write, terminal_move_cursor, terminal_clear_screen
7 use input_handler_module, only: get_key_input
8 use favorites_module, only: favorite_t, favorites_load, favorites_remove
9 use recents_module, only: recent_t, recents_load, recents_remove
10 implicit none
11 private
12
13 public :: show_welcome_menu
14
15 integer, parameter :: MAX_DISPLAY = 20
16
17 contains
18
19 !> Show welcome menu and return selected path
20 subroutine show_welcome_menu(selected_path, cancelled)
21 character(len=:), allocatable, intent(out) :: selected_path
22 logical, intent(out) :: cancelled
23 type(favorite_t), allocatable :: favorites(:)
24 type(recent_t), allocatable :: recents(:)
25 integer :: fav_count, rec_count, max_recents
26 logical :: success, showing_favorites
27 integer :: selected_index, scroll_offset
28 character(len=32) :: key_input
29 integer :: status, rows, cols
30 character(len=512) :: cwd
31
32 cancelled = .false.
33 showing_favorites = .true. ! Start with favorites view
34 selected_index = 0 ! Start at CURRENT DIRECTORY option
35 scroll_offset = 0
36
37 ! Get current working directory for display
38 call get_cwd(cwd)
39
40 ! Load favorites and recents
41 call favorites_load(favorites, fav_count, success)
42 if (.not. success) fav_count = 0
43
44 call recents_load(recents, rec_count, max_recents, success)
45 if (.not. success) rec_count = 0
46
47 ! Get terminal size
48 call terminal_get_size(rows, cols)
49
50 ! Main loop
51 do
52 ! Render menu
53 call render_welcome_menu(favorites, fav_count, recents, rec_count, &
54 showing_favorites, selected_index, scroll_offset, rows, cwd)
55
56 ! Get input
57 call get_key_input(key_input, status)
58 if (status /= 0) cycle
59
60 ! Handle input
61 select case (key_input)
62 case ('up', 'k')
63 if (selected_index > 0) then
64 selected_index = selected_index - 1
65 call adjust_scroll(scroll_offset)
66 end if
67
68 case ('down', 'j')
69 if (showing_favorites) then
70 ! Allow selecting up to fav_count (0 = CWD, 1..fav_count = favorites)
71 if (selected_index < fav_count) then
72 selected_index = selected_index + 1
73 call adjust_scroll(scroll_offset)
74 end if
75 else
76 ! Allow selecting up to rec_count (0 = CWD, 1..rec_count = recents)
77 if (selected_index < rec_count) then
78 selected_index = selected_index + 1
79 call adjust_scroll(scroll_offset)
80 end if
81 end if
82
83 case ('enter', 'right') ! Enter or right arrow
84 ! Handle selection
85 if (selected_index == 0) then
86 ! CURRENT DIRECTORY selected
87 selected_path = "CWD"
88 cancelled = .false.
89 exit
90 else if (showing_favorites .and. fav_count > 0) then
91 ! Favorites list - adjust index to account for CWD at index 0
92 if (selected_index >= 1 .and. selected_index <= fav_count) then
93 ! Check if directory exists
94 if (directory_exists(trim(favorites(selected_index)%path))) then
95 selected_path = trim(favorites(selected_index)%path)
96 cancelled = .false.
97 exit
98 else
99 ! Directory doesn't exist - show warning and remove
100 call handle_deleted_workspace(trim(favorites(selected_index)%path), &
101 showing_favorites, selected_index)
102 ! Reload favorites and recents
103 call favorites_load(favorites, fav_count, success)
104 if (.not. success) fav_count = 0
105 ! Adjust selection if needed
106 if (selected_index > fav_count) selected_index = fav_count
107 if (selected_index < 0) selected_index = 0
108 end if
109 end if
110 else if (.not. showing_favorites .and. rec_count > 0) then
111 ! Recents list - adjust index to account for CWD at index 0
112 if (selected_index >= 1 .and. selected_index <= rec_count) then
113 ! Check if directory exists
114 if (directory_exists(trim(recents(selected_index)%path))) then
115 selected_path = trim(recents(selected_index)%path)
116 cancelled = .false.
117 exit
118 else
119 ! Directory doesn't exist - show warning and remove
120 call handle_deleted_workspace(trim(recents(selected_index)%path), &
121 showing_favorites, selected_index)
122 ! Reload recents
123 call recents_load(recents, rec_count, max_recents, success)
124 if (.not. success) rec_count = 0
125 ! Adjust selection if needed
126 if (selected_index > rec_count) selected_index = rec_count
127 if (selected_index < 0) selected_index = 0
128 end if
129 end if
130 end if
131
132 case ('8')
133 ! Toggle between favorites and recents
134 showing_favorites = .not. showing_favorites
135 selected_index = 0 ! Reset to CURRENT DIRECTORY
136 scroll_offset = 0
137
138 case ('esc', 'q')
139 cancelled = .true.
140 exit
141
142 case ('b')
143 ! Browse filesystem - launch fortress navigator
144 cancelled = .true. ! Signal to launch navigator instead
145 selected_path = "BROWSE"
146 exit
147 end select
148 end do
149
150 call terminal_clear_screen()
151 end subroutine show_welcome_menu
152
153 !> Render the welcome menu
154 subroutine render_welcome_menu(favorites, fav_count, recents, rec_count, &
155 showing_favorites, selected_index, scroll_offset, rows, cwd)
156 type(favorite_t), intent(in) :: favorites(:)
157 type(recent_t), intent(in) :: recents(:)
158 integer, intent(in) :: fav_count, rec_count
159 logical, intent(in) :: showing_favorites
160 integer, intent(in) :: selected_index, scroll_offset, rows
161 character(len=*), intent(in) :: cwd
162 character(len=512) :: line
163 integer :: i, display_row, visible_height, item_count, actual_index
164 character(len=64) :: title
165
166 call terminal_clear_screen()
167 call terminal_move_cursor(1, 1)
168
169 ! Header
170 line = '╔═══════════════════════' // &
171 '═══════════════════════' // &
172 '══════════════════════╗'
173 call terminal_write(trim(line))
174 call terminal_move_cursor(2, 1)
175 write(line, '(A)') '║ FAC - Welcome Menu ║'
176 call terminal_write(trim(line))
177 call terminal_move_cursor(3, 1)
178 line = '╚═══════════════════════' // &
179 '═══════════════════════' // &
180 '══════════════════════╝'
181 call terminal_write(trim(line))
182
183 ! View title
184 call terminal_move_cursor(5, 1)
185 if (showing_favorites) then
186 write(title, '(A,I0,A)') 'FAVORITES (', fav_count, ' total)'
187 item_count = fav_count
188 else
189 write(title, '(A,I0,A)') 'RECENT WORKSPACES (', rec_count, ' total)'
190 item_count = rec_count
191 end if
192 call terminal_write(trim(title))
193
194 ! Separator
195 call terminal_move_cursor(6, 1)
196 line = '────────────────────────' // &
197 '────────────────────────' // &
198 '──────────────────────'
199 call terminal_write(trim(line))
200
201 ! List items
202 visible_height = rows - 8 ! Reserve space for header and footer
203 display_row = 7
204
205 ! Always show at least the CURRENT DIRECTORY option
206 ! Render items from scroll_offset to scroll_offset + visible_height
207 ! Index 0 = CURRENT DIRECTORY, indices 1..item_count = actual items
208 do i = 0, min(visible_height - 1, item_count)
209 actual_index = i + scroll_offset
210 if (actual_index > item_count) exit
211
212 call terminal_move_cursor(display_row, 1)
213
214 ! Check if this item is selected
215 if (actual_index == selected_index) then
216 ! Highlight selected
217 write(line, '(A)') char(27) // '[7m' ! Reverse video
218 else
219 write(line, '(A)') ''
220 end if
221
222 if (actual_index == 0) then
223 ! CURRENT DIRECTORY option
224 write(line, '(A,A,I0,A,A)') trim(line), ' [', actual_index, '] ', &
225 'CURRENT DIRECTORY → ' // trim(cwd)
226 else
227 ! Regular items (favorites or recents)
228 if (showing_favorites) then
229 write(line, '(A,A,I0,A,A,A,A)') trim(line), ' [', actual_index, '] ', &
230 trim(favorites(actual_index)%label), ' → ', &
231 trim(favorites(actual_index)%path)
232 else
233 write(line, '(A,A,I0,A,A,A,A)') trim(line), ' [', actual_index, '] ', &
234 trim(recents(actual_index)%label), ' → ', &
235 trim(recents(actual_index)%path)
236 end if
237 end if
238
239 if (actual_index == selected_index) then
240 write(line, '(A,A)') trim(line), char(27) // '[0m' ! Reset
241 end if
242
243 call terminal_write(trim(line))
244 display_row = display_row + 1
245 end do
246
247 ! Show message if no items (but CWD is always there)
248 if (item_count == 0 .and. selected_index /= 0) then
249 call terminal_move_cursor(display_row, 1)
250 if (showing_favorites) then
251 call terminal_write(' (No favorites yet - press ''f'' in Fortress to add)')
252 else
253 call terminal_write(' (No recent workspaces)')
254 end if
255 end if
256
257 ! Footer with keybindings
258 call terminal_move_cursor(rows - 2, 1)
259 line = '────────────────────────' // &
260 '────────────────────────' // &
261 '──────────────────────'
262 call terminal_write(trim(line))
263
264 call terminal_move_cursor(rows - 1, 1)
265 write(line, '(A)') '↑/↓:navigate Enter:select 8:toggle fav/recent b:browse ESC/q:quit'
266 call terminal_write(trim(line))
267 end subroutine render_welcome_menu
268
269 !> Adjust scroll offset to keep CURRENT DIRECTORY visible
270 subroutine adjust_scroll(offset)
271 integer, intent(inout) :: offset
272
273 ! IMPORTANT: offset must always be 0 to keep CWD (index 0) visible
274 ! Since CWD is always at the top, we never scroll past it
275 offset = 0
276
277 ! Note: This means with many items, CWD is always visible but
278 ! you scroll through the rest of the list below it
279 end subroutine adjust_scroll
280
281 !> Get terminal size (wrapper)
282 subroutine terminal_get_size(rows, cols)
283 integer, intent(out) :: rows, cols
284 ! Default size
285 rows = 24
286 cols = 80
287 ! TODO: Query actual terminal size if available
288 end subroutine terminal_get_size
289
290 !> Check if a directory exists (Phase 7: deleted workspace detection)
291 function directory_exists(path) result(exists)
292 character(len=*), intent(in) :: path
293 logical :: exists
294 integer :: ios
295
296 ! Try to open directory (will fail if doesn't exist)
297 call execute_command_line('test -d "' // trim(path) // '"', wait=.true., exitstat=ios)
298 exists = (ios == 0)
299 end function directory_exists
300
301 !> Handle deleted workspace (Phase 7: show warning and remove from list)
302 subroutine handle_deleted_workspace(path, is_favorite, index)
303 character(len=*), intent(in) :: path
304 logical, intent(in) :: is_favorite
305 integer, intent(in) :: index
306 character(len=512) :: warning_msg
307 logical :: remove_success
308
309 ! Show warning message
310 call terminal_move_cursor(1, 1)
311 warning_msg = "Warning: Workspace no longer exists: " // trim(path)
312 call terminal_write(trim(warning_msg))
313 call terminal_move_cursor(2, 1)
314 call terminal_write("Removing from list...")
315
316 ! Remove from appropriate list
317 if (is_favorite) then
318 call favorites_remove(index, remove_success)
319 else
320 call recents_remove(index, remove_success)
321 end if
322
323 ! Brief pause so user can see the message
324 call execute_command_line("sleep 1.0", wait=.true.)
325 end subroutine handle_deleted_workspace
326
327 !> Get current working directory
328 subroutine get_cwd(cwd)
329 character(len=*), intent(out) :: cwd
330 integer :: unit, ios
331
332 ! Use pwd command to get current directory
333 call execute_command_line('pwd > /tmp/.fac_cwd 2>/dev/null', wait=.true.)
334
335 open(newunit=unit, file='/tmp/.fac_cwd', status='old', iostat=ios)
336 if (ios == 0) then
337 read(unit, '(A)', iostat=ios) cwd
338 close(unit)
339 call execute_command_line('rm -f /tmp/.fac_cwd', wait=.true.)
340 else
341 cwd = '.'
342 end if
343 end subroutine get_cwd
344
345 end module welcome_menu_module
346