fortrangoingonforty/fuss / d14d0a1

Browse files

cleanup disk i/o

Authored by espadonne
SHA
d14d0a18726238325b8970eaade8a0e937cbbc4e
Parents
53c7a91
Tree
8f3fd7b

7 changed files

StatusFile+-
A src/cache_module.f90 98 0
M src/fuss_main.f90 87 33
C src/fuss_main.f90.bak 0 0
C src/fuss_main.f90.bak2 0 0
M src/git_module.f90 112 47
C src/git_module.f90.phase23 0 0
M src/terminal_module.f90 7 4
src/cache_module.f90added
@@ -0,0 +1,98 @@
1
+module cache_module
2
+    use types_module
3
+    implicit none
4
+
5
+    ! File cache type for storing git command results
6
+    type :: file_cache
7
+        real(8) :: timestamp
8
+        type(file_entry), allocatable :: files(:)
9
+        integer :: n_files
10
+        logical :: valid
11
+    end type
12
+
13
+    ! Global caches for dirty files and all files
14
+    type(file_cache) :: dirty_cache, all_cache
15
+
16
+    ! Cache time-to-live (500ms = 0.5 seconds)
17
+    ! After this time, cache is considered stale
18
+    real(8), parameter :: CACHE_TTL = 0.5d0
19
+
20
+contains
21
+
22
+    ! Check if a cache entry is still valid
23
+    function cache_valid(cache) result(valid)
24
+        type(file_cache), intent(in) :: cache
25
+        logical :: valid
26
+        real(8) :: current_time
27
+
28
+        if (.not. cache%valid) then
29
+            valid = .false.
30
+            return
31
+        end if
32
+
33
+        call cpu_time(current_time)
34
+        valid = (current_time - cache%timestamp) < CACHE_TTL
35
+    end function cache_valid
36
+
37
+    ! Invalidate a cache (mark as stale)
38
+    subroutine invalidate_cache(cache)
39
+        type(file_cache), intent(inout) :: cache
40
+        cache%valid = .false.
41
+    end subroutine invalidate_cache
42
+
43
+    ! Invalidate all caches (called after state-changing operations)
44
+    subroutine invalidate_all_caches()
45
+        call invalidate_cache(dirty_cache)
46
+        call invalidate_cache(all_cache)
47
+    end subroutine invalidate_all_caches
48
+
49
+    ! Update cache with new data
50
+    subroutine update_cache(cache, files, n_files)
51
+        type(file_cache), intent(inout) :: cache
52
+        type(file_entry), intent(in) :: files(:)
53
+        integer, intent(in) :: n_files
54
+        integer :: i
55
+
56
+        ! Free old data
57
+        if (allocated(cache%files)) deallocate(cache%files)
58
+
59
+        ! Allocate and copy new data
60
+        allocate(cache%files(n_files))
61
+        do i = 1, n_files
62
+            cache%files(i) = files(i)
63
+        end do
64
+
65
+        cache%n_files = n_files
66
+        call cpu_time(cache%timestamp)
67
+        cache%valid = .true.
68
+    end subroutine update_cache
69
+
70
+    ! Retrieve cached data
71
+    subroutine get_cached_files(cache, files, n_files)
72
+        type(file_cache), intent(in) :: cache
73
+        type(file_entry), allocatable, intent(out) :: files(:)
74
+        integer, intent(out) :: n_files
75
+        integer :: i
76
+
77
+        n_files = cache%n_files
78
+
79
+        if (allocated(files)) deallocate(files)
80
+        allocate(files(n_files))
81
+
82
+        do i = 1, n_files
83
+            files(i) = cache%files(i)
84
+        end do
85
+    end subroutine get_cached_files
86
+
87
+    ! Initialize caches (call at program start)
88
+    subroutine init_caches()
89
+        dirty_cache%valid = .false.
90
+        dirty_cache%timestamp = 0.0d0
91
+        dirty_cache%n_files = 0
92
+
93
+        all_cache%valid = .false.
94
+        all_cache%timestamp = 0.0d0
95
+        all_cache%n_files = 0
96
+    end subroutine init_caches
97
+
98
+end module cache_module
src/fuss_main.f90modified
@@ -5,12 +5,16 @@ program fuss
5
     use tree_module
5
     use tree_module
6
     use display_module
6
     use display_module
7
     use terminal_module
7
     use terminal_module
8
+    use cache_module
8
     implicit none
9
     implicit none
9
 
10
 
10
     ! Main program variables
11
     ! Main program variables
11
     logical :: show_all, interactive
12
     logical :: show_all, interactive
12
     character(len=:), allocatable :: root_path
13
     character(len=:), allocatable :: root_path
13
 
14
 
15
+    ! Initialize caches for performance optimization
16
+    call init_caches()
17
+
14
     ! Parse command line arguments
18
     ! Parse command line arguments
15
     call parse_arguments(show_all, interactive)
19
     call parse_arguments(show_all, interactive)
16
 
20
 
@@ -124,9 +128,9 @@ contains
124
         character(len=1024) :: buffer
128
         character(len=1024) :: buffer
125
         integer :: status
129
         integer :: status
126
 
130
 
127
-        call execute_command_line('pwd > /tmp/fuss_pwd.txt', exitstat=status)
131
+        call execute_command_line('pwd > /tmp/fuss_tmp.txt', exitstat=status)
128
 
132
 
129
-        open(unit=99, file='/tmp/fuss_pwd.txt', status='old', action='read')
133
+        open(unit=99, file='/tmp/fuss_tmp.txt', status='old', action='read')
130
         read(99, '(A)') buffer
134
         read(99, '(A)') buffer
131
         close(99, status='delete')
135
         close(99, status='delete')
132
 
136
 
@@ -173,6 +177,8 @@ contains
173
         logical :: running, hide_dotfiles
177
         logical :: running, hide_dotfiles
174
         character(len=256) :: repo_name, branch_name, term_program
178
         character(len=256) :: repo_name, branch_name, term_program
175
         integer :: term_height, viewport_offset, visible_items, top_padding
179
         integer :: term_height, viewport_offset, visible_items, top_padding
180
+        integer :: prev_selected, prev_viewport
181
+        logical :: needs_full_redraw
176
         type(tree_node), pointer :: tree_root
182
         type(tree_node), pointer :: tree_root
177
 
183
 
178
         ! Initialize tree pointer
184
         ! Initialize tree pointer
@@ -235,6 +241,11 @@ contains
235
         viewport_offset = 1
241
         viewport_offset = 1
236
         running = .true.
242
         running = .true.
237
 
243
 
244
+        ! Partial redraw optimization: initialize tracking state
245
+        prev_selected = 0  ! Force initial draw
246
+        prev_viewport = 0
247
+        needs_full_redraw = .true.
248
+
238
         ! Enter alternate screen buffer (preserves terminal content)
249
         ! Enter alternate screen buffer (preserves terminal content)
239
         call enter_alternate_screen()
250
         call enter_alternate_screen()
240
 
251
 
@@ -252,10 +263,24 @@ contains
252
                 viewport_offset = n_items - visible_items + 1
263
                 viewport_offset = n_items - visible_items + 1
253
             end if
264
             end if
254
 
265
 
255
-            ! Clear screen and redraw
266
+            ! Conditional redraw for performance optimization
256
-            call clear_screen()
267
+            if (needs_full_redraw .or. viewport_offset /= prev_viewport) then
257
-            call draw_interactive_tree(tree_root, items, n_items, selected, &
268
+                ! Full redraw needed: viewport scrolled or forced refresh
258
-                                       repo_name, branch_name, viewport_offset, visible_items, top_padding)
269
+                call clear_screen()
270
+                call draw_interactive_tree(tree_root, items, n_items, selected, &
271
+                                           repo_name, branch_name, viewport_offset, visible_items, top_padding)
272
+                needs_full_redraw = .false.
273
+            else if (selected /= prev_selected) then
274
+                ! Only selection changed within same viewport - still need full redraw for now
275
+                ! TODO: Could optimize this with partial line updates in the future
276
+                call clear_screen()
277
+                call draw_interactive_tree(tree_root, items, n_items, selected, &
278
+                                           repo_name, branch_name, viewport_offset, visible_items, top_padding)
279
+            end if
280
+
281
+            ! Update tracking state
282
+            prev_selected = selected
283
+            prev_viewport = viewport_offset
259
 
284
 
260
             ! Read key
285
             ! Read key
261
             call read_key(key)
286
             call read_key(key)
@@ -278,6 +303,8 @@ contains
278
                     call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles)
303
                     call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles)
279
                     ! Adjust selection if needed
304
                     ! Adjust selection if needed
280
                     if (selected > n_items .and. n_items > 0) selected = n_items
305
                     if (selected > n_items .and. n_items > 0) selected = n_items
306
+                    ! Force full redraw after tree structure change
307
+                    needs_full_redraw = .true.
281
                 end if
308
                 end if
282
             case ('a')  ! Stage file or directory (lowercase to avoid conflict with arrow A)
309
             case ('a')  ! Stage file or directory (lowercase to avoid conflict with arrow A)
283
                 ! Check if it's a directory - stage all files in it
310
                 ! Check if it's a directory - stage all files in it
@@ -285,62 +312,72 @@ contains
285
                     call git_stage_directory(items(selected)%path)
312
                     call git_stage_directory(items(selected)%path)
286
                     ! Refresh files after staging directory
313
                     ! Refresh files after staging directory
287
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
314
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
288
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
315
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
316
+                    needs_full_redraw = .true.
289
                 ! Otherwise it's a file - stage individual file
317
                 ! Otherwise it's a file - stage individual file
290
                 else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
318
                 else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
291
                     call git_add_file(items(selected)%path)
319
                     call git_add_file(items(selected)%path)
292
                     ! Refresh files after git add
320
                     ! Refresh files after git add
293
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
321
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
294
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
322
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
323
+                    needs_full_redraw = .true.
295
                 end if
324
                 end if
296
             case ('u')  ! Unstage file (lowercase)
325
             case ('u')  ! Unstage file (lowercase)
297
                 if (items(selected)%is_file .and. items(selected)%is_staged) then
326
                 if (items(selected)%is_file .and. items(selected)%is_staged) then
298
                     call git_unstage_file(items(selected)%path)
327
                     call git_unstage_file(items(selected)%path)
299
                     ! Refresh files after git unstage
328
                     ! Refresh files after git unstage
300
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
329
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
301
-                                            hide_dotfiles, selected, running)
330
+                                            hide_dotfiles, selected, running, force_refresh=.true.)
331
+                    needs_full_redraw = .true.
302
                 end if
332
                 end if
