Fortran · 10115 bytes Raw Blame History
1 module file_tree_renderer_module
2 use iso_fortran_env, only: int32
3 use file_tree_module
4 use terminal_io_module
5 implicit none
6 private
7
8 public :: render_file_tree
9
10 ! Simple expansion indicators (no box-drawing)
11 character(len=*), parameter :: EXPANDED_DIR = '-'
12 character(len=*), parameter :: COLLAPSED_DIR = '+'
13 character(len=1), parameter :: ESC = achar(27)
14
15 contains
16
17 subroutine render_file_tree(state, start_row, end_row, start_col, width, hints_expanded, git_prefix_active)
18 type(tree_state_t), intent(in) :: state
19 integer, intent(in) :: start_row, end_row, start_col, width
20 logical, intent(in) :: hints_expanded
21 logical, intent(in), optional :: git_prefix_active
22 logical :: git_mode
23 integer :: current_row, item_idx, row
24 character(len=512) :: status_line
25 character(len=:), allocatable :: padding
26
27 ! Handle optional git_prefix_active parameter
28 if (present(git_prefix_active)) then
29 git_mode = git_prefix_active
30 else
31 git_mode = .false.
32 end if
33
34 ! First, clear all rows in the tree pane
35 padding = repeat(' ', width)
36 do row = start_row, end_row
37 call terminal_move_cursor(row, start_col)
38 call terminal_write(padding)
39 end do
40
41 current_row = start_row
42
43 ! Display repo:branch info at top if available
44 if (len_trim(state%repo_name) > 0 .and. len_trim(state%branch_name) > 0) then
45 call terminal_move_cursor(current_row, start_col)
46 write(status_line, '(A,A,A,A,A,A,A)') &
47 ESC // '[1;36m', trim(state%repo_name), ESC // '[0m', &
48 ':', &
49 ESC // '[1;33m', trim(state%branch_name), ESC // '[0m'
50 call terminal_write(trim(status_line))
51 current_row = current_row + 2 ! Skip a line
52 end if
53
54 ! Display root
55 if (current_row <= end_row) then
56 call terminal_move_cursor(current_row, start_col)
57 call terminal_write('.')
58 current_row = current_row + 1
59 end if
60
61 ! Display tree (leave space for legend - 1 or 4 rows)
62 if (associated(state%root)) then
63 item_idx = 0
64 if (hints_expanded) then
65 call render_tree_node(state%root, '', .true., &
66 state, item_idx, current_row, end_row - 4, start_col, width)
67 else
68 call render_tree_node(state%root, '', .true., &
69 state, item_idx, current_row, end_row - 1, start_col, width)
70 end if
71 end if
72
73 ! Display legend at bottom (either minimal or expanded)
74 if (hints_expanded) then
75 ! Expanded legend (four rows)
76 if (end_row >= start_row + 4) then
77 ! First row: navigation
78 call terminal_move_cursor(end_row - 3, start_col)
79 call terminal_write(ESC // '[90m') ! Gray
80 call terminal_write('j/k:nav →:in ←:up o:open .:hide spc:toggle')
81 call terminal_write(ESC // '[0m')
82
83 ! Second and third rows: git operations (only shown when git mode active)
84 if (git_mode) then
85 ! Git mode active - show git bindings in yellow
86 call terminal_move_cursor(end_row - 2, start_col)
87 call terminal_write(ESC // '[1;33m') ! Bright yellow
88 call terminal_write('a:stage u:unstage d:diff m:commit')
89 call terminal_write(ESC // '[0m')
90
91 call terminal_move_cursor(end_row - 1, start_col)
92 call terminal_write(ESC // '[1;33m') ! Bright yellow
93 call terminal_write('p:push f:fetch l:pull t:tag esc:cancel')
94 call terminal_write(ESC // '[0m')
95 else
96 ! Normal mode - show shortcuts and fuzzy search hint
97 call terminal_move_cursor(end_row - 2, start_col)
98 call terminal_write(ESC // '[90m') ! Gray
99 call terminal_write('alt-v:vsplit alt-s:hsplit ctrl-g:git')
100 call terminal_write(ESC // '[0m')
101
102 call terminal_move_cursor(end_row - 1, start_col)
103 call terminal_write(ESC // '[90m') ! Gray
104 call terminal_write('type to fuzzy search files')
105 call terminal_write(ESC // '[0m')
106 end if
107
108 ! Fourth row: exit
109 call terminal_move_cursor(end_row, start_col)
110 call terminal_write(ESC // '[90m') ! Gray
111 call terminal_write('ctrl-/:collapse esc/F3:close')
112 call terminal_write(ESC // '[0m')
113 end if
114 else
115 ! Minimal legend (one row)
116 if (end_row >= start_row + 1) then
117 call terminal_move_cursor(end_row, start_col)
118 call terminal_write(ESC // '[90m') ! Gray
119 call terminal_write('.:hide ctrl-/:hints esc/F3:close')
120 call terminal_write(ESC // '[0m')
121 end if
122 end if
123 end subroutine render_file_tree
124
125 recursive subroutine render_tree_node(node, prefix, is_root, &
126 state, item_idx, current_row, end_row, start_col, width)
127 type(tree_node_t), pointer, intent(in) :: node
128 character(len=*), intent(in) :: prefix
129 logical, intent(in) :: is_root
130 type(tree_state_t), intent(in) :: state
131 integer, intent(inout) :: item_idx, current_row
132 integer, intent(in) :: end_row, start_col, width
133
134 character(len=:), allocatable :: line, new_prefix
135 type(tree_node_t), pointer :: child
136 logical :: is_selected, is_last_child
137
138 ! Don't print root node
139 if (.not. is_root) then
140 ! Skip hidden files when hide_dotfiles is enabled (dotfiles or gitignored)
141 if (state%hide_dotfiles .and. node%is_file .and. (node%is_dotfile .or. node%is_gitignored)) then
142 return
143 end if
144
145 ! Increment item_idx for both files and directories (all selectable items)
146 item_idx = item_idx + 1
147 is_selected = (item_idx == state%selected_index)
148
149 ! Only render if within viewport and current_row fits
150 if (item_idx >= state%viewport_offset .and. current_row <= end_row) then
151 ! Build line with simple +/- indicators and indentation
152 if (.not. node%is_file) then
153 ! Directory - add expand/collapse indicator and / suffix
154 if (associated(node%first_child)) then
155 ! Directory with children
156 if (node%expanded) then
157 line = prefix // EXPANDED_DIR // ' ' // trim(node%name) // '/'
158 else
159 line = prefix // COLLAPSED_DIR // ' ' // trim(node%name) // '/'
160 end if
161 else
162 ! Empty directory - no expand/collapse indicator
163 line = prefix // ' ' // trim(node%name) // '/'
164 end if
165 else
166 ! File - just indentation and name
167 line = prefix // ' ' // trim(node%name)
168 end if
169
170 ! Add status indicators for files only
171 if (node%is_file) then
172 if (node%is_staged) then
173 line = line // ' ' // ESC // '[32m↑' // ESC // '[0m' ! Green up arrow
174 end if
175 if (node%is_unstaged) then
176 line = line // ' ' // ESC // '[31m✗' // ESC // '[0m' ! Red X
177 end if
178 if (node%is_untracked) then
179 line = line // ' ' // ESC // '[90m✗' // ESC // '[0m' ! Gray X
180 end if
181 if (node%has_incoming) then
182 line = line // ' ' // ESC // '[34m↓' // ESC // '[0m' ! Blue down arrow
183 end if
184 end if
185
186 ! Render with selection highlight for files only
187 call terminal_move_cursor(current_row, start_col)
188 if (is_selected) then
189 ! Selection highlight (reverse video)
190 call terminal_write(ESC // '[7m' // line // ESC // '[0m')
191 else
192 ! Grey out directories that only contain hidden files
193 if (.not. node%is_file .and. node%all_children_hidden) then
194 call terminal_write(ESC // '[90m' // line // ESC // '[0m') ! Grey
195 else
196 call terminal_write(line)
197 end if
198 end if
199
200 current_row = current_row + 1
201 end if
202 end if
203
204 ! Render children (only if directory is expanded or root)
205 ! Files don't have children, so skip if is_file. For directories, check if expanded or root.
206 if ((.not. node%is_file .and. node%expanded) .or. is_root) then
207 child => node%first_child
208 do while (associated(child))
209 ! Determine if this is the last sibling
210 is_last_child = .not. associated(child%next_sibling)
211
212 ! Build prefix for child based on current node's position
213 if (is_root) then
214 ! Root's children start with no prefix
215 new_prefix = ''
216 else
217 ! Non-root children inherit prefix and add 2-space indentation
218 new_prefix = prefix // ' '
219 end if
220
221 call render_tree_node(child, new_prefix, .false., &
222 state, item_idx, current_row, end_row, start_col, width)
223 child => child%next_sibling
224 end do
225 end if
226 end subroutine render_tree_node
227
228 end module file_tree_renderer_module
229