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 | 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') |