303
             case ('S')  ! Stage all (Shift+S to avoid conflict with up arrow 'A')
333
             case ('S')  ! Stage all (Shift+S to avoid conflict with up arrow 'A')
304
                 call git_stage_all()
334
                 call git_stage_all()
305
                 ! Refresh files after staging all
335
                 ! Refresh files after staging all
306
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
336
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
307
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
337
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
338
+                    needs_full_redraw = .true.
308
             case ('U')  ! Unstage all (Shift+U)
339
             case ('U')  ! Unstage all (Shift+U)
309
                 call git_unstage_all()
340
                 call git_unstage_all()
310
                 ! Refresh files after unstaging all
341
                 ! Refresh files after unstaging all
311
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
342
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
312
-                                        hide_dotfiles, selected, running)
343
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
344
+                    needs_full_redraw = .true.
313
             case ('m')  ! Commit (lowercase)
345
             case ('m')  ! Commit (lowercase)
314
                 call commit_prompt()
346
                 call commit_prompt()
315
                 ! Refresh files after commit
347
                 ! Refresh files after commit
316
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
348
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
317
-                                        hide_dotfiles, selected, running)
349
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
350
+                    needs_full_redraw = .true.
318
             case ('M')  ! Amend last commit (Shift+m)
351
             case ('M')  ! Amend last commit (Shift+m)
319
                 call amend_commit_prompt()
352
                 call amend_commit_prompt()
320
                 ! Refresh files after amend commit
353
                 ! Refresh files after amend commit
321
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
354
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
322
-                                        hide_dotfiles, selected, running)
355
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
356
+                    needs_full_redraw = .true.
323
             case ('s')  ! Show git status (lowercase)
357
             case ('s')  ! Show git status (lowercase)
324
                 call show_status_view()
358
                 call show_status_view()
325
             case ('p')  ! Push (lowercase)
359
             case ('p')  ! Push (lowercase)
326
                 call push_prompt()
360
                 call push_prompt()
327
                 ! Refresh files after push
361
                 ! Refresh files after push
328
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
362
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
329
-                                        hide_dotfiles, selected, running)
363
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
364
+                    needs_full_redraw = .true.
330
             case ('t')  ! Tag (lowercase)
365
             case ('t')  ! Tag (lowercase)
331
                 call tag_prompt()
366
                 call tag_prompt()
332
             case ('b')  ! Switch branch
367
             case ('b')  ! Switch branch
333
                 call branch_switch_prompt()
368
                 call branch_switch_prompt()
334
                 ! Refresh files after branch switch
369
                 ! Refresh files after branch switch
335
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
370
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
336
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
371
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
372
+                    needs_full_redraw = .true.
337
                 ! Update branch name display
373
                 ! Update branch name display
338
                 call get_repo_info(repo_name, branch_name)
374
                 call get_repo_info(repo_name, branch_name)
339
             case ('n')  ! Create new branch
375
             case ('n')  ! Create new branch
340
                 call branch_create_prompt()
376
                 call branch_create_prompt()
341
                 ! Refresh files after branch creation
377
                 ! Refresh files after branch creation
342
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
378
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
343
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
379
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
380
+                    needs_full_redraw = .true.
344
                 ! Update branch name display
381
                 ! Update branch name display
345
                 call get_repo_info(repo_name, branch_name)
382
                 call get_repo_info(repo_name, branch_name)
346
             case ('R')  ! Delete branch (Shift+r, since 'r' is used for delete file)
383
             case ('R')  ! Delete branch (Shift+r, since 'r' is used for delete file)
@@ -350,7 +387,8 @@ contains
350
                 call git_fetch()
387
                 call git_fetch()
351
                 ! Refresh files after fetch and include files with incoming changes
388
                 ! Refresh files after fetch and include files with incoming changes
352
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
389
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
353
-                                        hide_dotfiles, selected, running, include_incoming=.true.)
390
+                                        hide_dotfiles, selected, running, include_incoming=.true., force_refresh=.true.)
391
+                    needs_full_redraw = .true.
354
             case ('d')  ! Git diff with less
392
             case ('d')  ! Git diff with less
355
                 if (items(selected)%is_file) then
393
                 if (items(selected)%is_file) then
356
                     call git_diff_file(items(selected)%path, items(selected)%has_incoming)
394
                     call git_diff_file(items(selected)%path, items(selected)%has_incoming)
@@ -368,42 +406,49 @@ contains
368
                     call delete_prompt(items(selected)%path, items(selected)%is_untracked)
406
                     call delete_prompt(items(selected)%path, items(selected)%is_untracked)
369
                     ! Refresh files after delete
407
                     ! Refresh files after delete
370
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
408
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
371
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
409
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
410
+                    needs_full_redraw = .true.
372
                 end if
411
                 end if
373
             case ('x', 'X')  ! Discard changes
412
             case ('x', 'X')  ! Discard changes
374
                 if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
413
                 if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
375
                     call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked)
414
                     call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked)
376
                     ! Refresh files after discard
415
                     ! Refresh files after discard
377
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
416
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
378
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
417
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
418
+                    needs_full_redraw = .true.
379
                 end if
419
                 end if
380
             case ('l')  ! Git pull
420
             case ('l')  ! Git pull
381
                 call git_pull()
421
                 call git_pull()
382
                 ! Refresh files after pull (incoming indicators will automatically clear)
422
                 ! Refresh files after pull (incoming indicators will automatically clear)
383
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
423
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
384
-                                        hide_dotfiles, selected, running, include_incoming=.true.)
424
+                                        hide_dotfiles, selected, running, include_incoming=.true., force_refresh=.true.)
425
+                    needs_full_redraw = .true.
385
                 ! Note: After successful pull, git diff will show no upstream differences
426
                 ! Note: After successful pull, git diff will show no upstream differences
386
                 ! so has_incoming will be .false. for all files automatically
427
                 ! so has_incoming will be .false. for all files automatically
387
             case ('z')  ! Stash push (save changes)
428
             case ('z')  ! Stash push (save changes)
388
                 call stash_push_prompt()
429
                 call stash_push_prompt()
389
                 ! Refresh files after stash
430
                 ! Refresh files after stash
390
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
431
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
391
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
432
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
433
+                    needs_full_redraw = .true.
392
             case ('Z')  ! Stash pop/apply (restore changes)
434
             case ('Z')  ! Stash pop/apply (restore changes)
393
                 call stash_pop_apply_prompt()
435
                 call stash_pop_apply_prompt()
394
                 ! Refresh files after stash pop/apply
436
                 ! Refresh files after stash pop/apply
395
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
437
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
396
-                                        hide_dotfiles, selected, running)
438
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
439
+                    needs_full_redraw = .true.
397
             case ('y')  ! Cherry-pick (yank commit)
440
             case ('y')  ! Cherry-pick (yank commit)
398
                 call cherry_pick_prompt()
441
                 call cherry_pick_prompt()
399
                 ! Refresh files after cherry-pick
442
                 ! Refresh files after cherry-pick
400
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
443
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
401
-                                        hide_dotfiles, selected, running)
444
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
445
+                    needs_full_redraw = .true.
402
             case ('v')  ! Revert commit
446
             case ('v')  ! Revert commit
403
                 call revert_commit_prompt()
447
                 call revert_commit_prompt()
404
                 ! Refresh files after revert
448
                 ! Refresh files after revert
405
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
449
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
406
-                                        hide_dotfiles, selected, running)
450
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
451
+                    needs_full_redraw = .true.
407
             case ('h')  ! Show commit history
452
             case ('h')  ! Show commit history
408
                 call history_browser_prompt()
453
                 call history_browser_prompt()
409
                 ! No refresh needed - read-only
454
                 ! No refresh needed - read-only
@@ -414,19 +459,22 @@ contains
414
                 call merge_branch_prompt()
459
                 call merge_branch_prompt()
415
                 ! Refresh files after merge
460
                 ! Refresh files after merge
416
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
461
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
417
-                                        hide_dotfiles, selected, running)
462
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
463
+                    needs_full_redraw = .true.
418
                 ! Update branch name display in case we merged
464
                 ! Update branch name display in case we merged
419
                 call get_repo_info(repo_name, branch_name)
465
                 call get_repo_info(repo_name, branch_name)
420
             case ('O')  ! Reset (Shift+o - "Oh no, undo!")
466
             case ('O')  ! Reset (Shift+o - "Oh no, undo!")
421
                 call reset_prompt()
467
                 call reset_prompt()
422
                 ! Refresh files after reset
468
                 ! Refresh files after reset
423
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
469
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
424
-                                        hide_dotfiles, selected, running)
470
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
471
+                    needs_full_redraw = .true.
425
             case ('I')  ! Interactive rebase (Shift+i)
472
             case ('I')  ! Interactive rebase (Shift+i)
426
                 call rebase_prompt()
473
                 call rebase_prompt()
427
                 ! Refresh files after rebase
474
                 ! Refresh files after rebase
428
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
475
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
429
-                                        hide_dotfiles, selected, running)
476
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
477
+                    needs_full_redraw = .true.
430
             case ('.')  ! Toggle hiding dotfiles and gitignored files
478
             case ('.')  ! Toggle hiding dotfiles and gitignored files
431
                 hide_dotfiles = .not. hide_dotfiles
479
                 hide_dotfiles = .not. hide_dotfiles
432
                 ! Rebuild item list with new filter
480
                 ! Rebuild item list with new filter
@@ -434,6 +482,8 @@ contains
434
                 ! Adjust selection and visible_items for new item count
482
                 ! Adjust selection and visible_items for new item count
435
                 if (selected > n_items .and. n_items > 0) selected = n_items
483
                 if (selected > n_items .and. n_items > 0) selected = n_items
436
                 if (n_items > 0 .and. selected < 1) selected = 1
484
                 if (n_items > 0 .and. selected < 1) selected = 1
485
+                ! Force full redraw after filter change
486
+                needs_full_redraw = .true.
437
                 ! Recalculate visible_items in case n_items changed
487
                 ! Recalculate visible_items in case n_items changed
438
                 visible_items = term_height - 6
488
                 visible_items = term_height - 6
439
                 if (visible_items < 3) visible_items = 3
489
                 if (visible_items < 3) visible_items = 3
@@ -1211,18 +1261,19 @@ contains
1211
     end subroutine branch_delete_prompt
1261
     end subroutine branch_delete_prompt
1212
 
1262
 
1213
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1263
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1214
-                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming)
1264
+                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming, force_refresh)
1215
         ! Centralized helper to refresh file list and rebuild tree
1265
         ! Centralized helper to refresh file list and rebuild tree
