fortrangoingonforty/facsimile / 89afb01

Browse files

resolve some rednering things with ctrl-b pane

Authored by espadonne
SHA
89afb015b0863cac22973bdfd699173585720395
Parents
aa6e7df
Tree
e0e0c2c

4 changed files

StatusFile+-
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
503503
         editor_start_col = tree_width + 2
504504
         editor_width = editor%screen_cols - editor_start_col + 1
505505
 
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)
508508
 
509509
         ! Render vertical separator
510510
         call render_vertical_separator(separator_col, editor%screen_rows - 1)
src/workspace/file_tree_module.f90modified
@@ -11,6 +11,7 @@ module file_tree_module
1111
     ! Tree node using linked list structure (first-child, next-sibling)
1212
     type :: tree_node_t
1313
         character(len=256) :: name = ''
14
+        character(len=512) :: full_path = ''  ! Full path for files (for staging)
1415
         logical :: is_file = .false.
1516
         logical :: is_staged = .false.
1617
         logical :: is_unstaged = .false.
@@ -29,10 +30,20 @@ module file_tree_module
2930
         logical :: has_incoming = .false.
3031
     end type file_entry_t
3132
 
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
+
3241
     ! Tree state for navigation
3342
     type :: tree_state_t
3443
         type(file_entry_t), allocatable :: files(:)
44
+        type(selectable_file_t), allocatable :: selectable_files(:)
3545
         integer :: n_files = 0
46
+        integer :: n_selectable = 0
3647
         integer :: selected_index = 1
3748
         integer :: viewport_offset = 1
3849
         type(tree_node_t), pointer :: root => null()
@@ -62,8 +73,10 @@ contains
6273
         type(tree_state_t), intent(inout) :: state
6374
 
6475
         if (allocated(state%files)) deallocate(state%files)
76
+        if (allocated(state%selectable_files)) deallocate(state%selectable_files)
6577
         if (associated(state%root)) call free_tree(state%root)
6678
         state%n_files = 0
79
+        state%n_selectable = 0
6780
         state%selected_index = 1
6881
     end subroutine cleanup_tree_state
6982
 
@@ -76,6 +89,7 @@ contains
7689
             call free_tree(state%root)
7790
             state%root => null()
7891
         end if
92
+        if (allocated(state%selectable_files)) deallocate(state%selectable_files)
7993
 
8094
         ! Get dirty files from git
8195
         call get_dirty_files(workspace_path, state%files, state%n_files)
@@ -83,12 +97,16 @@ contains
8397
         ! Build tree from files
8498
         if (state%n_files > 0) then
8599
             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
86104
         end if
87105
 
88106
         ! 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
92110
             state%selected_index = 1
93111
         end if
94112
     end subroutine refresh_tree_state
@@ -179,6 +197,7 @@ contains
179197
         integer, intent(in) :: n_files
180198
         type(tree_node_t), pointer, intent(out) :: root
181199
         integer :: i
200
+        integer :: debug_unit
182201
 
183202
         ! Create root
184203
         allocate(root)
@@ -196,8 +215,38 @@ contains
196215
 
197216
         ! Sort tree
198217
         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)
199230
     end subroutine build_tree
200231
 
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
+
201250
     subroutine add_to_tree(root, path, is_staged, is_unstaged, is_untracked, has_incoming)
202251
         type(tree_node_t), pointer, intent(inout) :: root
203252
         character(len=*), intent(in) :: path
@@ -238,12 +287,13 @@ contains
238287
                 child => new_node
239288
             end if
240289
 
241
-            ! If this is the final component, set status
290
+            ! If this is the final component, set status and full path
242291
             if (len_trim(remaining_path) == 0) then
243292
                 child%is_staged = is_staged
244293
                 child%is_unstaged = is_unstaged
245294
                 child%is_untracked = is_untracked
246295
                 child%has_incoming = has_incoming
296
+                child%full_path = trim(path)
247297
             end if
248298
 
249299
             current => child
@@ -271,9 +321,26 @@ contains
271321
         type(tree_node_t), pointer, intent(inout) :: parent
272322
         type(tree_node_t), pointer :: sorted, current, next_node, insert_pos, prev
273323
         logical :: inserted
324
+        integer :: debug_unit
325
+        type(tree_node_t), pointer :: check_ptr
274326
 
275327
         if (.not. associated(parent%first_child)) return
276328
 
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
+
277344
         sorted => null()
278345
 
279346
         current => parent%first_child
@@ -309,6 +376,20 @@ contains
309376
         end do
310377
 
311378
         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
312393
     end subroutine sort_children
313394
 
314395
     function compare_nodes(a, b) result(cmp)
@@ -332,6 +413,55 @@ contains
332413
         end if
333414
     end function compare_nodes
