resolve some rednering things with ctrl-b pane
- SHA
89afb015b0863cac22973bdfd699173585720395- Parents
-
aa6e7df - Tree
e0e0c2c
89afb01
89afb015b0863cac22973bdfd699173585720395aa6e7df
e0e0c2c| Status | File | + | - |
|---|---|---|---|
| M |
src/terminal/renderer_module.f90
|
2 | 2 |
| M |
src/workspace/file_tree_module.f90
|
141 | 11 |
| M |
src/workspace/file_tree_renderer_module.f90
|
72 | 23 |
| A |
test/test_tree.f90
|
90 | 0 |
src/terminal/renderer_module.f90modified@@ -503,8 +503,8 @@ contains | ||
| 503 | 503 | editor_start_col = tree_width + 2 |
| 504 | 504 | editor_width = editor%screen_cols - editor_start_col + 1 |
| 505 | 505 | |
| 506 | - ! Render file tree in left pane | |
| 507 | - call render_file_tree(tree_state, 1, editor%screen_rows - 1, 1, tree_width) | |
| 506 | + ! Render file tree in left pane (start at column 2 to avoid edge cutoff) | |
| 507 | + call render_file_tree(tree_state, 1, editor%screen_rows - 1, 2, tree_width - 2) | |
| 508 | 508 | |
| 509 | 509 | ! Render vertical separator |
| 510 | 510 | call render_vertical_separator(separator_col, editor%screen_rows - 1) |
src/workspace/file_tree_module.f90modified@@ -11,6 +11,7 @@ module file_tree_module | ||
| 11 | 11 | ! Tree node using linked list structure (first-child, next-sibling) |
| 12 | 12 | type :: tree_node_t |
| 13 | 13 | character(len=256) :: name = '' |
| 14 | + character(len=512) :: full_path = '' ! Full path for files (for staging) | |
| 14 | 15 | logical :: is_file = .false. |
| 15 | 16 | logical :: is_staged = .false. |
| 16 | 17 | logical :: is_unstaged = .false. |
@@ -29,10 +30,20 @@ module file_tree_module | ||
| 29 | 30 | logical :: has_incoming = .false. |
| 30 | 31 | end type file_entry_t |
| 31 | 32 | |
| 33 | + ! Selectable item (files only, in tree traversal order) | |
| 34 | + type :: selectable_file_t | |
| 35 | + character(len=512) :: path = '' | |
| 36 | + logical :: is_staged = .false. | |
| 37 | + logical :: is_unstaged = .false. | |
| 38 | + logical :: is_untracked = .false. | |
| 39 | + end type selectable_file_t | |
| 40 | + | |
| 32 | 41 | ! Tree state for navigation |
| 33 | 42 | type :: tree_state_t |
| 34 | 43 | type(file_entry_t), allocatable :: files(:) |
| 44 | + type(selectable_file_t), allocatable :: selectable_files(:) | |
| 35 | 45 | integer :: n_files = 0 |
| 46 | + integer :: n_selectable = 0 | |
| 36 | 47 | integer :: selected_index = 1 |
| 37 | 48 | integer :: viewport_offset = 1 |
| 38 | 49 | type(tree_node_t), pointer :: root => null() |
@@ -62,8 +73,10 @@ contains | ||
| 62 | 73 | type(tree_state_t), intent(inout) :: state |
| 63 | 74 | |
| 64 | 75 | if (allocated(state%files)) deallocate(state%files) |
| 76 | + if (allocated(state%selectable_files)) deallocate(state%selectable_files) | |
| 65 | 77 | if (associated(state%root)) call free_tree(state%root) |
| 66 | 78 | state%n_files = 0 |
| 79 | + state%n_selectable = 0 | |
| 67 | 80 | state%selected_index = 1 |
| 68 | 81 | end subroutine cleanup_tree_state |
| 69 | 82 | |
@@ -76,6 +89,7 @@ contains | ||
| 76 | 89 | call free_tree(state%root) |
| 77 | 90 | state%root => null() |
| 78 | 91 | end if |
| 92 | + if (allocated(state%selectable_files)) deallocate(state%selectable_files) | |
| 79 | 93 | |
| 80 | 94 | ! Get dirty files from git |
| 81 | 95 | call get_dirty_files(workspace_path, state%files, state%n_files) |
@@ -83,12 +97,16 @@ contains | ||
| 83 | 97 | ! Build tree from files |
| 84 | 98 | if (state%n_files > 0) then |
| 85 | 99 | call build_tree(state%files, state%n_files, state%root) |
| 100 | + ! Build selectable files list in tree traversal order | |
| 101 | + call build_selectable_list(state%root, state%selectable_files, state%n_selectable) | |
| 102 | + else | |
| 103 | + state%n_selectable = 0 | |
| 86 | 104 | end if |
| 87 | 105 | |
| 88 | 106 | ! Clamp selected index |
| 89 | - if (state%selected_index > state%n_files .and. state%n_files > 0) then | |
| 90 | - state%selected_index = state%n_files | |
| 91 | - else if (state%n_files == 0) then | |
| 107 | + if (state%selected_index > state%n_selectable .and. state%n_selectable > 0) then | |
| 108 | + state%selected_index = state%n_selectable | |
| 109 | + else if (state%n_selectable == 0) then | |
| 92 | 110 | state%selected_index = 1 |
| 93 | 111 | end if |
| 94 | 112 | end subroutine refresh_tree_state |
@@ -179,6 +197,7 @@ contains | ||
| 179 | 197 | integer, intent(in) :: n_files |
| 180 | 198 | type(tree_node_t), pointer, intent(out) :: root |
| 181 | 199 | integer :: i |
| 200 | + integer :: debug_unit | |
| 182 | 201 | |
| 183 | 202 | ! Create root |
| 184 | 203 | allocate(root) |
@@ -196,8 +215,38 @@ contains | ||
| 196 | 215 | |
| 197 | 216 | ! Sort tree |
| 198 | 217 | call sort_tree(root) |
| 218 | + | |
| 219 | + ! DEBUG: Write tree structure to file (unconditional) | |
| 220 | + open(newunit=debug_unit, file='/tmp/fac_tree_debug.txt', status='replace', action='write') | |
| 221 | + write(debug_unit, '(A)') '=== FINAL TREE STRUCTURE ===' | |
| 222 | + call debug_print_tree(root, '', debug_unit) | |
| 223 | + close(debug_unit) | |
| 224 | + | |
| 225 | + ! Also write to a simpler path | |
| 226 | + open(10, file='fac_debug.txt', status='replace', action='write') | |
| 227 | + write(10, '(A)') '=== FINAL TREE STRUCTURE ===' | |
| 228 | + call debug_print_tree(root, '', 10) | |
| 229 | + close(10) | |
| 199 | 230 | end subroutine build_tree |
| 200 | 231 | |
| 232 | + recursive subroutine debug_print_tree(node, prefix, unit) | |
| 233 | + type(tree_node_t), pointer, intent(in) :: node | |
| 234 | + character(len=*), intent(in) :: prefix | |
| 235 | + integer, intent(in) :: unit | |
| 236 | + type(tree_node_t), pointer :: child | |
| 237 | + | |
| 238 | + if (.not. associated(node)) return | |
| 239 | + | |
| 240 | + write(unit, '(A,A,A,L,A,L)') trim(prefix), trim(node%name), & | |
| 241 | + ' is_file=', node%is_file, ' has_next_sib=', associated(node%next_sibling) | |
| 242 | + | |
| 243 | + child => node%first_child | |
| 244 | + do while (associated(child)) | |
| 245 | + call debug_print_tree(child, prefix // ' ', unit) | |
| 246 | + child => child%next_sibling | |
| 247 | + end do | |
| 248 | + end subroutine debug_print_tree | |
| 249 | + | |
| 201 | 250 | subroutine add_to_tree(root, path, is_staged, is_unstaged, is_untracked, has_incoming) |
| 202 | 251 | type(tree_node_t), pointer, intent(inout) :: root |
| 203 | 252 | character(len=*), intent(in) :: path |
@@ -238,12 +287,13 @@ contains | ||
| 238 | 287 | child => new_node |
| 239 | 288 | end if |
| 240 | 289 | |
| 241 | - ! If this is the final component, set status | |
| 290 | + ! If this is the final component, set status and full path | |
| 242 | 291 | if (len_trim(remaining_path) == 0) then |
| 243 | 292 | child%is_staged = is_staged |
| 244 | 293 | child%is_unstaged = is_unstaged |
| 245 | 294 | child%is_untracked = is_untracked |
| 246 | 295 | child%has_incoming = has_incoming |
| 296 | + child%full_path = trim(path) | |
| 247 | 297 | end if |
| 248 | 298 | |
| 249 | 299 | current => child |
@@ -271,9 +321,26 @@ contains | ||
| 271 | 321 | type(tree_node_t), pointer, intent(inout) :: parent |
| 272 | 322 | type(tree_node_t), pointer :: sorted, current, next_node, insert_pos, prev |
| 273 | 323 | logical :: inserted |
| 324 | + integer :: debug_unit | |
| 325 | + type(tree_node_t), pointer :: check_ptr | |
| 274 | 326 | |
| 275 | 327 | if (.not. associated(parent%first_child)) return |
| 276 | 328 | |
| 329 | + ! DEBUG: Write pre-sort state (both file and stderr) | |
| 330 | + if (trim(parent%name) == 'workspace') then | |
| 331 | + open(newunit=debug_unit, file='/tmp/fac_sort_debug.txt', status='replace', action='write') | |
| 332 | + write(debug_unit, '(A)') '=== Sorting workspace children ===' | |
| 333 | + write(debug_unit, '(A)') 'Before sort:' | |
| 334 | + write(0, '(A)') '[DEBUG] Sorting workspace children' | |
| 335 | + write(0, '(A)') '[DEBUG] Before sort:' | |
| 336 | + check_ptr => parent%first_child | |
| 337 | + do while (associated(check_ptr)) | |
| 338 | + write(debug_unit, '(A,A,A,L)') ' ', trim(check_ptr%name), ' next_sib=', associated(check_ptr%next_sibling) | |
| 339 | + write(0, '(A,A,A,L)') '[DEBUG] ', trim(check_ptr%name), ' next_sib=', associated(check_ptr%next_sibling) | |
| 340 | + check_ptr => check_ptr%next_sibling | |
| 341 | + end do | |
| 342 | + end if | |
| 343 | + | |
| 277 | 344 | sorted => null() |
| 278 | 345 | |
| 279 | 346 | current => parent%first_child |
@@ -309,6 +376,20 @@ contains | ||
| 309 | 376 | end do |
| 310 | 377 | |
| 311 | 378 | parent%first_child => sorted |
| 379 | + | |
| 380 | + ! DEBUG: Write post-sort state (both file and stderr) | |
| 381 | + if (trim(parent%name) == 'workspace') then | |
| 382 | + write(debug_unit, '(A)') 'After sort:' | |
| 383 | + write(0, '(A)') '[DEBUG] After sort:' | |
| 384 | + check_ptr => parent%first_child | |
| 385 | + do while (associated(check_ptr)) | |
| 386 | + write(debug_unit, '(A,A,A,L)') ' ', trim(check_ptr%name), ' next_sib=', associated(check_ptr%next_sibling) | |
| 387 | + write(0, '(A,A,A,L)') '[DEBUG] ', trim(check_ptr%name), ' next_sib=', associated(check_ptr%next_sibling) | |
| 388 | + check_ptr => check_ptr%next_sibling | |
| 389 | + end do | |
| 390 | + write(debug_unit, '(A)') '' | |
| 391 | + close(debug_unit) | |
| 392 | + end if | |
| 312 | 393 | end subroutine sort_children |
| 313 | 394 | |
| 314 | 395 | function compare_nodes(a, b) result(cmp) |
@@ -332,6 +413,55 @@ contains | ||
| 332 | 413 | end if |
| 333 | 414 | end function compare_nodes |
| 334 | 415 | |
| 416 | + ! Build list of selectable files in tree traversal order | |
| 417 | + subroutine build_selectable_list(root, selectable, n_selectable) | |
| 418 | + type(tree_node_t), pointer, intent(in) :: root | |
| 419 | + type(selectable_file_t), allocatable, intent(out) :: selectable(:) | |
| 420 | + integer, intent(out) :: n_selectable | |
| 421 | + type(selectable_file_t), allocatable :: temp(:) | |
| 422 | + integer :: max_size, count | |
| 423 | + | |
| 424 | + max_size = 1000 | |
| 425 | + allocate(temp(max_size)) | |
| 426 | + count = 0 | |
| 427 | + | |
| 428 | + ! Traverse tree and collect files | |
| 429 | + call collect_files_recursive(root, temp, count, max_size) | |
| 430 | + | |
| 431 | + n_selectable = count | |
| 432 | + allocate(selectable(n_selectable)) | |
| 433 | + if (n_selectable > 0) selectable(1:n_selectable) = temp(1:n_selectable) | |
| 434 | + deallocate(temp) | |
| 435 | + end subroutine build_selectable_list | |
| 436 | + | |
| 437 | + recursive subroutine collect_files_recursive(node, list, count, max_size) | |
| 438 | + type(tree_node_t), pointer, intent(in) :: node | |
| 439 | + type(selectable_file_t), intent(inout) :: list(:) | |
| 440 | + integer, intent(inout) :: count | |
| 441 | + integer, intent(in) :: max_size | |
| 442 | + type(tree_node_t), pointer :: child | |
| 443 | + | |
| 444 | + if (.not. associated(node)) return | |
| 445 | + | |
| 446 | + ! If this is a file, add it to the list | |
| 447 | + if (node%is_file .and. len_trim(node%full_path) > 0) then | |
| 448 | + count = count + 1 | |
| 449 | + if (count <= max_size) then | |
| 450 | + list(count)%path = node%full_path | |
| 451 | + list(count)%is_staged = node%is_staged | |
| 452 | + list(count)%is_unstaged = node%is_unstaged | |
| 453 | + list(count)%is_untracked = node%is_untracked | |
| 454 | + end if | |
| 455 | + end if | |
| 456 | + | |
| 457 | + ! Recursively process children (in order) | |
| 458 | + child => node%first_child | |
| 459 | + do while (associated(child)) | |
| 460 | + call collect_files_recursive(child, list, count, max_size) | |
| 461 | + child => child%next_sibling | |
| 462 | + end do | |
| 463 | + end subroutine collect_files_recursive | |
| 464 | + | |
| 335 | 465 | recursive subroutine free_tree(node) |
| 336 | 466 | type(tree_node_t), pointer, intent(inout) :: node |
| 337 | 467 | type(tree_node_t), pointer :: child, next_child |
@@ -400,7 +530,7 @@ contains | ||
| 400 | 530 | |
| 401 | 531 | subroutine tree_move_down(state) |
| 402 | 532 | type(tree_state_t), intent(inout) :: state |
| 403 | - if (state%selected_index < state%n_files) then | |
| 533 | + if (state%selected_index < state%n_selectable) then | |
| 404 | 534 | state%selected_index = state%selected_index + 1 |
| 405 | 535 | end if |
| 406 | 536 | end subroutine tree_move_down |
@@ -409,8 +539,8 @@ contains | ||
| 409 | 539 | type(tree_state_t), intent(in) :: state |
| 410 | 540 | character(len=:), allocatable :: path |
| 411 | 541 | |
| 412 | - if (state%selected_index >= 1 .and. state%selected_index <= state%n_files) then | |
| 413 | - path = trim(state%files(state%selected_index)%path) | |
| 542 | + if (state%selected_index >= 1 .and. state%selected_index <= state%n_selectable) then | |
| 543 | + path = trim(state%selectable_files(state%selected_index)%path) | |
| 414 | 544 | else |
| 415 | 545 | path = '' |
| 416 | 546 | end if |
@@ -423,11 +553,11 @@ contains | ||
| 423 | 553 | character(len=1024) :: cmd |
| 424 | 554 | integer :: status |
| 425 | 555 | |
| 426 | - if (state%selected_index < 1 .or. state%selected_index > state%n_files) return | |
| 556 | + if (state%selected_index < 1 .or. state%selected_index > state%n_selectable) return | |
| 427 | 557 | |
| 428 | 558 | ! Stage the file |
| 429 | 559 | write(cmd, '(A,A,A,A,A)') 'cd "', trim(workspace_path), '" && git add "', & |
| 430 | - trim(state%files(state%selected_index)%path), '" 2>/dev/null' | |
| 560 | + trim(state%selectable_files(state%selected_index)%path), '" 2>/dev/null' | |
| 431 | 561 | call execute_command_line(trim(cmd), exitstat=status) |
| 432 | 562 | |
| 433 | 563 | ! Refresh tree |
@@ -440,11 +570,11 @@ contains | ||
| 440 | 570 | character(len=1024) :: cmd |
| 441 | 571 | integer :: status |
| 442 | 572 | |
| 443 | - if (state%selected_index < 1 .or. state%selected_index > state%n_files) return | |
| 573 | + if (state%selected_index < 1 .or. state%selected_index > state%n_selectable) return | |
| 444 | 574 | |
| 445 | 575 | ! Unstage the file |
| 446 | 576 | write(cmd, '(A,A,A,A,A)') 'cd "', trim(workspace_path), '" && git restore --staged "', & |
| 447 | - trim(state%files(state%selected_index)%path), '" 2>/dev/null' | |
| 577 | + trim(state%selectable_files(state%selected_index)%path), '" 2>/dev/null' | |
| 448 | 578 | call execute_command_line(trim(cmd), exitstat=status) |
| 449 | 579 | |
| 450 | 580 | ! Refresh tree |
src/workspace/file_tree_renderer_module.f90modified@@ -31,6 +31,10 @@ contains | ||
| 31 | 31 | |
| 32 | 32 | current_row = start_row |
| 33 | 33 | |
| 34 | + ! DEBUG: Visual marker at column 1 | |
| 35 | + call terminal_move_cursor(1, 1) | |
| 36 | + call terminal_write('[COL1]') | |
| 37 | + | |
| 34 | 38 | ! Display repo:branch info at top if available |
| 35 | 39 | if (len_trim(state%repo_name) > 0 .and. len_trim(state%branch_name) > 0) then |
| 36 | 40 | call terminal_move_cursor(current_row, start_col) |
@@ -74,18 +78,10 @@ contains | ||
| 74 | 78 | integer, intent(inout) :: item_idx, current_row |
| 75 | 79 | integer, intent(in) :: end_row, start_col, width |
| 76 | 80 | |
| 77 | - character(len=1024) :: line, new_prefix | |
| 81 | + character(len=:), allocatable :: line, new_prefix, branch | |
| 78 | 82 | type(tree_node_t), pointer :: child |
| 79 | - integer :: n_children, i | |
| 80 | - logical :: is_selected | |
| 81 | - | |
| 82 | - ! Count children | |
| 83 | - n_children = 0 | |
| 84 | - child => node%first_child | |
| 85 | - do while (associated(child)) | |
| 86 | - n_children = n_children + 1 | |
| 87 | - child => child%next_sibling | |
| 88 | - end do | |
| 83 | + logical :: is_selected, is_last_child | |
| 84 | + integer :: prefix_len, i | |
| 89 | 85 | |
| 90 | 86 | ! Don't print root node |
| 91 | 87 | if (.not. is_root) then |
@@ -100,21 +96,47 @@ contains | ||
| 100 | 96 | if (current_row <= end_row) then |
| 101 | 97 | ! Build line with tree structure |
| 102 | 98 | if (is_last) then |
| 103 | - line = trim(prefix) // BRANCH_LAST // ' ' // trim(node%name) | |
| 99 | + branch = BRANCH_LAST | |
| 104 | 100 | else |
| 105 | - line = trim(prefix) // BRANCH_MID // ' ' // trim(node%name) | |
| 101 | + branch = BRANCH_MID | |
| 102 | + end if | |
| 103 | + | |
| 104 | + ! DEBUG: Check branch assignment for root's children | |
| 105 | + if (len_trim(prefix) == 0 .and. trim(node%name) == 'src') then | |
| 106 | + open(98, file='/tmp/fac_branch_debug.txt', status='replace') | |
| 107 | + write(98, '(A,L,A,I0)') 'is_last=', is_last, ' len(branch)=', len(branch) | |
| 108 | + write(98, '(A,Z2.2,Z2.2,Z2.2)') 'branch bytes: ', & | |
| 109 | + ichar(branch(1:1)), ichar(branch(2:2)), ichar(branch(3:3)) | |
| 110 | + write(98, '(A)') 'BRANCH_MID='//BRANCH_MID | |
| 111 | + write(98, '(A)') 'BRANCH_LAST='//BRANCH_LAST | |
| 112 | + close(98) | |
| 113 | + end if | |
| 114 | + | |
| 115 | + ! Construct the line - prefix can be empty string, that's fine | |
| 116 | + line = prefix // branch // ' ' // trim(node%name) | |
| 117 | + | |
| 118 | + ! DEBUG: Check what's actually in line for src | |
| 119 | + if (trim(node%name) == 'src') then | |
| 120 | + open(96, file='/tmp/fac_line_debug.txt', status='replace') | |
| 121 | + write(96, '(A,I0)') 'len(line)=', len(line) | |
| 122 | + write(96, '(A,I0)') 'len(prefix)=', len(prefix) | |
| 123 | + write(96, '(A,I0)') 'len(branch)=', len(branch) | |
| 124 | + write(96, '(A)') 'line bytes (first 20):' | |
| 125 | + write(96, '(20(Z2.2,1X))') (ichar(line(i:i)), i=1,min(20,len(line))) | |
| 126 | + write(96, '(A)') 'Full line: >'//line//'<' | |
| 127 | + close(96) | |
| 106 | 128 | end if |
| 107 | 129 | |
| 108 | 130 | ! Add status indicators for files only |
| 109 | 131 | if (node%is_file) then |
| 110 | 132 | if (node%is_staged) then |
| 111 | - line = trim(line) // ' ' // ESC // '[32m↑' // ESC // '[0m' ! Green up arrow | |
| 133 | + line = line // ' ' // ESC // '[32m↑' // ESC // '[0m' ! Green up arrow | |
| 112 | 134 | end if |
| 113 | 135 | if (node%is_unstaged) then |
| 114 | - line = trim(line) // ' ' // ESC // '[31m✗' // ESC // '[0m' ! Red X | |
| 136 | + line = line // ' ' // ESC // '[31m✗' // ESC // '[0m' ! Red X | |
| 115 | 137 | end if |
| 116 | 138 | if (node%is_untracked) then |
| 117 | - line = trim(line) // ' ' // ESC // '[90m✗' // ESC // '[0m' ! Gray X | |
| 139 | + line = line // ' ' // ESC // '[90m✗' // ESC // '[0m' ! Gray X | |
| 118 | 140 | end if |
| 119 | 141 | end if |
| 120 | 142 | |
@@ -122,9 +144,9 @@ contains | ||
| 122 | 144 | call terminal_move_cursor(current_row, start_col) |
| 123 | 145 | if (is_selected) then |
| 124 | 146 | ! Selection highlight (reverse video) |
| 125 | - call terminal_write(ESC // '[7m' // trim(line) // ESC // '[0m') | |
| 147 | + call terminal_write(ESC // '[7m' // line // ESC // '[0m') | |
| 126 | 148 | else |
| 127 | - call terminal_write(trim(line)) | |
| 149 | + call terminal_write(line) | |
| 128 | 150 | end if |
| 129 | 151 | |
| 130 | 152 | current_row = current_row + 1 |
@@ -132,22 +154,49 @@ contains | ||
| 132 | 154 | end if |
| 133 | 155 | |
| 134 | 156 | ! Render children |
| 135 | - i = 0 | |
| 136 | 157 | child => node%first_child |
| 137 | 158 | do while (associated(child) .and. current_row <= end_row) |
| 138 | - i = i + 1 | |
| 159 | + ! Determine if this is the last sibling | |
| 160 | + is_last_child = .not. associated(child%next_sibling) | |
| 161 | + | |
| 162 | + ! DEBUG: Write sibling info for workspace children | |
| 163 | + if (trim(node%name) == 'workspace') then | |
| 164 | + open(99, file='/tmp/fac_render_debug.txt', position='append') | |
| 165 | + write(99, '(A,A,A,L)') 'Child: ', trim(child%name), ' has_next_sib=', associated(child%next_sibling) | |
| 166 | + close(99) | |
| 167 | + end if | |
| 168 | + | |
| 139 | 169 | |
| 170 | + ! Build prefix for child based on current node's position | |
| 140 | 171 | if (is_root) then |
| 172 | + ! Root's children start with no prefix | |
| 141 | 173 | new_prefix = '' |
| 142 | 174 | else |
| 175 | + ! Non-root children inherit prefix and add continuation | |
| 176 | + ! IMPORTANT: Don't trim prefix! It contains accumulated indentation | |
| 143 | 177 | if (is_last) then |
| 144 | - new_prefix = trim(prefix) // ' ' | |
| 178 | + ! Current node is last, so children get spaces (no vertical line continues) | |
| 179 | + new_prefix = prefix // ' ' | |
| 145 | 180 | else |
| 146 | - new_prefix = trim(prefix) // VERTICAL // ' ' | |
| 181 | + ! Current node is not last, so vertical line continues for children | |
| 182 | + new_prefix = prefix // VERTICAL // ' ' | |
| 183 | + end if | |
| 184 | + end if | |
| 185 | + | |
| 186 | + ! DEBUG: Log prefix building for src | |
| 187 | + if (trim(node%name) == 'src' .or. trim(child%name) == 'workspace') then | |
| 188 | + open(97, file='/tmp/fac_prefix_debug.txt', position='append') | |
| 189 | + write(97, '(A,A,A,L,A,A,A,A)') & | |
| 190 | + 'Node: ', trim(node%name), ' is_root=', is_root, ' is_last=', merge('T', 'F', is_last), & | |
| 191 | + ' Child: ', trim(child%name) | |
| 192 | + write(97, '(A,I0,A)') 'new_prefix len=', len(new_prefix), ' content=>'//new_prefix//'<' | |
| 193 | + if (.not. is_root .and. .not. is_last) then | |
| 194 | + write(97, '(A)') 'Should have added VERTICAL: '//VERTICAL | |
| 147 | 195 | end if |
| 196 | + close(97) | |
| 148 | 197 | end if |
| 149 | 198 | |
| 150 | - call render_tree_node(child, new_prefix, i == n_children, .false., & | |
| 199 | + call render_tree_node(child, new_prefix, is_last_child, .false., & | |
| 151 | 200 | state, item_idx, current_row, end_row, start_col, width) |
| 152 | 201 | child => child%next_sibling |
| 153 | 202 | end do |
test/test_tree.f90added@@ -0,0 +1,90 @@ | ||
| 1 | +program test_tree | |
| 2 | + use file_tree_module | |
| 3 | + implicit none | |
| 4 | + | |
| 5 | + type(file_entry_t), allocatable :: files(:) | |
| 6 | + type(tree_node_t), pointer :: root, child | |
| 7 | + integer :: n_files | |
| 8 | + | |
| 9 | + ! Create test data matching current git status | |
| 10 | + n_files = 3 | |
| 11 | + allocate(files(n_files)) | |
| 12 | + | |
| 13 | + files(1)%path = 'README.md' | |
| 14 | + files(1)%is_staged = .false. | |
| 15 | + files(1)%is_unstaged = .true. | |
| 16 | + files(1)%is_untracked = .false. | |
| 17 | + files(1)%has_incoming = .false. | |
| 18 | + | |
| 19 | + files(2)%path = 'src/workspace/file_tree_module.f90' | |
| 20 | + files(2)%is_staged = .false. | |
| 21 | + files(2)%is_unstaged = .true. | |
| 22 | + files(2)%is_untracked = .false. | |
| 23 | + files(2)%has_incoming = .false. | |
| 24 | + | |
| 25 | + files(3)%path = 'src/workspace/file_tree_renderer_module.f90' | |
| 26 | + files(3)%is_staged = .false. | |
| 27 | + files(3)%is_unstaged = .true. | |
| 28 | + files(3)%is_untracked = .false. | |
| 29 | + files(3)%has_incoming = .false. | |
| 30 | + | |
| 31 | + ! Build tree | |
| 32 | + call build_tree(files, n_files, root) | |
| 33 | + | |
| 34 | + print *, 'Tree structure:' | |
| 35 | + call print_tree(root, '') | |
| 36 | + | |
| 37 | + ! Check workspace children specifically | |
| 38 | + print *, '' | |
| 39 | + print *, 'Checking src/workspace children:' | |
| 40 | + call check_workspace_children(root) | |
| 41 | + | |
| 42 | +contains | |
| 43 | + | |
| 44 | + recursive subroutine print_tree(node, prefix) | |
| 45 | + type(tree_node_t), pointer, intent(in) :: node | |
| 46 | + character(len=*), intent(in) :: prefix | |
| 47 | + type(tree_node_t), pointer :: child | |
| 48 | + | |
| 49 | + if (.not. associated(node)) return | |
| 50 | + | |
| 51 | + print '(A,A,A,L,A,L)', trim(prefix), trim(node%name), & | |
| 52 | + ' is_file=', node%is_file, ' has_next_sib=', associated(node%next_sibling) | |
| 53 | + | |
| 54 | + child => node%first_child | |
| 55 | + do while (associated(child)) | |
| 56 | + call print_tree(child, prefix // ' ') | |
| 57 | + child => child%next_sibling | |
| 58 | + end do | |
| 59 | + end subroutine print_tree | |
| 60 | + | |
| 61 | + recursive subroutine check_workspace_children(node) | |
| 62 | + type(tree_node_t), pointer, intent(in) :: node | |
| 63 | + type(tree_node_t), pointer :: child | |
| 64 | + integer :: count | |
| 65 | + | |
| 66 | + if (.not. associated(node)) return | |
| 67 | + | |
| 68 | + ! Found workspace directory | |
| 69 | + if (trim(node%name) == 'workspace') then | |
| 70 | + print *, 'Found workspace directory!' | |
| 71 | + child => node%first_child | |
| 72 | + count = 0 | |
| 73 | + do while (associated(child)) | |
| 74 | + count = count + 1 | |
| 75 | + print '(A,I0,A,A,A,L)', ' Child ', count, ': ', trim(child%name), & | |
| 76 | + ' has_next_sib=', associated(child%next_sibling) | |
| 77 | + child => child%next_sibling | |
| 78 | + end do | |
| 79 | + return | |
| 80 | + end if | |
| 81 | + | |
| 82 | + ! Recurse to children | |
| 83 | + child => node%first_child | |
| 84 | + do while (associated(child)) | |
| 85 | + call check_workspace_children(child) | |
| 86 | + child => child%next_sibling | |
| 87 | + end do | |
| 88 | + end subroutine check_workspace_children | |
| 89 | + | |
| 90 | +end program test_tree | |