1216
         ! Consolidates the pattern repeated 20+ times in the codebase
1266
         ! Consolidates the pattern repeated 20+ times in the codebase
1267
+        ! Now with caching support for performance optimization
1217
         logical, intent(in) :: show_all, hide_dotfiles
1268
         logical, intent(in) :: show_all, hide_dotfiles
1218
         type(file_entry), allocatable, intent(inout) :: files(:)
1269
         type(file_entry), allocatable, intent(inout) :: files(:)
1219
         integer, intent(inout) :: n_files, n_items, selected
1270
         integer, intent(inout) :: n_files, n_items, selected
1220
         type(tree_node), pointer, intent(inout) :: tree_root
1271
         type(tree_node), pointer, intent(inout) :: tree_root
1221
         type(selectable_item), allocatable, intent(inout) :: items(:)
1272
         type(selectable_item), allocatable, intent(inout) :: items(:)
1222
         logical, intent(inout), optional :: running
1273
         logical, intent(inout), optional :: running
1223
-        logical, intent(in), optional :: exit_if_empty, include_incoming
1274
+        logical, intent(in), optional :: exit_if_empty, include_incoming, force_refresh
1224
 
1275
 
1225
-        logical :: do_exit_if_empty, do_include_incoming
1276
+        logical :: do_exit_if_empty, do_include_incoming, do_force_refresh
1226
 
1277
 
1227
         ! Handle optional parameters
1278
         ! Handle optional parameters
1228
         do_exit_if_empty = .false.
1279
         do_exit_if_empty = .false.
@@ -1231,12 +1282,15 @@ contains
1231
         do_include_incoming = .false.
1282
         do_include_incoming = .false.
1232
         if (present(include_incoming)) do_include_incoming = include_incoming
1283
         if (present(include_incoming)) do_include_incoming = include_incoming
1233
 
1284
 
1234
-        ! Get files based on mode
1285
+        do_force_refresh = .false.
1286
+        if (present(force_refresh)) do_force_refresh = force_refresh
1287
+
1288
+        ! Get files based on mode (with caching support)
1235
         if (show_all) then
1289
         if (show_all) then
1236
-            call get_all_files(files, n_files)
1290
+            call get_all_files(files, n_files, force_refresh=do_force_refresh)
1237
             call mark_incoming_changes(files, n_files)
1291
             call mark_incoming_changes(files, n_files)
1238
         else
1292
         else
1239
-            call get_dirty_files(files, n_files)
1293
+            call get_dirty_files(files, n_files, force_refresh=do_force_refresh)
1240
             if (do_include_incoming) then
1294
             if (do_include_incoming) then
1241
                 ! For fetch/pull: also include files with only incoming changes
1295
                 ! For fetch/pull: also include files with only incoming changes
1242
                 call add_incoming_files(files, n_files)
1296
                 call add_incoming_files(files, n_files)
src/fuss_main.f90 → src/fuss_main.f90.bakcopied (98% similarity)
@@ -5,12 +5,16 @@ program fuss
5
     use tree_module
5
     use tree_module
6
     use display_module
6
     use display_module
7
     use terminal_module
7
     use terminal_module
8
+    use cache_module
8
     implicit none
9
     implicit none
9
 
10
 
10
     ! Main program variables
11
     ! Main program variables
11
     logical :: show_all, interactive
12
     logical :: show_all, interactive
12
     character(len=:), allocatable :: root_path
13
     character(len=:), allocatable :: root_path
13
 
14
 
15
+    ! Initialize caches for performance optimization
16
+    call init_caches()
17
+
14
     ! Parse command line arguments
18
     ! Parse command line arguments
15
     call parse_arguments(show_all, interactive)
19
     call parse_arguments(show_all, interactive)
16
 
20
 
@@ -1211,18 +1215,19 @@ contains
1211
     end subroutine branch_delete_prompt
1215
     end subroutine branch_delete_prompt
1212
 
1216
 
1213
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1217
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1214
-                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming)
1218
+                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming, force_refresh)
1215
         ! Centralized helper to refresh file list and rebuild tree
1219
         ! Centralized helper to refresh file list and rebuild tree
1216
         ! Consolidates the pattern repeated 20+ times in the codebase
1220
         ! Consolidates the pattern repeated 20+ times in the codebase
1221
+        ! Now with caching support for performance optimization
1217
         logical, intent(in) :: show_all, hide_dotfiles
1222
         logical, intent(in) :: show_all, hide_dotfiles
1218
         type(file_entry), allocatable, intent(inout) :: files(:)
1223
         type(file_entry), allocatable, intent(inout) :: files(:)
1219
         integer, intent(inout) :: n_files, n_items, selected
1224
         integer, intent(inout) :: n_files, n_items, selected
1220
         type(tree_node), pointer, intent(inout) :: tree_root
1225
         type(tree_node), pointer, intent(inout) :: tree_root
1221
         type(selectable_item), allocatable, intent(inout) :: items(:)
1226
         type(selectable_item), allocatable, intent(inout) :: items(:)
1222
         logical, intent(inout), optional :: running
1227
         logical, intent(inout), optional :: running
1223
-        logical, intent(in), optional :: exit_if_empty, include_incoming
1228
+        logical, intent(in), optional :: exit_if_empty, include_incoming, force_refresh
1224
 
1229
 
1225
-        logical :: do_exit_if_empty, do_include_incoming
1230
+        logical :: do_exit_if_empty, do_include_incoming, do_force_refresh
1226
 
1231
 
1227
         ! Handle optional parameters
1232
         ! Handle optional parameters
1228
         do_exit_if_empty = .false.
1233
         do_exit_if_empty = .false.
@@ -1231,12 +1236,15 @@ contains
1231
         do_include_incoming = .false.
1236
         do_include_incoming = .false.
1232
         if (present(include_incoming)) do_include_incoming = include_incoming
1237
         if (present(include_incoming)) do_include_incoming = include_incoming
1233
 
1238
 
1234
-        ! Get files based on mode
1239
+        do_force_refresh = .false.
1240
+        if (present(force_refresh)) do_force_refresh = force_refresh
1241
+
1242
+        ! Get files based on mode (with caching support)
1235
         if (show_all) then
1243
         if (show_all) then
1236
-            call get_all_files(files, n_files)
1244
+            call get_all_files(files, n_files, force_refresh=do_force_refresh)
1237
             call mark_incoming_changes(files, n_files)
1245
             call mark_incoming_changes(files, n_files)
1238
         else
1246
         else
1239
-            call get_dirty_files(files, n_files)
1247
+            call get_dirty_files(files, n_files, force_refresh=do_force_refresh)
1240
             if (do_include_incoming) then
1248
             if (do_include_incoming) then
1241
                 ! For fetch/pull: also include files with only incoming changes
1249
                 ! For fetch/pull: also include files with only incoming changes
1242
                 call add_incoming_files(files, n_files)
1250
                 call add_incoming_files(files, n_files)
src/fuss_main.f90 → src/fuss_main.f90.bak2copied (94% similarity)
@@ -5,12 +5,16 @@ program fuss
5
     use tree_module
5
     use tree_module
6
     use display_module
6
     use display_module
7
     use terminal_module
7
     use terminal_module
8
+    use cache_module
8
     implicit none
9
     implicit none
9
 
10
 
10
     ! Main program variables
11
     ! Main program variables
11
     logical :: show_all, interactive
12
     logical :: show_all, interactive
12
     character(len=:), allocatable :: root_path
13
     character(len=:), allocatable :: root_path
13
 
14
 
15
+    ! Initialize caches for performance optimization
16
+    call init_caches()
17
+
14
     ! Parse command line arguments
18
     ! Parse command line arguments
15
     call parse_arguments(show_all, interactive)
19
     call parse_arguments(show_all, interactive)
16
 
20
 
@@ -235,6 +239,13 @@ contains
235
         viewport_offset = 1
239
         viewport_offset = 1
236
         running = .true.
240
         running = .true.
237
 
241
 
242
+        ! Partial redraw optimization: track previous state
243
+        integer :: prev_selected, prev_viewport
244
+        logical :: needs_full_redraw
245
+        prev_selected = 0  ! Force initial draw
246
+        prev_viewport = 0
247
+        needs_full_redraw = .true.
248
+
238
         ! Enter alternate screen buffer (preserves terminal content)
249
         ! Enter alternate screen buffer (preserves terminal content)
239
         call enter_alternate_screen()
250
         call enter_alternate_screen()
240
 
251
 
@@ -252,10 +263,24 @@ contains
252
                 viewport_offset = n_items - visible_items + 1
263
                 viewport_offset = n_items - visible_items + 1
253
             end if
264
             end if
254
 
265
 
255
-            ! Clear screen and redraw
266
+            ! Conditional redraw for performance optimization
256
-            call clear_screen()
267
+            if (needs_full_redraw .or. viewport_offset /= prev_viewport) then
257
-            call draw_interactive_tree(tree_root, items, n_items, selected, &
268
+                ! Full redraw needed: viewport scrolled or forced refresh
258
-                                       repo_name, branch_name, viewport_offset, visible_items, top_padding)
269
+                call clear_screen()
270
+                call draw_interactive_tree(tree_root, items, n_items, selected, &
271
+                                           repo_name, branch_name, viewport_offset, visible_items, top_padding)
272
+                needs_full_redraw = .false.
273
+            else if (selected /= prev_selected) then
274
+                ! Only selection changed within same viewport - still need full redraw for now
275
+                ! TODO: Could optimize this with partial line updates in the future
276
+                call clear_screen()
277
+                call draw_interactive_tree(tree_root, items, n_items, selected, &
278
+                                           repo_name, branch_name, viewport_offset, visible_items, top_padding)
279
+            end if
280
+
281
+            ! Update tracking state
282
+            prev_selected = selected
283
+            prev_viewport = viewport_offset
259
 
284
 
260
             ! Read key
285
             ! Read key
261
             call read_key(key)
286
             call read_key(key)
@@ -278,6 +303,8 @@ contains
278
                     call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles)
303
                     call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles)
279
                     ! Adjust selection if needed
304
                     ! Adjust selection if needed
280
                     if (selected > n_items .and. n_items > 0) selected = n_items
305
                     if (selected > n_items .and. n_items > 0) selected = n_items
306
+                    ! Force full redraw after tree structure change
307
+                    needs_full_redraw = .true.
281
                 end if
308
                 end if
282
             case ('a')  ! Stage file or directory (lowercase to avoid conflict with arrow A)
309
             case ('a')  ! Stage file or directory (lowercase to avoid conflict with arrow A)
283
                 ! Check if it's a directory - stage all files in it
310
                 ! Check if it's a directory - stage all files in it
