cleanup disk i/o
- SHA
d14d0a18726238325b8970eaade8a0e937cbbc4e- Parents
-
53c7a91 - Tree
8f3fd7b
d14d0a1
d14d0a18726238325b8970eaade8a0e937cbbc4e53c7a91
8f3fd7b| Status | File | + | - |
|---|---|---|---|
| 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 | 5 | use tree_module |
| 6 | 6 | use display_module |
| 7 | 7 | use terminal_module |
| 8 | + use cache_module | |
| 8 | 9 | implicit none |
| 9 | 10 | |
| 10 | 11 | ! Main program variables |
| 11 | 12 | logical :: show_all, interactive |
| 12 | 13 | character(len=:), allocatable :: root_path |
| 13 | 14 | |
| 15 | + ! Initialize caches for performance optimization | |
| 16 | + call init_caches() | |
| 17 | + | |
| 14 | 18 | ! Parse command line arguments |
| 15 | 19 | call parse_arguments(show_all, interactive) |
| 16 | 20 | |
@@ -124,9 +128,9 @@ contains | ||
| 124 | 128 | character(len=1024) :: buffer |
| 125 | 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 | 134 | read(99, '(A)') buffer |
| 131 | 135 | close(99, status='delete') |
| 132 | 136 | |
@@ -173,6 +177,8 @@ contains | ||
| 173 | 177 | logical :: running, hide_dotfiles |
| 174 | 178 | character(len=256) :: repo_name, branch_name, term_program |
| 175 | 179 | integer :: term_height, viewport_offset, visible_items, top_padding |
| 180 | + integer :: prev_selected, prev_viewport | |
| 181 | + logical :: needs_full_redraw | |
| 176 | 182 | type(tree_node), pointer :: tree_root |
| 177 | 183 | |
| 178 | 184 | ! Initialize tree pointer |
@@ -235,6 +241,11 @@ contains | ||
| 235 | 241 | viewport_offset = 1 |
| 236 | 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 | 249 | ! Enter alternate screen buffer (preserves terminal content) |
| 239 | 250 | call enter_alternate_screen() |
| 240 | 251 | |
@@ -252,10 +263,24 @@ contains | ||
| 252 | 263 | viewport_offset = n_items - visible_items + 1 |
| 253 | 264 | end if |
| 254 | 265 | |
| 255 | - ! Clear screen and redraw | |
| 256 | - call clear_screen() | |
| 257 | - call draw_interactive_tree(tree_root, items, n_items, selected, & | |
| 258 | - repo_name, branch_name, viewport_offset, visible_items, top_padding) | |
| 266 | + ! Conditional redraw for performance optimization | |
| 267 | + if (needs_full_redraw .or. viewport_offset /= prev_viewport) then | |
| 268 | + ! Full redraw needed: viewport scrolled or forced refresh | |
| 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 | 285 | ! Read key |
| 261 | 286 | call read_key(key) |
@@ -278,6 +303,8 @@ contains | ||
| 278 | 303 | call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles) |
| 279 | 304 | ! Adjust selection if needed |
| 280 | 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 | 308 | end if |
| 282 | 309 | case ('a') ! Stage file or directory (lowercase to avoid conflict with arrow A) |
| 283 | 310 | ! Check if it's a directory - stage all files in it |
@@ -285,62 +312,72 @@ contains | ||
| 285 | 312 | call git_stage_directory(items(selected)%path) |
| 286 | 313 | ! Refresh files after staging directory |
| 287 | 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 | 317 | ! Otherwise it's a file - stage individual file |
| 290 | 318 | else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then |
| 291 | 319 | call git_add_file(items(selected)%path) |
| 292 | 320 | ! Refresh files after git add |
| 293 | 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 | 324 | end if |
| 296 | 325 | case ('u') ! Unstage file (lowercase) |
| 297 | 326 | if (items(selected)%is_file .and. items(selected)%is_staged) then |
| 298 | 327 | call git_unstage_file(items(selected)%path) |
| 299 | 328 | ! Refresh files after git unstage |
| 300 | 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 | 332 | end if |
| 303 | 333 | case ('S') ! Stage all (Shift+S to avoid conflict with up arrow 'A') |
| 304 | 334 | call git_stage_all() |
| 305 | 335 | ! Refresh files after staging all |
| 306 | 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 | 339 | case ('U') ! Unstage all (Shift+U) |
| 309 | 340 | call git_unstage_all() |
| 310 | 341 | ! Refresh files after unstaging all |
| 311 | 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 | 345 | case ('m') ! Commit (lowercase) |
| 314 | 346 | call commit_prompt() |
| 315 | 347 | ! Refresh files after commit |
| 316 | 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 | 351 | case ('M') ! Amend last commit (Shift+m) |
| 319 | 352 | call amend_commit_prompt() |
| 320 | 353 | ! Refresh files after amend commit |
| 321 | 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 | 357 | case ('s') ! Show git status (lowercase) |
| 324 | 358 | call show_status_view() |
| 325 | 359 | case ('p') ! Push (lowercase) |
| 326 | 360 | call push_prompt() |
| 327 | 361 | ! Refresh files after push |
| 328 | 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 | 365 | case ('t') ! Tag (lowercase) |
| 331 | 366 | call tag_prompt() |
| 332 | 367 | case ('b') ! Switch branch |
| 333 | 368 | call branch_switch_prompt() |
| 334 | 369 | ! Refresh files after branch switch |
| 335 | 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 | 373 | ! Update branch name display |
| 338 | 374 | call get_repo_info(repo_name, branch_name) |
| 339 | 375 | case ('n') ! Create new branch |
| 340 | 376 | call branch_create_prompt() |
| 341 | 377 | ! Refresh files after branch creation |
| 342 | 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 | 381 | ! Update branch name display |
| 345 | 382 | call get_repo_info(repo_name, branch_name) |
| 346 | 383 | case ('R') ! Delete branch (Shift+r, since 'r' is used for delete file) |
@@ -350,7 +387,8 @@ contains | ||
| 350 | 387 | call git_fetch() |
| 351 | 388 | ! Refresh files after fetch and include files with incoming changes |
| 352 | 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 | 392 | case ('d') ! Git diff with less |
| 355 | 393 | if (items(selected)%is_file) then |
| 356 | 394 | call git_diff_file(items(selected)%path, items(selected)%has_incoming) |
@@ -368,42 +406,49 @@ contains | ||
| 368 | 406 | call delete_prompt(items(selected)%path, items(selected)%is_untracked) |
| 369 | 407 | ! Refresh files after delete |
| 370 | 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 | 411 | end if |
| 373 | 412 | case ('x', 'X') ! Discard changes |
| 374 | 413 | if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then |
| 375 | 414 | call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked) |
| 376 | 415 | ! Refresh files after discard |
| 377 | 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 | 419 | end if |
| 380 | 420 | case ('l') ! Git pull |
| 381 | 421 | call git_pull() |
| 382 | 422 | ! Refresh files after pull (incoming indicators will automatically clear) |
| 383 | 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 | 426 | ! Note: After successful pull, git diff will show no upstream differences |
| 386 | 427 | ! so has_incoming will be .false. for all files automatically |
| 387 | 428 | case ('z') ! Stash push (save changes) |
| 388 | 429 | call stash_push_prompt() |
| 389 | 430 | ! Refresh files after stash |
| 390 | 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 | 434 | case ('Z') ! Stash pop/apply (restore changes) |
| 393 | 435 | call stash_pop_apply_prompt() |
| 394 | 436 | ! Refresh files after stash pop/apply |
| 395 | 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 | 440 | case ('y') ! Cherry-pick (yank commit) |
| 398 | 441 | call cherry_pick_prompt() |
| 399 | 442 | ! Refresh files after cherry-pick |
| 400 | 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 | 446 | case ('v') ! Revert commit |
| 403 | 447 | call revert_commit_prompt() |
| 404 | 448 | ! Refresh files after revert |
| 405 | 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 | 452 | case ('h') ! Show commit history |
| 408 | 453 | call history_browser_prompt() |
| 409 | 454 | ! No refresh needed - read-only |
@@ -414,19 +459,22 @@ contains | ||
| 414 | 459 | call merge_branch_prompt() |
| 415 | 460 | ! Refresh files after merge |
| 416 | 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 | 464 | ! Update branch name display in case we merged |
| 419 | 465 | call get_repo_info(repo_name, branch_name) |
| 420 | 466 | case ('O') ! Reset (Shift+o - "Oh no, undo!") |
| 421 | 467 | call reset_prompt() |
| 422 | 468 | ! Refresh files after reset |
| 423 | 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 | 472 | case ('I') ! Interactive rebase (Shift+i) |
| 426 | 473 | call rebase_prompt() |
| 427 | 474 | ! Refresh files after rebase |
| 428 | 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 | 478 | case ('.') ! Toggle hiding dotfiles and gitignored files |
| 431 | 479 | hide_dotfiles = .not. hide_dotfiles |
| 432 | 480 | ! Rebuild item list with new filter |
@@ -434,6 +482,8 @@ contains | ||
| 434 | 482 | ! Adjust selection and visible_items for new item count |
| 435 | 483 | if (selected > n_items .and. n_items > 0) selected = n_items |
| 436 | 484 | if (n_items > 0 .and. selected < 1) selected = 1 |
| 485 | + ! Force full redraw after filter change | |
| 486 | + needs_full_redraw = .true. | |
| 437 | 487 | ! Recalculate visible_items in case n_items changed |
| 438 | 488 | visible_items = term_height - 6 |
| 439 | 489 | if (visible_items < 3) visible_items = 3 |
@@ -1211,18 +1261,19 @@ contains | ||
| 1211 | 1261 | end subroutine branch_delete_prompt |
| 1212 | 1262 | |
| 1213 | 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 | 1265 | ! Centralized helper to refresh file list and rebuild tree |
| 1216 | 1266 | ! Consolidates the pattern repeated 20+ times in the codebase |
| 1267 | + ! Now with caching support for performance optimization | |
| 1217 | 1268 | logical, intent(in) :: show_all, hide_dotfiles |
| 1218 | 1269 | type(file_entry), allocatable, intent(inout) :: files(:) |
| 1219 | 1270 | integer, intent(inout) :: n_files, n_items, selected |
| 1220 | 1271 | type(tree_node), pointer, intent(inout) :: tree_root |
| 1221 | 1272 | type(selectable_item), allocatable, intent(inout) :: items(:) |
| 1222 | 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 | 1278 | ! Handle optional parameters |
| 1228 | 1279 | do_exit_if_empty = .false. |
@@ -1231,12 +1282,15 @@ contains | ||
| 1231 | 1282 | do_include_incoming = .false. |
| 1232 | 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 | 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 | 1291 | call mark_incoming_changes(files, n_files) |
| 1238 | 1292 | else |
| 1239 | - call get_dirty_files(files, n_files) | |
| 1293 | + call get_dirty_files(files, n_files, force_refresh=do_force_refresh) | |
| 1240 | 1294 | if (do_include_incoming) then |
| 1241 | 1295 | ! For fetch/pull: also include files with only incoming changes |
| 1242 | 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 | 5 | use tree_module |
| 6 | 6 | use display_module |
| 7 | 7 | use terminal_module |
| 8 | + use cache_module | |
| 8 | 9 | implicit none |
| 9 | 10 | |
| 10 | 11 | ! Main program variables |
| 11 | 12 | logical :: show_all, interactive |
| 12 | 13 | character(len=:), allocatable :: root_path |
| 13 | 14 | |
| 15 | + ! Initialize caches for performance optimization | |
| 16 | + call init_caches() | |
| 17 | + | |
| 14 | 18 | ! Parse command line arguments |
| 15 | 19 | call parse_arguments(show_all, interactive) |
| 16 | 20 | |
@@ -1211,18 +1215,19 @@ contains | ||
| 1211 | 1215 | end subroutine branch_delete_prompt |
| 1212 | 1216 | |
| 1213 | 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 | 1219 | ! Centralized helper to refresh file list and rebuild tree |
| 1216 | 1220 | ! Consolidates the pattern repeated 20+ times in the codebase |
| 1221 | + ! Now with caching support for performance optimization | |
| 1217 | 1222 | logical, intent(in) :: show_all, hide_dotfiles |
| 1218 | 1223 | type(file_entry), allocatable, intent(inout) :: files(:) |
| 1219 | 1224 | integer, intent(inout) :: n_files, n_items, selected |
| 1220 | 1225 | type(tree_node), pointer, intent(inout) :: tree_root |
| 1221 | 1226 | type(selectable_item), allocatable, intent(inout) :: items(:) |
| 1222 | 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 | 1232 | ! Handle optional parameters |
| 1228 | 1233 | do_exit_if_empty = .false. |
@@ -1231,12 +1236,15 @@ contains | ||
| 1231 | 1236 | do_include_incoming = .false. |
| 1232 | 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 | 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 | 1245 | call mark_incoming_changes(files, n_files) |
| 1238 | 1246 | else |
| 1239 | - call get_dirty_files(files, n_files) | |
| 1247 | + call get_dirty_files(files, n_files, force_refresh=do_force_refresh) | |
| 1240 | 1248 | if (do_include_incoming) then |
| 1241 | 1249 | ! For fetch/pull: also include files with only incoming changes |
| 1242 | 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 | 5 | use tree_module |
| 6 | 6 | use display_module |
| 7 | 7 | use terminal_module |
| 8 | + use cache_module | |
| 8 | 9 | implicit none |
| 9 | 10 | |
| 10 | 11 | ! Main program variables |
| 11 | 12 | logical :: show_all, interactive |
| 12 | 13 | character(len=:), allocatable :: root_path |
| 13 | 14 | |
| 15 | + ! Initialize caches for performance optimization | |
| 16 | + call init_caches() | |
| 17 | + | |
| 14 | 18 | ! Parse command line arguments |
| 15 | 19 | call parse_arguments(show_all, interactive) |
| 16 | 20 | |
@@ -235,6 +239,13 @@ contains | ||
| 235 | 239 | viewport_offset = 1 |
| 236 | 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 | 249 | ! Enter alternate screen buffer (preserves terminal content) |
| 239 | 250 | call enter_alternate_screen() |
| 240 | 251 | |
@@ -252,10 +263,24 @@ contains | ||
| 252 | 263 | viewport_offset = n_items - visible_items + 1 |
| 253 | 264 | end if |
| 254 | 265 | |
| 255 | - ! Clear screen and redraw | |
| 256 | - call clear_screen() | |
| 257 | - call draw_interactive_tree(tree_root, items, n_items, selected, & | |
| 258 | - repo_name, branch_name, viewport_offset, visible_items, top_padding) | |
| 266 | + ! Conditional redraw for performance optimization | |
| 267 | + if (needs_full_redraw .or. viewport_offset /= prev_viewport) then | |
| 268 | + ! Full redraw needed: viewport scrolled or forced refresh | |
| 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 | 285 | ! Read key |
| 261 | 286 | call read_key(key) |
@@ -278,6 +303,8 @@ contains | ||
| 278 | 303 | call rebuild_item_list_from_tree(tree_root, items, n_items, hide_dotfiles) |
| 279 | 304 | ! Adjust selection if needed |
| 280 | 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 | 308 | end if |
| 282 | 309 | case ('a') ! Stage file or directory (lowercase to avoid conflict with arrow A) |
| 283 | 310 | ! Check if it's a directory - stage all files in it |
@@ -285,62 +312,63 @@ contains | ||
| 285 | 312 | call git_stage_directory(items(selected)%path) |
| 286 | 313 | ! Refresh files after staging directory |
| 287 | 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 | 317 | ! Otherwise it's a file - stage individual file |
| 290 | 318 | else if (items(selected)%is_file .and. (items(selected)%is_unstaged .or. items(selected)%is_untracked)) then |
| 291 | 319 | call git_add_file(items(selected)%path) |
| 292 | 320 | ! Refresh files after git add |
| 293 | 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 | 323 | end if |
| 296 | 324 | case ('u') ! Unstage file (lowercase) |
| 297 | 325 | if (items(selected)%is_file .and. items(selected)%is_staged) then |
| 298 | 326 | call git_unstage_file(items(selected)%path) |
| 299 | 327 | ! Refresh files after git unstage |
| 300 | 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 | 330 | end if |
| 303 | 331 | case ('S') ! Stage all (Shift+S to avoid conflict with up arrow 'A') |
| 304 | 332 | call git_stage_all() |
| 305 | 333 | ! Refresh files after staging all |
| 306 | 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 | 336 | case ('U') ! Unstage all (Shift+U) |
| 309 | 337 | call git_unstage_all() |
| 310 | 338 | ! Refresh files after unstaging all |
| 311 | 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 | 341 | case ('m') ! Commit (lowercase) |
| 314 | 342 | call commit_prompt() |
| 315 | 343 | ! Refresh files after commit |
| 316 | 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 | 346 | case ('M') ! Amend last commit (Shift+m) |
| 319 | 347 | call amend_commit_prompt() |
| 320 | 348 | ! Refresh files after amend commit |
| 321 | 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 | 351 | case ('s') ! Show git status (lowercase) |
| 324 | 352 | call show_status_view() |
| 325 | 353 | case ('p') ! Push (lowercase) |
| 326 | 354 | call push_prompt() |
| 327 | 355 | ! Refresh files after push |
| 328 | 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 | 358 | case ('t') ! Tag (lowercase) |
| 331 | 359 | call tag_prompt() |
| 332 | 360 | case ('b') ! Switch branch |
| 333 | 361 | call branch_switch_prompt() |
| 334 | 362 | ! Refresh files after branch switch |
| 335 | 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 | 365 | ! Update branch name display |
| 338 | 366 | call get_repo_info(repo_name, branch_name) |
| 339 | 367 | case ('n') ! Create new branch |
| 340 | 368 | call branch_create_prompt() |
| 341 | 369 | ! Refresh files after branch creation |
| 342 | 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 | 372 | ! Update branch name display |
| 345 | 373 | call get_repo_info(repo_name, branch_name) |
| 346 | 374 | case ('R') ! Delete branch (Shift+r, since 'r' is used for delete file) |
@@ -350,7 +378,7 @@ contains | ||
| 350 | 378 | call git_fetch() |
| 351 | 379 | ! Refresh files after fetch and include files with incoming changes |
| 352 | 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 | 382 | case ('d') ! Git diff with less |
| 355 | 383 | if (items(selected)%is_file) then |
| 356 | 384 | call git_diff_file(items(selected)%path, items(selected)%has_incoming) |
@@ -368,42 +396,42 @@ contains | ||
| 368 | 396 | call delete_prompt(items(selected)%path, items(selected)%is_untracked) |
| 369 | 397 | ! Refresh files after delete |
| 370 | 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 | 400 | end if |
| 373 | 401 | case ('x', 'X') ! Discard changes |
| 374 | 402 | if (items(selected)%is_file .and. (items(selected)%is_staged .or. items(selected)%is_unstaged .or. items(selected)%is_untracked)) then |
| 375 | 403 | call discard_prompt(items(selected)%path, items(selected)%is_staged, items(selected)%is_untracked) |
| 376 | 404 | ! Refresh files after discard |
| 377 | 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 | 407 | end if |
| 380 | 408 | case ('l') ! Git pull |
| 381 | 409 | call git_pull() |
| 382 | 410 | ! Refresh files after pull (incoming indicators will automatically clear) |
| 383 | 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 | 413 | ! Note: After successful pull, git diff will show no upstream differences |
| 386 | 414 | ! so has_incoming will be .false. for all files automatically |
| 387 | 415 | case ('z') ! Stash push (save changes) |
| 388 | 416 | call stash_push_prompt() |
| 389 | 417 | ! Refresh files after stash |
| 390 | 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 | 420 | case ('Z') ! Stash pop/apply (restore changes) |
| 393 | 421 | call stash_pop_apply_prompt() |
| 394 | 422 | ! Refresh files after stash pop/apply |
| 395 | 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 | 425 | case ('y') ! Cherry-pick (yank commit) |
| 398 | 426 | call cherry_pick_prompt() |
| 399 | 427 | ! Refresh files after cherry-pick |
| 400 | 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 | 430 | case ('v') ! Revert commit |
| 403 | 431 | call revert_commit_prompt() |
| 404 | 432 | ! Refresh files after revert |
| 405 | 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 | 435 | case ('h') ! Show commit history |
| 408 | 436 | call history_browser_prompt() |
| 409 | 437 | ! No refresh needed - read-only |
@@ -414,19 +442,19 @@ contains | ||
| 414 | 442 | call merge_branch_prompt() |
| 415 | 443 | ! Refresh files after merge |
| 416 | 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 | 446 | ! Update branch name display in case we merged |
| 419 | 447 | call get_repo_info(repo_name, branch_name) |
| 420 | 448 | case ('O') ! Reset (Shift+o - "Oh no, undo!") |
| 421 | 449 | call reset_prompt() |
| 422 | 450 | ! Refresh files after reset |
| 423 | 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 | 453 | case ('I') ! Interactive rebase (Shift+i) |
| 426 | 454 | call rebase_prompt() |
| 427 | 455 | ! Refresh files after rebase |
| 428 | 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 | 458 | case ('.') ! Toggle hiding dotfiles and gitignored files |
| 431 | 459 | hide_dotfiles = .not. hide_dotfiles |
| 432 | 460 | ! Rebuild item list with new filter |
@@ -1211,18 +1239,19 @@ contains | ||
| 1211 | 1239 | end subroutine branch_delete_prompt |
| 1212 | 1240 | |
| 1213 | 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 | 1243 | ! Centralized helper to refresh file list and rebuild tree |
| 1216 | 1244 | ! Consolidates the pattern repeated 20+ times in the codebase |
| 1245 | + ! Now with caching support for performance optimization | |
| 1217 | 1246 | logical, intent(in) :: show_all, hide_dotfiles |
| 1218 | 1247 | type(file_entry), allocatable, intent(inout) :: files(:) |
| 1219 | 1248 | integer, intent(inout) :: n_files, n_items, selected |
| 1220 | 1249 | type(tree_node), pointer, intent(inout) :: tree_root |
| 1221 | 1250 | type(selectable_item), allocatable, intent(inout) :: items(:) |
| 1222 | 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 | 1256 | ! Handle optional parameters |
| 1228 | 1257 | do_exit_if_empty = .false. |
@@ -1231,12 +1260,15 @@ contains | ||
| 1231 | 1260 | do_include_incoming = .false. |
| 1232 | 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 | 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 | 1269 | call mark_incoming_changes(files, n_files) |
| 1238 | 1270 | else |
| 1239 | - call get_dirty_files(files, n_files) | |
| 1271 | + call get_dirty_files(files, n_files, force_refresh=do_force_refresh) | |
| 1240 | 1272 | if (do_include_incoming) then |
| 1241 | 1273 | ! For fetch/pull: also include files with only incoming changes |
| 1242 | 1274 | call add_incoming_files(files, n_files) |
src/git_module.f90modified@@ -1,8 +1,13 @@ | ||
| 1 | 1 | module git_module |
| 2 | 2 | use iso_fortran_env, only: error_unit |
| 3 | 3 | use types_module |
| 4 | + use cache_module | |
| 4 | 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 | 11 | contains |
| 7 | 12 | |
| 8 | 13 | ! ========== Performance Optimization: Binary Search Functions ========== |
@@ -198,7 +203,8 @@ contains | ||
| 198 | 203 | end if |
| 199 | 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 | 208 | type(file_entry), allocatable, intent(out) :: files(:) |
| 203 | 209 | integer, intent(out) :: n_files |
| 204 | 210 | integer :: iostat, unit_num, status_code |
@@ -280,9 +286,10 @@ contains | ||
| 280 | 286 | call quicksort_files(files, 1, n_files) |
| 281 | 287 | end if |
| 282 | 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 | 293 | type(file_entry), allocatable, intent(out) :: files(:) |
| 287 | 294 | integer, intent(out) :: n_files |
| 288 | 295 | integer :: iostat, unit_num, status_code, i |
@@ -290,8 +297,8 @@ contains | ||
| 290 | 297 | type(file_entry), allocatable :: dirty_files(:), temp_files(:) |
| 291 | 298 | integer :: n_dirty, max_files |
| 292 | 299 | |
| 293 | - ! First get dirty files | |
| 294 | - call get_dirty_files(dirty_files, n_dirty) | |
| 300 | + ! First get dirty files (call impl directly to avoid double caching) | |
| 301 | + call get_dirty_files_impl(dirty_files, n_dirty) | |
| 295 | 302 | |
| 296 | 303 | ! Get all files using find |
| 297 | 304 | call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code) |
@@ -369,8 +376,66 @@ contains | ||
| 369 | 376 | end if |
| 370 | 377 | deallocate(temp_files) |
| 371 | 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 | 435 | end subroutine get_all_files |
| 373 | 436 | |
| 437 | + ! ========== End Cached Wrappers ========== | |
| 438 | + | |
| 374 | 439 | subroutine resize_array(array, new_size) |
| 375 | 440 | type(file_entry), allocatable, intent(inout) :: array(:) |
| 376 | 441 | integer, intent(in) :: new_size |
@@ -656,12 +721,12 @@ contains | ||
| 656 | 721 | message = '' |
| 657 | 722 | |
| 658 | 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 | 726 | if (status /= 0) return |
| 662 | 727 | |
| 663 | 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 | 730 | if (iostat /= 0) return |
| 666 | 731 | |
| 667 | 732 | ! Read first line (single-line commit message) |
@@ -767,7 +832,7 @@ contains | ||
| 767 | 832 | end if |
| 768 | 833 | |
| 769 | 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 | 836 | exitstat=status) |
| 772 | 837 | |
| 773 | 838 | if (status /= 0) then |
@@ -777,7 +842,7 @@ contains | ||
| 777 | 842 | end if |
| 778 | 843 | |
| 779 | 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 | 846 | if (iostat /= 0) then |
| 782 | 847 | print '(A)', achar(27) // '[31m✗ Could not read branch name' // achar(27) // '[0m' |
| 783 | 848 | print '(A)', 'Press any key to continue...' |
@@ -844,11 +909,11 @@ contains | ||
| 844 | 909 | branch_name = '' |
| 845 | 910 | |
| 846 | 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 | 913 | exitstat=status_code) |
| 849 | 914 | |
| 850 | 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 | 917 | if (iostat == 0) then |
| 853 | 918 | read(unit_num, '(A)', iostat=iostat) repo_name |
| 854 | 919 | close(unit_num, status='delete') |
@@ -856,11 +921,11 @@ contains | ||
| 856 | 921 | end if |
| 857 | 922 | |
| 858 | 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 | 925 | exitstat=status_code) |
| 861 | 926 | |
| 862 | 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 | 929 | if (iostat == 0) then |
| 865 | 930 | read(unit_num, '(A)', iostat=iostat) branch_name |
| 866 | 931 | close(unit_num, status='delete') |
@@ -886,7 +951,7 @@ contains | ||
| 886 | 951 | ! Use fzf to select remote branch |
| 887 | 952 | call execute_command_line('git branch -r | grep -v HEAD | sed "s/^ //" | ' // & |
| 888 | 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 | 955 | exitstat=status_code) |
| 891 | 956 | |
| 892 | 957 | if (status_code /= 0) then |
@@ -899,7 +964,7 @@ contains | ||
| 899 | 964 | end if |
| 900 | 965 | |
| 901 | 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 | 968 | if (status_code == 0) then |
| 904 | 969 | read(99, '(A)', iostat=status_code) selected_branch |
| 905 | 970 | close(99, status='delete') |
@@ -1334,11 +1399,11 @@ contains | ||
| 1334 | 1399 | 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1335 | 1400 | '--prompt="Source branch: " ' // & |
| 1336 | 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 | 1403 | exitstat=status_code) |
| 1339 | 1404 | |
| 1340 | 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 | 1407 | print '(A)', '' |
| 1343 | 1408 | print '(A)', 'Operation cancelled.' |
| 1344 | 1409 | print '(A)', '' |
@@ -1349,7 +1414,7 @@ contains | ||
| 1349 | 1414 | end if |
| 1350 | 1415 | |
| 1351 | 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 | 1418 | read(99, '(A)', iostat=status) selected_branch |
| 1354 | 1419 | close(99, status='delete') |
| 1355 | 1420 | |
@@ -1372,11 +1437,11 @@ contains | ||
| 1372 | 1437 | 'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1373 | 1438 | '--prompt="Commit: " ' // & |
| 1374 | 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 | 1441 | call execute_command_line(trim(command), exitstat=status_code) |
| 1377 | 1442 | |
| 1378 | 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 | 1445 | print '(A)', '' |
| 1381 | 1446 | print '(A)', 'Operation cancelled.' |
| 1382 | 1447 | print '(A)', '' |
@@ -1387,7 +1452,7 @@ contains | ||
| 1387 | 1452 | end if |
| 1388 | 1453 | |
| 1389 | 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 | 1456 | read(99, '(A)', iostat=status) selected_commit |
| 1392 | 1457 | close(99, status='delete') |
| 1393 | 1458 | |
@@ -1448,11 +1513,11 @@ contains | ||
| 1448 | 1513 | 'fzf --height=20 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1449 | 1514 | '--prompt="Commit to revert: " ' // & |
| 1450 | 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 | 1517 | exitstat=status_code) |
| 1453 | 1518 | |
| 1454 | 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 | 1521 | print '(A)', '' |
| 1457 | 1522 | print '(A)', 'Operation cancelled.' |
| 1458 | 1523 | print '(A)', '' |
@@ -1463,7 +1528,7 @@ contains | ||
| 1463 | 1528 | end if |
| 1464 | 1529 | |
| 1465 | 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 | 1532 | read(99, '(A)', iostat=status) selected_commit |
| 1468 | 1533 | close(99, status='delete') |
| 1469 | 1534 | |
@@ -1558,11 +1623,11 @@ contains | ||
| 1558 | 1623 | 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1559 | 1624 | '--prompt="Branch to merge: " ' // & |
| 1560 | 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 | 1627 | exitstat=status_code) |
| 1563 | 1628 | |
| 1564 | 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 | 1631 | print '(A)', '' |
| 1567 | 1632 | print '(A)', 'Operation cancelled.' |
| 1568 | 1633 | print '(A)', '' |
@@ -1573,7 +1638,7 @@ contains | ||
| 1573 | 1638 | end if |
| 1574 | 1639 | |
| 1575 | 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 | 1642 | read(99, '(A)', iostat=status) selected_branch |
| 1578 | 1643 | close(99, status='delete') |
| 1579 | 1644 | |
@@ -1656,7 +1721,7 @@ contains | ||
| 1656 | 1721 | 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1657 | 1722 | '--prompt="Switch to branch: " ' // & |
| 1658 | 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 | 1725 | exitstat=status_code) |
| 1661 | 1726 | |
| 1662 | 1727 | ! Re-enable cbreak mode first |
@@ -1671,7 +1736,7 @@ contains | ||
| 1671 | 1736 | end if |
| 1672 | 1737 | |
| 1673 | 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 | 1740 | if (status_code /= 0) then |
| 1676 | 1741 | print '(A)', 'Branch switch cancelled.' |
| 1677 | 1742 | print '(A)', '' |
@@ -1743,7 +1808,7 @@ contains | ||
| 1743 | 1808 | success = .false. |
| 1744 | 1809 | |
| 1745 | 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 | 1812 | exitstat=status_code) |
| 1748 | 1813 | |
| 1749 | 1814 | if (status_code /= 0) then |
@@ -1751,7 +1816,7 @@ contains | ||
| 1751 | 1816 | return |
| 1752 | 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 | 1820 | if (status_code == 0) then |
| 1756 | 1821 | read(99, '(A)', iostat=status_code) current_branch |
| 1757 | 1822 | close(99, status='delete') |
@@ -1764,7 +1829,7 @@ contains | ||
| 1764 | 1829 | |
| 1765 | 1830 | ! Use fzf to select branch to delete (exclude current branch) |
| 1766 | 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 | 1833 | call execute_command_line(trim(command), exitstat=status_code) |
| 1769 | 1834 | |
| 1770 | 1835 | ! Re-enable cbreak mode |
@@ -1779,7 +1844,7 @@ contains | ||
| 1779 | 1844 | end if |
| 1780 | 1845 | |
| 1781 | 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 | 1848 | if (status_code /= 0) then |
| 1784 | 1849 | print '(A)', 'Branch deletion cancelled.' |
| 1785 | 1850 | print '(A)', '' |
@@ -1895,7 +1960,7 @@ contains | ||
| 1895 | 1960 | success = .false. |
| 1896 | 1961 | |
| 1897 | 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 | 1964 | if (status_code /= 0) then |
| 1900 | 1965 | print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m' |
| 1901 | 1966 | print '(A)', 'Press any key to continue...' |
@@ -1903,15 +1968,15 @@ contains | ||
| 1903 | 1968 | end if |
| 1904 | 1969 | |
| 1905 | 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 | 1972 | if (status_code /= 0) then |
| 1908 | 1973 | print '(A)', achar(27) // '[33mNo stashes available' // achar(27) // '[0m' |
| 1909 | 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 | 1976 | return |
| 1912 | 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 | 1981 | ! Restore terminal for fzf |
| 1917 | 1982 | call execute_command_line('stty sane < /dev/tty', exitstat=status_code) |
@@ -1921,7 +1986,7 @@ contains | ||
| 1921 | 1986 | 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 1922 | 1987 | '--prompt="Select stash: " ' // & |
| 1923 | 1988 | '--preview="git stash show -p {1}" --preview-window=right:50% ' // & |
| 1924 | - '> /tmp/fuss_stash_select.txt', & | |
| 1989 | + '> ' // FUSS_TEMP // '', & | |
| 1925 | 1990 | exitstat=status_code) |
| 1926 | 1991 | |
| 1927 | 1992 | ! Re-enable cbreak mode |
@@ -1936,7 +2001,7 @@ contains | ||
| 1936 | 2001 | end if |
| 1937 | 2002 | |
| 1938 | 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 | 2005 | if (status_code /= 0) then |
| 1941 | 2006 | print '(A)', 'Stash operation cancelled.' |
| 1942 | 2007 | print '(A)', '' |
@@ -2065,7 +2130,7 @@ contains | ||
| 2065 | 2130 | 'fzf --height=15 --border=rounded --border-label=" ESC to cancel " ' // & |
| 2066 | 2131 | '--prompt="Reset to: " ' // & |
| 2067 | 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 | 2134 | exitstat=status_code) |
| 2070 | 2135 | |
| 2071 | 2136 | if (status_code /= 0) then |
@@ -2078,7 +2143,7 @@ contains | ||
| 2078 | 2143 | end if |
| 2079 | 2144 | |
| 2080 | 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 | 2147 | if (status /= 0) then |
| 2083 | 2148 | print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m' |
| 2084 | 2149 | call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
@@ -2103,11 +2168,11 @@ contains | ||
| 2103 | 2168 | print '(A)', 'Enter choice (1/2/3) or ESC to cancel: ' |
| 2104 | 2169 | |
| 2105 | 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 | 2172 | exitstat=status) |
| 2108 | 2173 | |
| 2109 | 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 | 2176 | if (status /= 0) then |
| 2112 | 2177 | print '(A)', 'Reset cancelled.' |
| 2113 | 2178 | call execute_command_line('stty cbreak -echo < /dev/tty', exitstat=status) |
@@ -2130,11 +2195,11 @@ contains | ||
| 2130 | 2195 | print '(A)', achar(27) // '[1;31mThis operation CANNOT be undone!' // achar(27) // '[0m' |
| 2131 | 2196 | print '(A)', '' |
| 2132 | 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 | 2199 | exitstat=status) |
| 2135 | 2200 | |
| 2136 | 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 | 2203 | if (status == 0) then |
| 2139 | 2204 | read(99, '(A)', iostat=status) confirmation |
| 2140 | 2205 | close(99) |
@@ -2248,7 +2313,7 @@ contains | ||
| 2248 | 2313 | '--preview="git log --oneline --color=always {1}~1..HEAD | head -20" ' // & |
| 2249 | 2314 | '--preview-window=right:60% ' // & |
| 2250 | 2315 | '--preview-label=" Commits that will be rebased " ' // & |
| 2251 | - '> /tmp/fuss_rebase_base.txt', & | |
| 2316 | + '> ' // FUSS_TEMP // '', & | |
| 2252 | 2317 | exitstat=status_code) |
| 2253 | 2318 | |
| 2254 | 2319 | if (status_code /= 0) then |
@@ -2261,7 +2326,7 @@ contains | ||
| 2261 | 2326 | end if |
| 2262 | 2327 | |
| 2263 | 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 | 2330 | if (status /= 0) then |
| 2266 | 2331 | print '(A)', achar(27) // '[31m✗ Failed to read selection' // achar(27) // '[0m' |
| 2267 | 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 | 1 | module git_module |
| 2 | 2 | use iso_fortran_env, only: error_unit |
| 3 | 3 | use types_module |
| 4 | + use cache_module | |
| 4 | 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 | 11 | contains |
| 7 | 12 | |
| 8 | 13 | ! ========== Performance Optimization: Binary Search Functions ========== |
@@ -198,7 +203,8 @@ contains | ||
| 198 | 203 | end if |
| 199 | 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 | 208 | type(file_entry), allocatable, intent(out) :: files(:) |
| 203 | 209 | integer, intent(out) :: n_files |
| 204 | 210 | integer :: iostat, unit_num, status_code |
@@ -280,9 +286,10 @@ contains | ||
| 280 | 286 | call quicksort_files(files, 1, n_files) |
| 281 | 287 | end if |
| 282 | 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 | 293 | type(file_entry), allocatable, intent(out) :: files(:) |
| 287 | 294 | integer, intent(out) :: n_files |
| 288 | 295 | integer :: iostat, unit_num, status_code, i |
@@ -290,8 +297,8 @@ contains | ||
| 290 | 297 | type(file_entry), allocatable :: dirty_files(:), temp_files(:) |
| 291 | 298 | integer :: n_dirty, max_files |
| 292 | 299 | |
| 293 | - ! First get dirty files | |
| 294 | - call get_dirty_files(dirty_files, n_dirty) | |
| 300 | + ! First get dirty files (call impl directly to avoid double caching) | |
| 301 | + call get_dirty_files_impl(dirty_files, n_dirty) | |
| 295 | 302 | |
| 296 | 303 | ! Get all files using find |
| 297 | 304 | call execute_command_line('find . -type f ! -path "*/\.git/*" > /tmp/fuss_all_files.txt', exitstat=status_code) |
@@ -369,8 +376,66 @@ contains | ||
| 369 | 376 | end if |
| 370 | 377 | deallocate(temp_files) |
| 371 | 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 | 435 | end subroutine get_all_files |
| 373 | 436 | |
| 437 | + ! ========== End Cached Wrappers ========== | |
| 438 | + | |
| 374 | 439 | subroutine resize_array(array, new_size) |
| 375 | 440 | type(file_entry), allocatable, intent(inout) :: array(:) |
| 376 | 441 | integer, intent(in) :: new_size |
src/terminal_module.f90modified@@ -1,6 +1,9 @@ | ||
| 1 | 1 | module terminal_module |
| 2 | 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 | 7 | contains |
| 5 | 8 | |
| 6 | 9 | subroutine enter_alternate_screen() |
@@ -85,11 +88,11 @@ contains | ||
| 85 | 88 | height = 24 ! Default fallback |
| 86 | 89 | |
| 87 | 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 | 92 | exitstat=status) |
| 90 | 93 | |
| 91 | 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 | 96 | if (iostat == 0) then |
| 94 | 97 | read(unit_num, *, iostat=iostat) height |
| 95 | 98 | close(unit_num, status='delete') |
@@ -99,10 +102,10 @@ contains | ||
| 99 | 102 | end if |
| 100 | 103 | |
| 101 | 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 | 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 | 109 | if (iostat == 0) then |
| 107 | 110 | read(unit_num, *, iostat=iostat) height |
| 108 | 111 | close(unit_num, status='delete') |