334415
 
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
+
335465
     recursive subroutine free_tree(node)
336466
         type(tree_node_t), pointer, intent(inout) :: node
337467
         type(tree_node_t), pointer :: child, next_child
@@ -400,7 +530,7 @@ contains
400530
 
401531
     subroutine tree_move_down(state)
402532
         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
404534
             state%selected_index = state%selected_index + 1
405535
         end if
406536
     end subroutine tree_move_down
@@ -409,8 +539,8 @@ contains
409539
         type(tree_state_t), intent(in) :: state
410540
         character(len=:), allocatable :: path
411541
 
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)
414544
         else
415545
             path = ''
416546
         end if
@@ -423,11 +553,11 @@ contains
423553
         character(len=1024) :: cmd
424554
         integer :: status
425555
 
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
427557
 
428558
         ! Stage the file
429559
         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'
431561
         call execute_command_line(trim(cmd), exitstat=status)
432562
 
433563
         ! Refresh tree
@@ -440,11 +570,11 @@ contains
440570
         character(len=1024) :: cmd
441571
         integer :: status
442572
 
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
444574
 
445575
         ! Unstage the file
446576
         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'
448578
         call execute_command_line(trim(cmd), exitstat=status)
449579
 
450580
         ! Refresh tree
src/workspace/file_tree_renderer_module.f90modified
@@ -31,6 +31,10 @@ contains
3131
 
3232
         current_row = start_row
3333
 
34
+        ! DEBUG: Visual marker at column 1
35
+        call terminal_move_cursor(1, 1)
36
+        call terminal_write('[COL1]')
37
+
3438
         ! Display repo:branch info at top if available
3539
         if (len_trim(state%repo_name) > 0 .and. len_trim(state%branch_name) > 0) then
3640
             call terminal_move_cursor(current_row, start_col)
@@ -74,18 +78,10 @@ contains
7478
         integer, intent(inout) :: item_idx, current_row
7579
         integer, intent(in) :: end_row, start_col, width
7680
 
77
-        character(len=1024) :: line, new_prefix
81
+        character(len=:), allocatable :: line, new_prefix, branch
7882
         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
8985
 
9086
         ! Don't print root node
9187
         if (.not. is_root) then
@@ -100,21 +96,47 @@ contains
10096
             if (current_row <= end_row) then
10197
                 ! Build line with tree structure
10298
                 if (is_last) then
103
-                    line = trim(prefix) // BRANCH_LAST // ' ' // trim(node%name)
99
+                    branch = BRANCH_LAST
104100
                 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)
106128
                 end if
107129
 
108130
                 ! Add status indicators for files only
109131
                 if (node%is_file) then
110132
                     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
112134
                     end if
113135
                     if (node%is_unstaged) then
114
-                        line = trim(line) // ' ' // ESC // '[31m✗' // ESC // '[0m'  ! Red X
136
+                        line = line // ' ' // ESC // '[31m✗' // ESC // '[0m'  ! Red X
115137
                     end if
116138
                     if (node%is_untracked) then
117
-                        line = trim(line) // ' ' // ESC // '[90m✗' // ESC // '[0m'  ! Gray X
139
+                        line = line // ' ' // ESC // '[90m✗' // ESC // '[0m'  ! Gray X
118140
                     end if
119141
                 end if
120142
 
@@ -122,9 +144,9 @@ contains
122144
                 call terminal_move_cursor(current_row, start_col)
123145
                 if (is_selected) then
124146
                     ! Selection highlight (reverse video)
125
-                    call terminal_write(ESC // '[7m' // trim(line) // ESC // '[0m')
147
+                    call terminal_write(ESC // '[7m' // line // ESC // '[0m')
126148
                 else
127
-                    call terminal_write(trim(line))
149
+                    call terminal_write(line)
128150
                 end if
129151
 
130152
                 current_row = current_row + 1
@@ -132,22 +154,49 @@ contains
132154
         end if
133155
 
134156
         ! Render children
135
-        i = 0
136157
         child => node%first_child
137158
         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
+
139169
 
170
+            ! Build prefix for child based on current node's position
140171
             if (is_root) then
172
+                ! Root's children start with no prefix
141173
                 new_prefix = ''
142174
             else
175
+                ! Non-root children inherit prefix and add continuation
176
+                ! IMPORTANT: Don't trim prefix! It contains accumulated indentation
143177
                 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 // '    '
145180
                 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
147195
                 end if
196
+                close(97)
148197
             end if
149198
 
150
-            call render_tree_node(child, new_prefix, i == n_children, .false., &
199
+            call render_tree_node(child, new_prefix, is_last_child, .false., &
151200
                                 state, item_idx, current_row, end_row, start_col, width)
152201
             child => child%next_sibling
153202
         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