@@ -285,62 +312,63 @@ contains
285
                     call git_stage_directory(items(selected)%path)
312
                     call git_stage_directory(items(selected)%path)
286
                     ! Refresh files after staging directory
313
                     ! Refresh files after staging directory
287
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
314
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
288
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
315
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
316
+                    needs_full_redraw = .true.
289
                 ! Otherwise it's a file - stage individual file
317
                 ! Otherwise it's a file - stage individual file
290
                 else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
318
                 else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
291
                     call git_add_file(items(selected)%path)
319
                     call git_add_file(items(selected)%path)
292
                     ! Refresh files after git add
320
                     ! Refresh files after git add
293
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
321
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
294
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
322
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
295
                 end if
323
                 end if
296
             case ('u')  ! Unstage file (lowercase)
324
             case ('u')  ! Unstage file (lowercase)
297
                 if (items(selected)%is_file .and. items(selected)%is_staged) then
325
                 if (items(selected)%is_file .and. items(selected)%is_staged) then
298
                     call git_unstage_file(items(selected)%path)
326
                     call git_unstage_file(items(selected)%path)
299
                     ! Refresh files after git unstage
327
                     ! Refresh files after git unstage
300
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
328
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
301
-                                            hide_dotfiles, selected, running)
329
+                                            hide_dotfiles, selected, running, force_refresh=.true.)
302
                 end if
330
                 end if
303
             case ('S')  ! Stage all (Shift+S to avoid conflict with up arrow 'A')
331
             case ('S')  ! Stage all (Shift+S to avoid conflict with up arrow 'A')
304
                 call git_stage_all()
332
                 call git_stage_all()
305
                 ! Refresh files after staging all
333
                 ! Refresh files after staging all
306
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
334
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
307
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
335
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
308
             case ('U')  ! Unstage all (Shift+U)
336
             case ('U')  ! Unstage all (Shift+U)
309
                 call git_unstage_all()
337
                 call git_unstage_all()
310
                 ! Refresh files after unstaging all
338
                 ! Refresh files after unstaging all
311
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
339
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
312
-                                        hide_dotfiles, selected, running)
340
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
313
             case ('m')  ! Commit (lowercase)
341
             case ('m')  ! Commit (lowercase)
314
                 call commit_prompt()
342
                 call commit_prompt()
315
                 ! Refresh files after commit
343
                 ! Refresh files after commit
316
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
344
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
317
-                                        hide_dotfiles, selected, running)
345
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
318
             case ('M')  ! Amend last commit (Shift+m)
346
             case ('M')  ! Amend last commit (Shift+m)
319
                 call amend_commit_prompt()
347
                 call amend_commit_prompt()
320
                 ! Refresh files after amend commit
348
                 ! Refresh files after amend commit
321
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
349
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
322
-                                        hide_dotfiles, selected, running)
350
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
323
             case ('s')  ! Show git status (lowercase)
351
             case ('s')  ! Show git status (lowercase)
324
                 call show_status_view()
352
                 call show_status_view()
325
             case ('p')  ! Push (lowercase)
353
             case ('p')  ! Push (lowercase)
326
                 call push_prompt()
354
                 call push_prompt()
327
                 ! Refresh files after push
355
                 ! Refresh files after push
328
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
356
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
329
-                                        hide_dotfiles, selected, running)
357
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
330
             case ('t')  ! Tag (lowercase)
358
             case ('t')  ! Tag (lowercase)
331
                 call tag_prompt()
359
                 call tag_prompt()
332
             case ('b')  ! Switch branch
360
             case ('b')  ! Switch branch
333
                 call branch_switch_prompt()
361
                 call branch_switch_prompt()
334
                 ! Refresh files after branch switch
362
                 ! Refresh files after branch switch
335
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
363
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
336
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
364
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
337
                 ! Update branch name display
365
                 ! Update branch name display
338
                 call get_repo_info(repo_name, branch_name)
366
                 call get_repo_info(repo_name, branch_name)
339
             case ('n')  ! Create new branch
367
             case ('n')  ! Create new branch
340
                 call branch_create_prompt()
368
                 call branch_create_prompt()
341
                 ! Refresh files after branch creation
369
                 ! Refresh files after branch creation
342
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
370
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
343
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
371
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
344
                 ! Update branch name display
372
                 ! Update branch name display
345
                 call get_repo_info(repo_name, branch_name)
373
                 call get_repo_info(repo_name, branch_name)
346
             case ('R')  ! Delete branch (Shift+r, since 'r' is used for delete file)
374
             case ('R')  ! Delete branch (Shift+r, since 'r' is used for delete file)
@@ -350,7 +378,7 @@ contains
350
                 call git_fetch()
378
                 call git_fetch()
351
                 ! Refresh files after fetch and include files with incoming changes
379
                 ! Refresh files after fetch and include files with incoming changes
352
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
380
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
353
-                                        hide_dotfiles, selected, running, include_incoming=.true.)
381
+                                        hide_dotfiles, selected, running, include_incoming=.true., force_refresh=.true.)
354
             case ('d')  ! Git diff with less
382
             case ('d')  ! Git diff with less
355
                 if (items(selected)%is_file) then
383
                 if (items(selected)%is_file) then
356
                     call git_diff_file(items(selected)%path, items(selected)%has_incoming)
384
                     call git_diff_file(items(selected)%path, items(selected)%has_incoming)
@@ -368,42 +396,42 @@ contains
368
                     call delete_prompt(items(selected)%path, items(selected)%is_untracked)
396
                     call delete_prompt(items(selected)%path, items(selected)%is_untracked)
369
                     ! Refresh files after delete
397
                     ! Refresh files after delete
370
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
398
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
371
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
399
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
372
                 end if
400
                 end if
373
             case ('x', 'X')  ! Discard changes
401
             case ('x', 'X')  ! Discard changes
374
                 if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
402
                 if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then
375
                     call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked)
403
                     call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked)
376
                     ! Refresh files after discard
404
                     ! Refresh files after discard
377
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
405
                     call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
378
-                                            hide_dotfiles, selected, running, exit_if_empty=.true.)
406
+                                            hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
379
                 end if
407
                 end if
380
             case ('l')  ! Git pull
408
             case ('l')  ! Git pull
381
                 call git_pull()
409
                 call git_pull()
382
                 ! Refresh files after pull (incoming indicators will automatically clear)
410
                 ! Refresh files after pull (incoming indicators will automatically clear)
383
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
411
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
384
-                                        hide_dotfiles, selected, running, include_incoming=.true.)
412
+                                        hide_dotfiles, selected, running, include_incoming=.true., force_refresh=.true.)
385
                 ! Note: After successful pull, git diff will show no upstream differences
413
                 ! Note: After successful pull, git diff will show no upstream differences
386
                 ! so has_incoming will be .false. for all files automatically
414
                 ! so has_incoming will be .false. for all files automatically
387
             case ('z')  ! Stash push (save changes)
415
             case ('z')  ! Stash push (save changes)
388
                 call stash_push_prompt()
416
                 call stash_push_prompt()
389
                 ! Refresh files after stash
417
                 ! Refresh files after stash
390
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
418
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
391
-                                        hide_dotfiles, selected, running, exit_if_empty=.true.)
419
+                                        hide_dotfiles, selected, running, exit_if_empty=.true., force_refresh=.true.)
392
             case ('Z')  ! Stash pop/apply (restore changes)
420
             case ('Z')  ! Stash pop/apply (restore changes)
393
                 call stash_pop_apply_prompt()
421
                 call stash_pop_apply_prompt()
394
                 ! Refresh files after stash pop/apply
422
                 ! Refresh files after stash pop/apply
395
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
423
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
396
-                                        hide_dotfiles, selected, running)
424
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
397
             case ('y')  ! Cherry-pick (yank commit)
425
             case ('y')  ! Cherry-pick (yank commit)
398
                 call cherry_pick_prompt()
426
                 call cherry_pick_prompt()
399
                 ! Refresh files after cherry-pick
427
                 ! Refresh files after cherry-pick
400
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
428
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
401
-                                        hide_dotfiles, selected, running)
429
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
402
             case ('v')  ! Revert commit
430
             case ('v')  ! Revert commit
403
                 call revert_commit_prompt()
431
                 call revert_commit_prompt()
404
                 ! Refresh files after revert
432
                 ! Refresh files after revert
405
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
433
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
406
-                                        hide_dotfiles, selected, running)
434
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
407
             case ('h')  ! Show commit history
435
             case ('h')  ! Show commit history
408
                 call history_browser_prompt()
436
                 call history_browser_prompt()
409
                 ! No refresh needed - read-only
437
                 ! No refresh needed - read-only
@@ -414,19 +442,19 @@ contains
414
                 call merge_branch_prompt()
442
                 call merge_branch_prompt()
415
                 ! Refresh files after merge
443
                 ! Refresh files after merge
416
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
444
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
417
-                                        hide_dotfiles, selected, running)
445
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
418
                 ! Update branch name display in case we merged
446
                 ! Update branch name display in case we merged
419
                 call get_repo_info(repo_name, branch_name)
447
                 call get_repo_info(repo_name, branch_name)
420
             case ('O')  ! Reset (Shift+o - "Oh no, undo!")
448
             case ('O')  ! Reset (Shift+o - "Oh no, undo!")
421
                 call reset_prompt()
449
                 call reset_prompt()
422
                 ! Refresh files after reset
450
                 ! Refresh files after reset
423
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
451
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
424
-                                        hide_dotfiles, selected, running)
452
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
425
             case ('I')  ! Interactive rebase (Shift+i)
453
             case ('I')  ! Interactive rebase (Shift+i)
426
                 call rebase_prompt()
454
                 call rebase_prompt()
427
                 ! Refresh files after rebase
455
                 ! Refresh files after rebase
428
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
456
                 call refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
429
-                                        hide_dotfiles, selected, running)
457
+                                        hide_dotfiles, selected, running, force_refresh=.true.)
430
             case ('.')  ! Toggle hiding dotfiles and gitignored files
458
             case ('.')  ! Toggle hiding dotfiles and gitignored files
431
                 hide_dotfiles = .not. hide_dotfiles
459
                 hide_dotfiles = .not. hide_dotfiles
432
                 ! Rebuild item list with new filter
460
                 ! Rebuild item list with new filter
@@ -1211,18 +1239,19 @@ contains
1211
     end subroutine branch_delete_prompt
1239
     end subroutine branch_delete_prompt
1212
 
1240
 
1213
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1241
     subroutine refresh_and_rebuild(show_all, files, n_files, items, n_items, tree_root, &
1214
-                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming)
1242
+                                    hide_dotfiles, selected, running, exit_if_empty, include_incoming, force_refresh)
1215
         ! Centralized helper to refresh file list and rebuild tree
1243
         ! Centralized helper to refresh file list and rebuild tree
1216
         ! Consolidates the pattern repeated 20+ times in the codebase
1244
         ! Consolidates the pattern repeated 20+ times in the codebase
1245
+        ! Now with caching support for performance optimization
1217
         logical, intent(in) :: show_all, hide_dotfiles
1246
         logical, intent(in) :: show_all, hide_dotfiles
1218
         type(file_entry), allocatable, intent(inout) :: files(:)
1247
         type(file_entry), allocatable, intent(inout) :: files(:)
1219
         integer, intent(inout) :: n_files, n_items, selected
1248
         integer, intent(inout) :: n_files, n_items, selected
1220
         type(tree_node), pointer, intent(inout) :: tree_root
1249
         type(tree_node), pointer, intent(inout) :: tree_root
1221
         type(selectable_item), allocatable, intent(inout) :: items(:)
1250
         type(selectable_item), allocatable, intent(inout) :: items(:)
1222
         logical, intent(inout), optional :: running
1251
         logical, intent(inout), optional :: running
1223
-        logical, intent(in), optional :: exit_if_empty, include_incoming
1252
+        logical, intent(in), optional :: exit_if_empty, include_incoming, force_refresh
1224
 
1253
 
1225
-        logical :: do_exit_if_empty, do_include_incoming
1254
+        logical :: do_exit_if_empty, do_include_incoming, do_force_refresh
1226
 
1255
 
1227
         ! Handle optional parameters
1256
         ! Handle optional parameters
1228
         do_exit_if_empty = .false.
1257
         do_exit_if_empty = .false.
@@ -1231,12 +1260,15 @@ contains
1231
         do_include_incoming = .false.
1260
         do_include_incoming = .false.
1232
         if (present(include_incoming)) do_include_incoming = include_incoming
1261
         if (present(include_incoming)) do_include_incoming = include_incoming
1233
 
1262
 
1234
-        ! Get files based on mode
1263
+        do_force_refresh = .false.
1264
+        if (present(force_refresh)) do_force_refresh = force_refresh
1265
+
1266
+        ! Get files based on mode (with caching support)
1235
         if (show_all) then
1267
         if (show_all) then
1236
-            call get_all_files(files, n_files)
1268
+            call get_all_files(files, n_files, force_refresh=do_force_refresh)
1237
             call mark_incoming_changes(files, n_files)
1269
             call mark_incoming_changes(files, n_files)
1238
         else
1270
         else
1239
-            call get_dirty_files(files, n_files)
1271
+            call get_dirty_files(files, n_files, force_refresh=do_force_refresh)
1240
             if (do_include_incoming) then
1272
             if (do_include_incoming) then
1241
                 ! For fetch/pull: also include files with only incoming changes
1273
                 ! For fetch/pull: also include files with only incoming changes
1242
                 call add_incoming_files(files, n_files)
1274
                 call add_incoming_files(files, n_files)
src/git_module.f90modified
@@ -1,8 +1,13 @@
1
 module git_module
1
 module git_module
2
     use iso_fortran_env, only: error_unit
2
     use iso_fortran_env, only: error_unit
3
     use types_module
3
     use types_module
4
+    use cache_module
4
     implicit none
5
     implicit none
5
 
6
 
7
+    ! Shared temp file for reducing disk I/O and clutter
8
+    ! Reused across operations, deleted after each read
9
+    character(len=*), parameter :: FUSS_TEMP = '/tmp/fuss_tmp.txt'
10
+
6
 contains
11
 contains
7
 
12
 
8
     ! ========== Performance Optimization: Binary Search Functions ==========
13
     ! ========== Performance Optimization: Binary Search Functions ==========
@@ -198,7 +203,8 @@ contains
198
         end if
203
         end if
199
     end function unquote_filename
204
     end function unquote_filename
200
 
205
 
201
-    subroutine get_dirty_files(files, n_files)
206
+    ! Internal implementation - called directly for actual git operations
207
+    subroutine get_dirty_files_impl(files, n_files)
202
         type(file_entry), allocatable, intent(out) :: files(:)
208
         type(file_entry), allocatable, intent(out) :: files(:)
203
         integer, intent(out) :: n_files
209
         integer, intent(out) :: n_files
204
         integer :: iostat, unit_num, status_code
210
         integer :: iostat, unit_num, status_code
@@ -280,9 +286,10 @@ contains
280
             call quicksort_files(files, 1, n_files)
286
             call quicksort_files(files, 1, n_files)
281
         end if
287
         end if
282
         deallocate(temp_files)
288
         deallocate(temp_files)
283
-    end subroutine get_dirty_files
289
+    end subroutine get_dirty_files_impl
284
 
290
 
285
-    subroutine get_all_files(files, n_files)
291
+    ! Internal implementation - called directly for actual file operations
292
+    subroutine get_all_files_impl(files, n_files)
286
         type(file_entry), allocatable, intent(out) :: files(:)
293
         type(file_entry), allocatable, intent(out) :: files(:)
287
         integer, intent(out) :: n_files
294
         integer, intent(out) :: n_files
288
         integer :: iostat, unit_num, status_code, i
295
         integer :: iostat, unit_num, status_code, i
@@ -290,8 +297,8 @@ contains
290
         type(file_entry), allocatable :: dirty_files(:), temp_files(:)
297
         type(file_entry), allocatable :: dirty_files(:), temp_files(:)
291
         integer :: n_dirty, max_files
298
         integer :: n_dirty, max_files
292
 
299
 
293
-        ! First get dirty files
300
+        ! First get dirty files (call impl directly to avoid double caching)
294
-        call get_dirty_files(dirty_files, n_dirty)
301
+        call get_dirty_files_impl(dirty_files, n_dirty)
295
 
302
 
296
         ! Get all files using find
303
         ! Get all files using find
297
         call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code)
304
         call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code)
@@ -369,8 +376,66 @@ contains
369
         end if
376
         end if
370
         deallocate(temp_files)
377
         deallocate(temp_files)
371
         if (allocated(dirty_files)) deallocate(dirty_files)
378
         if (allocated(dirty_files)) deallocate(dirty_files)
379
+    end subroutine get_all_files_impl
380
+
381
+    ! ========== Cached Wrappers for File Retrieval ==========
382
+
383
+    ! Get dirty files with caching support
384
+    subroutine get_dirty_files(files, n_files, force_refresh)
385
+        type(file_entry), allocatable, intent(out) :: files(:)
386
+        integer, intent(out) :: n_files
387
+        logical, intent(in), optional :: force_refresh
388
+        logical :: do_refresh
389
+
390
+        do_refresh = .false.
391
+        if (present(force_refresh)) do_refresh = force_refresh
392
+
393
+        ! Force refresh if requested
394
+        if (do_refresh) then
395
+            call invalidate_cache(dirty_cache)
396
+        end if
397
+
398
+        ! Check cache validity
399
+        if (cache_valid(dirty_cache)) then
400
+            ! Use cached results
401
+            call get_cached_files(dirty_cache, files, n_files)
402
+        else
403
+            ! Call actual implementation
404
+            call get_dirty_files_impl(files, n_files)
405
+            ! Update cache with fresh data
406
+            call update_cache(dirty_cache, files, n_files)
407
+        end if
408
+    end subroutine get_dirty_files
409
+
410
+    ! Get all files with caching support
411
+    subroutine get_all_files(files, n_files, force_refresh)
412
+        type(file_entry), allocatable, intent(out) :: files(:)
413
+        integer, intent(out) :: n_files
414
+        logical, intent(in), optional :: force_refresh
415
+        logical :: do_refresh
416
+
417
+        do_refresh = .false.
418
+        if (present(force_refresh)) do_refresh = force_refresh
419
+
420
+        ! Force refresh if requested
421
+        if (do_refresh) then
422
+            call invalidate_cache(all_cache)
423
+        end if
424
+
425
+        ! Check cache validity
426
+        if (cache_valid(all_cache)) then
427
+            ! Use cached results
428
+            call get_cached_files(all_cache, files, n_files)
429
+        else
430
+            ! Call actual implementation
431
+            call get_all_files_impl(files, n_files)
432
+            ! Update cache with fresh data
433
+            call update_cache(all_cache, files, n_files)
434
+        end if
372
     end subroutine get_all_files
435
     end subroutine get_all_files
373
 
436
 
437
+    ! ========== End Cached Wrappers ==========
438
+
374
     subroutine resize_array(array, new_size)
439
     subroutine resize_array(array, new_size)
375
         type(file_entry), allocatable, intent(inout) :: array(:)
440
         type(file_entry), allocatable, intent(inout) :: array(:)
376
         integer, intent(in) :: new_size
441
         integer, intent(in) :: new_size
@@ -656,12 +721,12 @@ contains
656
         message = ''
721
         message = ''
657
 
722
 
658
         ! Get the last commit message using git log
723
         ! Get the last commit message using git log
659
-        call execute_command_line('git log -1 --pretty=%B > /tmp/fuss_last_commit.txt 2>/dev/null', exitstat=status)
724
+        call execute_command_line('git log -1 --pretty=%B > ' // FUSS_TEMP // ' 2>/dev/null', exitstat=status)
660
 
725
 
661
         if (status /= 0) return
726
         if (status /= 0) return
662
 
727
 
663
         ! Read the commit message
728
         ! Read the commit message
664
-        open(newunit=unit_num, file='/tmp/fuss_last_commit.txt', status='old', action='read', iostat=iostat)
729
+        open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
665
         if (iostat /= 0) return
730
         if (iostat /= 0) return
666
 
731
 
667
         ! Read first line (single-line commit message)
732
         ! Read first line (single-line commit message)
@@ -767,7 +832,7 @@ contains
767
             end if
832
             end if
768
 
833
 
769
             ! Origin exists - get current branch name and push to origin
834
             ! Origin exists - get current branch name and push to origin
770
-            call execute_command_line('git rev-parse --abbrev-ref HEAD > /tmp/fuss_current_branch.txt 2>&1', &
835
+            call execute_command_line('git rev-parse --abbrev-ref HEAD > ' // FUSS_TEMP // ' 2>&1', &
771
                                       exitstat=status)
836
                                       exitstat=status)
772
 
837
 
773
             if (status /= 0) then
838
             if (status /= 0) then
@@ -777,7 +842,7 @@ contains
777
             end if
842
             end if
778
 
843
 
779
             ! Read current branch name
844
             ! Read current branch name
780
-            open(newunit=unit_num, file='/tmp/fuss_current_branch.txt', status='old', action='read', iostat=iostat)
845
+            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
781
             if (iostat /= 0) then
846
             if (iostat /= 0) then
782
                 print '(A)', achar(27) // '[31m✗ Could not read branch name' // achar(27) // '[0m'
847
                 print '(A)', achar(27) // '[31m✗ Could not read branch name' // achar(27) // '[0m'
783
                 print '(A)', 'Press any key to continue...'
848
                 print '(A)', 'Press any key to continue...'
@@ -844,11 +909,11 @@ contains
844
         branch_name = ''
909
         branch_name = ''
845
 
910
 
846
         ! Get repo name (basename of repo root)
911
         ! Get repo name (basename of repo root)
847
-        call execute_command_line('git rev-parse --show-toplevel 2>/dev/null | xargs basename > /tmp/fuss_repo.txt', &
912
+        call execute_command_line('git rev-parse --show-toplevel 2>/dev/null | xargs basename > ' // FUSS_TEMP // '', &
848
                                   exitstat=status_code)
913
                                   exitstat=status_code)
849
 
914
 
850
         if (status_code == 0) then
915
         if (status_code == 0) then
851
-            open(newunit=unit_num, file='/tmp/fuss_repo.txt', status='old', action='read', iostat=iostat)
916
+            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
852
             if (iostat == 0) then
917
             if (iostat == 0) then
853
                 read(unit_num, '(A)', iostat=iostat) repo_name
918
                 read(unit_num, '(A)', iostat=iostat) repo_name
854
                 close(unit_num, status='delete')
919
                 close(unit_num, status='delete')
@@ -856,11 +921,11 @@ contains
856
         end if
921
         end if
857
 
922
 
858
         ! Get current branch name
923
         ! Get current branch name
859
-        call execute_command_line('git rev-parse --abbrev-ref HEAD 2>/dev/null > /tmp/fuss_branch.txt', &
924
+        call execute_command_line('git rev-parse --abbrev-ref HEAD 2>/dev/null > ' // FUSS_TEMP // '', &
860
                                   exitstat=status_code)
925
                                   exitstat=status_code)
861
 
926
 
862
         if (status_code == 0) then
927
         if (status_code == 0) then
863
-            open(newunit=unit_num, file='/tmp/fuss_branch.txt', status='old', action='read', iostat=iostat)
928
+            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
864
             if (iostat == 0) then
929
             if (iostat == 0) then
865
                 read(unit_num, '(A)', iostat=iostat) branch_name
930
                 read(unit_num, '(A)', iostat=iostat) branch_name
866
                 close(unit_num, status='delete')
931
                 close(unit_num, status='delete')
@@ -886,7 +951,7 @@ contains
886
         ! Use fzf to select remote branch
951
         ! Use fzf to select remote branch
887
         call execute_command_line('git branch -r | grep -v HEAD | sed "s/^  //" | ' // &
952
         call execute_command_line('git branch -r | grep -v HEAD | sed "s/^  //" | ' // &
888
                                   'fzf --height=10 --border=rounded --border-label=" ESC to cancel " ' // &
953
                                   'fzf --height=10 --border=rounded --border-label=" ESC to cancel " ' // &
889
-                                  '--prompt="Select upstream: " > /tmp/fuss_upstream.txt', &
954
+                                  '--prompt="Select upstream: " > ' // FUSS_TEMP // '', &
890
                                   exitstat=status_code)
955
                                   exitstat=status_code)
891
 
956
 
892
         if (status_code /= 0) then
957
         if (status_code /= 0) then
@@ -899,7 +964,7 @@ contains
899
         end if
964
         end if
900
 
965
 
901
         ! Read selected branch
966
         ! Read selected branch
902
-        open(unit=99, file='/tmp/fuss_upstream.txt', status='old', action='read', iostat=status_code)
967
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status_code)
903
         if (status_code == 0) then
968
         if (status_code == 0) then
904
             read(99, '(A)', iostat=status_code) selected_branch
969
             read(99, '(A)', iostat=status_code) selected_branch
905
             close(99, status='delete')
970
             close(99, status='delete')
@@ -1334,11 +1399,11 @@ contains
1334
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1399
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1335
                                   '--prompt="Source branch: " ' // &
1400
                                   '--prompt="Source branch: " ' // &
1336
                                   '--preview="git log --oneline --graph --color=always {} | head -20" ' // &
1401
                                   '--preview="git log --oneline --graph --color=always {} | head -20" ' // &
1337
-                                  '--preview-window=right:50% > /tmp/fuss_branch_select.txt', &
1402
+                                  '--preview-window=right:50% > ' // FUSS_TEMP // '', &
1338
                                   exitstat=status_code)
1403
                                   exitstat=status_code)
1339
 
1404
 
1340
         if (status_code /= 0) then
1405
         if (status_code /= 0) then
1341
-            call execute_command_line('rm -f /tmp/fuss_branch_select.txt', exitstat=status)
1406
+            call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status)
1342
             print '(A)', ''
1407
             print '(A)', ''
1343
             print '(A)', 'Operation cancelled.'
1408
             print '(A)', 'Operation cancelled.'
1344
             print '(A)', ''
1409
             print '(A)', ''
@@ -1349,7 +1414,7 @@ contains
1349
         end if
1414
         end if
1350
 
1415
 
1351
         ! Read selected branch
1416
         ! Read selected branch
1352
-        open(unit=99, file='/tmp/fuss_branch_select.txt', status='old', action='read')
1417
+        open(unit=99, file=FUSS_TEMP, status='old', action='read')
1353
         read(99, '(A)', iostat=status) selected_branch
1418
         read(99, '(A)', iostat=status) selected_branch
1354
         close(99, status='delete')
1419
         close(99, status='delete')
1355
 
1420
 
@@ -1372,11 +1437,11 @@ contains
1372
                                   'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // &
1437
                                   'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // &
1373
                                   '--prompt="Commit: " ' // &
1438
                                   '--prompt="Commit: " ' // &
1374
                                   '--preview="git show --color=always {1}" ' // &
1439
                                   '--preview="git show --color=always {1}" ' // &
1375
-                                  '--preview-window=right:60% > /tmp/fuss_commit_select.txt'
1440
+                                  '--preview-window=right:60% > ' // FUSS_TEMP // ''
1376
         call execute_command_line(trim(command), exitstat=status_code)
1441
         call execute_command_line(trim(command), exitstat=status_code)
1377
 
1442
 
1378
         if (status_code /= 0) then
1443
         if (status_code /= 0) then
1379
-            call execute_command_line('rm -f /tmp/fuss_commit_select.txt', exitstat=status)
1444
+            call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status)
1380
             print '(A)', ''
1445
             print '(A)', ''
1381
             print '(A)', 'Operation cancelled.'
1446
             print '(A)', 'Operation cancelled.'
1382
             print '(A)', ''
1447
             print '(A)', ''
@@ -1387,7 +1452,7 @@ contains
1387
         end if
1452
         end if
1388
 
1453
 
1389
         ! Read selected commit (first 7 chars is the hash)
1454
         ! Read selected commit (first 7 chars is the hash)
1390
-        open(unit=99, file='/tmp/fuss_commit_select.txt', status='old', action='read')
1455
+        open(unit=99, file=FUSS_TEMP, status='old', action='read')
1391
         read(99, '(A)', iostat=status) selected_commit
1456
         read(99, '(A)', iostat=status) selected_commit
1392
         close(99, status='delete')
1457
         close(99, status='delete')
1393
 
1458
 
@@ -1448,11 +1513,11 @@ contains
1448
                                   'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // &
1513
                                   'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // &
1449
                                   '--prompt="Commit to revert: " ' // &
1514
                                   '--prompt="Commit to revert: " ' // &
1450
                                   '--preview="git show --color=always {1}" ' // &
1515
                                   '--preview="git show --color=always {1}" ' // &
1451
-                                  '--preview-window=right:60% > /tmp/fuss_revert_select.txt', &
1516
+                                  '--preview-window=right:60% > ' // FUSS_TEMP // '', &
1452
                                   exitstat=status_code)
1517
                                   exitstat=status_code)
1453
 
1518
 
1454
         if (status_code /= 0) then
1519
         if (status_code /= 0) then
1455
-            call execute_command_line('rm -f /tmp/fuss_revert_select.txt', exitstat=status)
1520
+            call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status)
1456
             print '(A)', ''
1521
             print '(A)', ''
1457
             print '(A)', 'Operation cancelled.'
1522
             print '(A)', 'Operation cancelled.'
1458
             print '(A)', ''
1523
             print '(A)', ''
@@ -1463,7 +1528,7 @@ contains
1463
         end if
1528
         end if
1464
 
1529
 
1465
         ! Read selected commit hash
1530
         ! Read selected commit hash
1466
-        open(unit=99, file='/tmp/fuss_revert_select.txt', status='old', action='read')
1531
+        open(unit=99, file=FUSS_TEMP, status='old', action='read')
1467
         read(99, '(A)', iostat=status) selected_commit
1532
         read(99, '(A)', iostat=status) selected_commit
1468
         close(99, status='delete')
1533
         close(99, status='delete')
1469
 
1534
 
@@ -1558,11 +1623,11 @@ contains
1558
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1623
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1559
                                   '--prompt="Branch to merge: " ' // &
1624
                                   '--prompt="Branch to merge: " ' // &
1560
                                   '--preview="echo Commits to merge:; git log --oneline --color=always HEAD..{} | head -20" ' // &
1625
                                   '--preview="echo Commits to merge:; git log --oneline --color=always HEAD..{} | head -20" ' // &
1561
-                                  '--preview-window=right:50% > /tmp/fuss_merge_select.txt', &
1626
+                                  '--preview-window=right:50% > ' // FUSS_TEMP // '', &
1562
                                   exitstat=status_code)
1627
                                   exitstat=status_code)
1563
 
1628
 
1564
         if (status_code /= 0) then
1629
         if (status_code /= 0) then
1565
-            call execute_command_line('rm -f /tmp/fuss_merge_select.txt', exitstat=status)
1630
+            call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status)
1566
             print '(A)', ''
1631
             print '(A)', ''
1567
             print '(A)', 'Operation cancelled.'
1632
             print '(A)', 'Operation cancelled.'
1568
             print '(A)', ''
1633
             print '(A)', ''
@@ -1573,7 +1638,7 @@ contains
1573
         end if
1638
         end if
1574
 
1639
 
1575
         ! Read selected branch
1640
         ! Read selected branch
1576
-        open(unit=99, file='/tmp/fuss_merge_select.txt', status='old', action='read')
1641
+        open(unit=99, file=FUSS_TEMP, status='old', action='read')
1577
         read(99, '(A)', iostat=status) selected_branch
1642
         read(99, '(A)', iostat=status) selected_branch
1578
         close(99, status='delete')
1643
         close(99, status='delete')
1579
 
1644
 
@@ -1656,7 +1721,7 @@ contains
1656
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1721
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1657
                                   '--prompt="Switch to branch: " ' // &
1722
                                   '--prompt="Switch to branch: " ' // &
1658
                                   '--preview="git log --oneline --graph --color=always {}" ' // &
1723
                                   '--preview="git log --oneline --graph --color=always {}" ' // &
1659
-                                  '--preview-window=right:50% > /tmp/fuss_branch_select.txt', &
1724
+                                  '--preview-window=right:50% > ' // FUSS_TEMP // '', &
1660
                                   exitstat=status_code)
1725
                                   exitstat=status_code)
1661
 
1726
 
1662
         ! Re-enable cbreak mode first
1727
         ! Re-enable cbreak mode first
@@ -1671,7 +1736,7 @@ contains
1671
         end if
1736
         end if
1672
 
1737
 
1673
         ! Read selected branch
1738
         ! Read selected branch
1674
-        open(unit=99, file='/tmp/fuss_branch_select.txt', status='old', action='read', iostat=status_code)
1739
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status_code)
1675
         if (status_code /= 0) then
1740
         if (status_code /= 0) then
1676
             print '(A)', 'Branch switch cancelled.'
1741
             print '(A)', 'Branch switch cancelled.'
1677
             print '(A)', ''
1742
             print '(A)', ''
@@ -1743,7 +1808,7 @@ contains
1743
         success = .false.
1808
         success = .false.
1744
 
1809
 
1745
         ! Get current branch name to prevent deleting it
1810
         ! Get current branch name to prevent deleting it
1746
-        call execute_command_line('git rev-parse --abbrev-ref HEAD > /tmp/fuss_current_branch.txt 2>&1', &
1811
+        call execute_command_line('git rev-parse --abbrev-ref HEAD > ' // FUSS_TEMP // ' 2>&1', &
1747
                                   exitstat=status_code)
1812
                                   exitstat=status_code)
1748
 
1813
 
1749
         if (status_code /= 0) then
1814
         if (status_code /= 0) then
@@ -1751,7 +1816,7 @@ contains
1751
             return
1816
             return
1752
         end if
1817
         end if
1753
 
1818
 
1754
-        open(unit=99, file='/tmp/fuss_current_branch.txt', status='old', action='read', iostat=status_code)
1819
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status_code)
1755
         if (status_code == 0) then
1820
         if (status_code == 0) then
1756
             read(99, '(A)', iostat=status_code) current_branch
1821
             read(99, '(A)', iostat=status_code) current_branch
1757
             close(99, status='delete')
1822
             close(99, status='delete')
@@ -1764,7 +1829,7 @@ contains
1764
 
1829
 
1765
         ! Use fzf to select branch to delete (exclude current branch)
1830
         ! Use fzf to select branch to delete (exclude current branch)
1766
         write(command, '(A,A,A)') 'git branch | grep -v "^* " | sed "s/^  //" | grep -v "^', trim(current_branch), &
1831
         write(command, '(A,A,A)') 'git branch | grep -v "^* " | sed "s/^  //" | grep -v "^', trim(current_branch), &
1767
-                                  '$" | fzf --height=15 --border=rounded --border-label=" ESC to cancel " --prompt="Delete branch: " > /tmp/fuss_branch_delete.txt'
1832
+                                  '$" | fzf --height=15 --border=rounded --border-label=" ESC to cancel " --prompt="Delete branch: " > ' // FUSS_TEMP // ''
1768
         call execute_command_line(trim(command), exitstat=status_code)
1833
         call execute_command_line(trim(command), exitstat=status_code)
1769
 
1834
 
1770
         ! Re-enable cbreak mode
1835
         ! Re-enable cbreak mode
@@ -1779,7 +1844,7 @@ contains
1779
         end if
1844
         end if
1780
 
1845
 
1781
         ! Read selected branch
1846
         ! Read selected branch
1782
-        open(unit=99, file='/tmp/fuss_branch_delete.txt', status='old', action='read', iostat=status_code)
1847
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status_code)
1783
         if (status_code /= 0) then
1848
         if (status_code /= 0) then
1784
             print '(A)', 'Branch deletion cancelled.'
1849
             print '(A)', 'Branch deletion cancelled.'
1785
             print '(A)', ''
1850
             print '(A)', ''
@@ -1895,7 +1960,7 @@ contains
1895
         success = .false.
1960
         success = .false.
1896
 
1961
 
1897
         ! Check if there are any stashes
1962
         ! Check if there are any stashes
1898
-        call execute_command_line('git stash list > /tmp/fuss_stash_check.txt 2>&1', exitstat=status_code)
1963
+        call execute_command_line('git stash list > ' // FUSS_TEMP // ' 2>&1', exitstat=status_code)
1899
         if (status_code /= 0) then
1964
         if (status_code /= 0) then
1900
             print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m'
1965
             print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m'
1901
             print '(A)', 'Press any key to continue...'
1966
             print '(A)', 'Press any key to continue...'
@@ -1903,15 +1968,15 @@ contains
1903
         end if
1968
         end if
1904
 
1969
 
1905
         ! Check if stash list is empty
1970
         ! Check if stash list is empty
1906
-        call execute_command_line('test -s /tmp/fuss_stash_check.txt', exitstat=status_code)
1971
+        call execute_command_line('test -s ' // FUSS_TEMP // '', exitstat=status_code)
1907
         if (status_code /= 0) then
1972
         if (status_code /= 0) then
1908
             print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m'
1973
             print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m'
1909
             print '(A)', 'Press any key to continue...'
1974
             print '(A)', 'Press any key to continue...'
1910
-            call execute_command_line('rm -f /tmp/fuss_stash_check.txt', exitstat=status_code)
1975
+            call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status_code)
1911
             return
1976
             return
1912
         end if
1977
         end if
1913
 
1978
 
1914
-        call execute_command_line('rm -f /tmp/fuss_stash_check.txt', exitstat=status_code)
1979
+        call execute_command_line('rm -f ' // FUSS_TEMP // '', exitstat=status_code)
1915
 
1980
 
1916
         ! Restore terminal for fzf
1981
         ! Restore terminal for fzf
1917
         call execute_command_line('stty sane < /dev/tty', exitstat=status_code)
1982
         call execute_command_line('stty sane < /dev/tty', exitstat=status_code)
@@ -1921,7 +1986,7 @@ contains
1921
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1986
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
1922
                                   '--prompt="Select stash: " ' // &
1987
                                   '--prompt="Select stash: " ' // &
1923
                                   '--preview="git stash show -p {1}" --preview-window=right:50% ' // &
1988
                                   '--preview="git stash show -p {1}" --preview-window=right:50% ' // &
1924
-                                  '> /tmp/fuss_stash_select.txt', &
1989
+                                  '> ' // FUSS_TEMP // '', &
1925
                                   exitstat=status_code)
1990
                                   exitstat=status_code)
1926
 
1991
 
1927
         ! Re-enable cbreak mode
1992
         ! Re-enable cbreak mode
@@ -1936,7 +2001,7 @@ contains
1936
         end if
2001
         end if
1937
 
2002
 
1938
         ! Read selected stash
2003
         ! Read selected stash
1939
-        open(unit=99, file='/tmp/fuss_stash_select.txt', status='old', action='read', iostat=status_code)
2004
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status_code)
1940
         if (status_code /= 0) then
2005
         if (status_code /= 0) then
1941
             print '(A)', 'Stash operation cancelled.'
2006
             print '(A)', 'Stash operation cancelled.'
1942
             print '(A)', ''
2007
             print '(A)', ''
@@ -2065,7 +2130,7 @@ contains
2065
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
2130
                                   'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // &
2066
                                   '--prompt="Reset to: " ' // &
2131
                                   '--prompt="Reset to: " ' // &
2067
                                   '--preview="git show --stat --color=always {1}" ' // &
2132
                                   '--preview="git show --stat --color=always {1}" ' // &
2068
-                                  '--preview-window=right:60% > /tmp/fuss_reset_commit.txt', &
2133
+                                  '--preview-window=right:60% > ' // FUSS_TEMP // '', &
2069
                                   exitstat=status_code)
2134
                                   exitstat=status_code)
2070
 
2135
 
2071
         if (status_code /= 0) then
2136
         if (status_code /= 0) then
@@ -2078,7 +2143,7 @@ contains
2078
         end if
2143
         end if
2079
 
2144
 
2080
         ! Read selected commit
2145
         ! Read selected commit
2081
-        open(unit=99, file='/tmp/fuss_reset_commit.txt', status='old', action='read', iostat=status)
2146
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status)
2082
         if (status /= 0) then
2147
         if (status /= 0) then
2083
             print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m'
2148
             print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m'
2084
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
2149
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
@@ -2103,11 +2168,11 @@ contains
2103
         print '(A)', 'Enter choice (1/2/3) or ESC to cancel: '
2168
         print '(A)', 'Enter choice (1/2/3) or ESC to cancel: '
2104
 
2169
 
2105
         ! Get mode choice
2170
         ! Get mode choice
2106
-        call execute_command_line('read -n 1 choice < /dev/tty; echo $choice > /tmp/fuss_reset_mode.txt', &
2171
+        call execute_command_line('read -n 1 choice < /dev/tty; echo $choice > ' // FUSS_TEMP // '', &
2107
                                   exitstat=status)
2172
                                   exitstat=status)
2108
 
2173
 
2109
         ! Read mode choice
2174
         ! Read mode choice
2110
-        open(unit=99, file='/tmp/fuss_reset_mode.txt', status='old', action='read', iostat=status)
2175
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status)
2111
         if (status /= 0) then
2176
         if (status /= 0) then
2112
             print '(A)', 'Reset cancelled.'
2177
             print '(A)', 'Reset cancelled.'
2113
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
2178
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
@@ -2130,11 +2195,11 @@ contains
2130
                 print '(A)', achar(27) // '[1;31mThis operation CANNOT be undone!' // achar(27) // '[0m'
2195
                 print '(A)', achar(27) // '[1;31mThis operation CANNOT be undone!' // achar(27) // '[0m'
2131
                 print '(A)', ''
2196
                 print '(A)', ''
2132
                 print '(A)', 'Type "yes" to confirm hard reset: '
2197
                 print '(A)', 'Type "yes" to confirm hard reset: '
2133
-                call execute_command_line('read conf < /dev/tty; echo $conf > /tmp/fuss_reset_confirm.txt', &
2198
+                call execute_command_line('read conf < /dev/tty; echo $conf > ' // FUSS_TEMP // '', &
2134
                                           exitstat=status)
2199
                                           exitstat=status)
2135
 
2200
 
2136
                 ! Read confirmation
2201
                 ! Read confirmation
2137
-                open(unit=99, file='/tmp/fuss_reset_confirm.txt', status='old', action='read', iostat=status)
2202
+                open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status)
2138
                 if (status == 0) then
2203
                 if (status == 0) then
2139
                     read(99, '(A)', iostat=status) confirmation
2204
                     read(99, '(A)', iostat=status) confirmation
2140
                     close(99)
2205
                     close(99)
@@ -2248,7 +2313,7 @@ contains
2248
                                   '--preview="git log --oneline --color=always {1}~1..HEAD | head -20" ' // &
2313
                                   '--preview="git log --oneline --color=always {1}~1..HEAD | head -20" ' // &
2249
                                   '--preview-window=right:60% ' // &
2314
                                   '--preview-window=right:60% ' // &
2250
                                   '--preview-label=" Commits that will be rebased " ' // &
2315
                                   '--preview-label=" Commits that will be rebased " ' // &
2251
-                                  '> /tmp/fuss_rebase_base.txt', &
2316
+                                  '> ' // FUSS_TEMP // '', &
2252
                                   exitstat=status_code)
2317
                                   exitstat=status_code)
2253
 
2318
 
2254
         if (status_code /= 0) then
2319
         if (status_code /= 0) then
@@ -2261,7 +2326,7 @@ contains
2261
         end if
2326
         end if
2262
 
2327
 
2263
         ! Read selected commit
2328
         ! Read selected commit
2264
-        open(unit=99, file='/tmp/fuss_rebase_base.txt', status='old', action='read', iostat=status)
2329
+        open(unit=99, file=FUSS_TEMP, status='old', action='read', iostat=status)
2265
         if (status /= 0) then
2330
         if (status /= 0) then
2266
             print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m'
2331
             print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m'
2267
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
2332
             call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status)
src/git_module.f90 → src/git_module.f90.phase23copied (97% similarity)
@@ -1,8 +1,13 @@
1
 module git_module
1
 module git_module
2
     use iso_fortran_env, only: error_unit
2
     use iso_fortran_env, only: error_unit
3
     use types_module
3
     use types_module
4
+    use cache_module
4
     implicit none
5
     implicit none
5
 
6
 
7
+    ! Shared temp file for reducing disk I/O and clutter
8
+    ! Reused across operations, deleted after each read
9
+    character(len=*), parameter :: FUSS_TEMP = '/tmp/fuss_tmp.txt'
10
+
6
 contains
11
 contains
7
 
12
 
8
     ! ========== Performance Optimization: Binary Search Functions ==========
13
     ! ========== Performance Optimization: Binary Search Functions ==========
@@ -198,7 +203,8 @@ contains
198
         end if
203
         end if
199
     end function unquote_filename
204
     end function unquote_filename
200
 
205
 
201
-    subroutine get_dirty_files(files, n_files)
206
+    ! Internal implementation - called directly for actual git operations
207
+    subroutine get_dirty_files_impl(files, n_files)
202
         type(file_entry), allocatable, intent(out) :: files(:)
208
         type(file_entry), allocatable, intent(out) :: files(:)
203
         integer, intent(out) :: n_files
209
         integer, intent(out) :: n_files
204
         integer :: iostat, unit_num, status_code
210
         integer :: iostat, unit_num, status_code
@@ -280,9 +286,10 @@ contains
280
             call quicksort_files(files, 1, n_files)
286
             call quicksort_files(files, 1, n_files)
281
         end if
287
         end if
282
         deallocate(temp_files)
288
         deallocate(temp_files)
283
-    end subroutine get_dirty_files
289
+    end subroutine get_dirty_files_impl
284
 
290
 
285
-    subroutine get_all_files(files, n_files)
291
+    ! Internal implementation - called directly for actual file operations
292
+    subroutine get_all_files_impl(files, n_files)
286
         type(file_entry), allocatable, intent(out) :: files(:)
293
         type(file_entry), allocatable, intent(out) :: files(:)
287
         integer, intent(out) :: n_files
294
         integer, intent(out) :: n_files
288
         integer :: iostat, unit_num, status_code, i
295
         integer :: iostat, unit_num, status_code, i
@@ -290,8 +297,8 @@ contains
290
         type(file_entry), allocatable :: dirty_files(:), temp_files(:)
297
         type(file_entry), allocatable :: dirty_files(:), temp_files(:)
291
         integer :: n_dirty, max_files
298
         integer :: n_dirty, max_files
292
 
299
 
293
-        ! First get dirty files
300
+        ! First get dirty files (call impl directly to avoid double caching)
294
-        call get_dirty_files(dirty_files, n_dirty)
301
+        call get_dirty_files_impl(dirty_files, n_dirty)
295
 
302
 
296
         ! Get all files using find
303
         ! Get all files using find
297
         call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code)
304
         call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code)
@@ -369,8 +376,66 @@ contains
369
         end if
376
         end if
370
         deallocate(temp_files)
377
         deallocate(temp_files)
371
         if (allocated(dirty_files)) deallocate(dirty_files)
378
         if (allocated(dirty_files)) deallocate(dirty_files)
379
+    end subroutine get_all_files_impl
380
+
381
+    ! ========== Cached Wrappers for File Retrieval ==========
382
+
383
+    ! Get dirty files with caching support
384
+    subroutine get_dirty_files(files, n_files, force_refresh)
385
+        type(file_entry), allocatable, intent(out) :: files(:)
386
+        integer, intent(out) :: n_files
387
+        logical, intent(in), optional :: force_refresh
388
+        logical :: do_refresh
389
+
390
+        do_refresh = .false.
391
+        if (present(force_refresh)) do_refresh = force_refresh
392
+
393
+        ! Force refresh if requested
394
+        if (do_refresh) then
395
+            call invalidate_cache(dirty_cache)
396
+        end if
397
+
398
+        ! Check cache validity
399
+        if (cache_valid(dirty_cache)) then
400
+            ! Use cached results
401
+            call get_cached_files(dirty_cache, files, n_files)
402
+        else
403
+            ! Call actual implementation
404
+            call get_dirty_files_impl(files, n_files)
405
+            ! Update cache with fresh data
406
+            call update_cache(dirty_cache, files, n_files)
407
+        end if
408
+    end subroutine get_dirty_files
409
+
410
+    ! Get all files with caching support
411
+    subroutine get_all_files(files, n_files, force_refresh)
412
+        type(file_entry), allocatable, intent(out) :: files(:)
413
+        integer, intent(out) :: n_files
414
+        logical, intent(in), optional :: force_refresh
415
+        logical :: do_refresh
416
+
417
+        do_refresh = .false.
418
+        if (present(force_refresh)) do_refresh = force_refresh
419
+
420
+        ! Force refresh if requested
421
+        if (do_refresh) then
422
+            call invalidate_cache(all_cache)
423
+        end if
424
+
425
+        ! Check cache validity
426
+        if (cache_valid(all_cache)) then
427
+            ! Use cached results
428
+            call get_cached_files(all_cache, files, n_files)
429
+        else
430
+            ! Call actual implementation
431
+            call get_all_files_impl(files, n_files)
432
+            ! Update cache with fresh data
433
+            call update_cache(all_cache, files, n_files)
434
+        end if
372
     end subroutine get_all_files
435
     end subroutine get_all_files
373
 
436
 
437
+    ! ========== End Cached Wrappers ==========
438
+
374
     subroutine resize_array(array, new_size)
439
     subroutine resize_array(array, new_size)
375
         type(file_entry), allocatable, intent(inout) :: array(:)
440
         type(file_entry), allocatable, intent(inout) :: array(:)
376
         integer, intent(in) :: new_size
441
         integer, intent(in) :: new_size
src/terminal_module.f90modified
@@ -1,6 +1,9 @@
1
 module terminal_module
1
 module terminal_module
2
     implicit none
2
     implicit none
3
 
3
 
4
+    ! Shared temp file for reducing disk I/O
5
+    character(len=*), parameter :: FUSS_TEMP = '/tmp/fuss_tmp.txt'
6
+
4
 contains
7
 contains
5
 
8
 
6
     subroutine enter_alternate_screen()
9
     subroutine enter_alternate_screen()
@@ -85,11 +88,11 @@ contains
85
         height = 24  ! Default fallback
88
         height = 24  ! Default fallback
86
 
89
 
87
         ! Try method 1: Use stty size to get terminal dimensions
90
         ! Try method 1: Use stty size to get terminal dimensions
88
-        call execute_command_line('stty size < /dev/tty 2>/dev/null | cut -d" " -f1 > /tmp/fuss_term_height.txt', &
91
+        call execute_command_line('stty size < /dev/tty 2>/dev/null | cut -d" " -f1 > ' // FUSS_TEMP // '', &
89
                                   exitstat=status)
92
                                   exitstat=status)
90
 
93
 
91
         if (status == 0) then
94
         if (status == 0) then
92
-            open(newunit=unit_num, file='/tmp/fuss_term_height.txt', status='old', action='read', iostat=iostat)
95
+            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
93
             if (iostat == 0) then
96
             if (iostat == 0) then
94
                 read(unit_num, *, iostat=iostat) height
97
                 read(unit_num, *, iostat=iostat) height
95
                 close(unit_num, status='delete')
98
                 close(unit_num, status='delete')
@@ -99,10 +102,10 @@ contains
99
         end if
102
         end if
100
 
103
 
101
         ! Try method 2: tput lines
104
         ! Try method 2: tput lines
102
-        call execute_command_line('tput lines < /dev/tty > /tmp/fuss_term_height.txt 2>/dev/null', exitstat=status)
105
+        call execute_command_line('tput lines < /dev/tty > ' // FUSS_TEMP // ' 2>/dev/null', exitstat=status)
103
 
106
 
104
         if (status == 0) then
107
         if (status == 0) then
105
-            open(newunit=unit_num, file='/tmp/fuss_term_height.txt', status='old', action='read', iostat=iostat)
108
+            open(newunit=unit_num, file=FUSS_TEMP, status='old', action='read', iostat=iostat)
106
             if (iostat == 0) then
109
             if (iostat == 0) then
107
                 read(unit_num, *, iostat=iostat) height
110
                 read(unit_num, *, iostat=iostat) height
108
                 close(unit_num, status='delete')
111
                 close(unit_num, status='delete')