Fortran · 14442 bytes Raw Blame History
1 module ui_display
2 use iso_fortran_env, only: output_unit
3 use terminal_control
4 use git_ops, only: write_git_indicators
5 use filesystem_ops, only: MAX_PATH, MAX_FILES
6 implicit none
7 private
8
9 public :: draw_interface, get_file_color
10
11 contains
12
13 subroutine draw_interface(r, c, current_dir, current_files, current_is_dir, current_is_exec, &
14 current_is_staged, current_is_unstaged, current_is_untracked, current_has_incoming, &
15 current_count, parent_files, parent_is_dir, parent_is_exec, parent_count, &
16 selected, parent_selected, scroll_offset, parent_scroll_offset, &
17 in_git_repo, repo_name, branch_name, &
18 move_mode, move_source_name, move_dest_selected, &
19 has_clipboard, clipboard_is_cut, clipboard_source_name, clipboard_count, &
20 is_selected, selection_count, &
21 current_is_favorite, parent_is_favorite)
22 integer, intent(in) :: r, c, current_count, parent_count, selected, parent_selected
23 integer, intent(in) :: scroll_offset, parent_scroll_offset
24 character(len=*), intent(in) :: current_dir, repo_name, branch_name
25 character(len=*), dimension(*), intent(in) :: current_files, parent_files
26 logical, dimension(*), intent(in) :: current_is_dir, parent_is_dir
27 logical, dimension(*), intent(in) :: current_is_exec, parent_is_exec
28 logical, dimension(*), intent(in) :: current_is_staged, current_is_unstaged, current_is_untracked
29 logical, dimension(*), intent(in) :: current_has_incoming
30 logical, intent(in) :: in_git_repo, move_mode
31 character(len=*), intent(in) :: move_source_name
32 integer, intent(in) :: move_dest_selected
33 logical, intent(in) :: has_clipboard, clipboard_is_cut
34 character(len=*), intent(in) :: clipboard_source_name
35 integer, intent(in) :: clipboard_count
36 logical, dimension(*), intent(in) :: is_selected
37 integer, intent(in) :: selection_count
38 logical, dimension(*), intent(in) :: current_is_favorite, parent_is_favorite
39 integer :: left_w, i, parent_idx, current_idx, vis_h, display_len
40 character(len=256) :: fname
41 character(len=20) :: color_code
42
43 left_w = c * 3 / 10
44 vis_h = r - 3 ! Visible height
45
46 ! Header
47 if (move_mode) then
48 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
49 " | " // RED // "MOVE: " // trim(move_source_name) // RESET
50 else if (selection_count > 0) then
51 ! Show selection count
52 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
53 " | " // BLUE // trim(adjustl(itoa(selection_count))) // " selected" // RESET
54 else if (has_clipboard) then
55 if (clipboard_count > 1) then
56 ! Multiple items in clipboard
57 if (clipboard_is_cut) then
58 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
59 " | " // YELLOW // "CUT: " // trim(adjustl(itoa(clipboard_count))) // " items" // RESET
60 else
61 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
62 " | " // GREEN // "COPY: " // trim(adjustl(itoa(clipboard_count))) // " items" // RESET
63 end if
64 else
65 ! Single item in clipboard
66 if (clipboard_is_cut) then
67 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
68 " | " // YELLOW // "CUT: " // trim(clipboard_source_name) // RESET
69 else
70 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) // &
71 " | " // GREEN // "COPY: " // trim(clipboard_source_name) // RESET
72 end if
73 end if
74 else
75 write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir)
76 end if
77
78 ! Files (render based on scroll offsets)
79 do i = 1, vis_h
80 parent_idx = i + parent_scroll_offset
81 current_idx = i + scroll_offset
82
83 ! Parent pane
84 if (parent_idx >= 1 .and. parent_idx <= parent_count) then
85 fname = parent_files(parent_idx)
86
87 ! Track if this item has a star (for visual width adjustment)
88 display_len = 0
89 if (parent_is_favorite(parent_idx)) then
90 fname = "★ " // trim(fname)
91 display_len = 1 ! Star takes 2 visual columns, so add 1 extra
92 end if
93
94 if (parent_is_dir(parent_idx) .and. parent_files(parent_idx) /= "." .and. parent_files(parent_idx) /= "..") then
95 fname = trim(fname) // "/"
96 end if
97
98 ! Get color for parent file
99 color_code = get_file_color(parent_files(parent_idx), parent_is_dir(parent_idx), parent_is_exec(parent_idx))
100
101 ! Calculate visual width: string length + extra for wide char
102 display_len = min(len_trim(fname) + display_len, left_w)
103
104 if (parent_idx == parent_selected) then
105 write(output_unit, '(a)', advance='no') DIM // BOLD // trim(color_code) // &
106 fname(1:min(len_trim(fname), left_w)) // RESET
107 else
108 write(output_unit, '(a)', advance='no') DIM // trim(color_code) // &
109 fname(1:min(len_trim(fname), left_w)) // RESET
110 end if
111 write(output_unit, '(a)', advance='no') repeat(" ", max(0, left_w - display_len))
112 else
113 write(output_unit, '(a)', advance='no') repeat(" ", left_w)
114 end if
115
116 ! Separator
117 write(output_unit, '(a)', advance='no') " │ "
118
119 ! Current pane
120 if (current_idx >= 1 .and. current_idx <= current_count) then
121 fname = current_files(current_idx)
122
123 ! Track if this item has a star (for visual width - star takes 2 columns)
124 display_len = 0
125 if (current_is_favorite(current_idx)) then
126 fname = "★ " // trim(fname)
127 display_len = 1 ! Add 1 to account for star being 2 visual columns
128 end if
129
130 if (current_is_dir(current_idx) .and. current_files(current_idx) /= "." .and. current_files(current_idx) /= "..") then
131 fname = trim(fname) // "/"
132 end if
133
134 ! Store the visual display length for this line (used by git indicators)
135 display_len = len_trim(fname) + display_len
136
137 ! Get color for current file
138 color_code = get_file_color(current_files(current_idx), current_is_dir(current_idx), current_is_exec(current_idx))
139
140 ! Check if this file is cut to clipboard (show in dark red)
141 ! Only highlight for single-item cuts (multi-cuts shown in header)
142 if (has_clipboard .and. clipboard_is_cut .and. clipboard_count == 1 .and. &
143 trim(current_files(current_idx)) == trim(clipboard_source_name)) then
144 ! File is cut - show in dark red (dimmed red)
145 write(output_unit, '(a)', advance='no') DIM // RED // trim(fname)
146 if (in_git_repo) then
147 call write_git_indicators(current_is_staged(current_idx), &
148 current_is_unstaged(current_idx), &
149 current_is_untracked(current_idx), &
150 current_has_incoming(current_idx), .false.)
151 end if
152 write(output_unit, '(a)') RESET
153 ! Move mode: show source in red, destination in white
154 else if (move_mode .and. trim(current_files(current_idx)) == trim(move_source_name)) then
155 ! Source file - show in RED
156 write(output_unit, '(a)', advance='no') RED // BOLD // trim(fname) // RESET
157 write(output_unit, '(a)') ""
158 else if (move_mode .and. current_idx == move_dest_selected) then
159 ! Destination cursor - show with white background
160 write(output_unit, '(a)', advance='no') REVERSE // WHITE // trim(fname)
161 if (in_git_repo) then
162 call write_git_indicators(current_is_staged(current_idx), &
163 current_is_unstaged(current_idx), &
164 current_is_untracked(current_idx), &
165 current_has_incoming(current_idx), .true.)
166 end if
167 write(output_unit, '(a)') RESET
168 else if (current_idx == selected .and. .not. move_mode) then
169 ! Normal selection cursor (not in move mode)
170 ! If file is cut, show selected with red background instead of default color
171 ! Only highlight for single-item cuts (multi-cuts shown in header)
172 if (has_clipboard .and. clipboard_is_cut .and. clipboard_count == 1 .and. &
173 trim(current_files(current_idx)) == trim(clipboard_source_name)) then
174 write(output_unit, '(a)', advance='no') REVERSE // RED // trim(fname)
175 else
176 write(output_unit, '(a)', advance='no') REVERSE // trim(color_code) // trim(fname)
177 end if
178 ! Add git indicators if in repo
179 if (in_git_repo) then
180 call write_git_indicators(current_is_staged(current_idx), &
181 current_is_unstaged(current_idx), &
182 current_is_untracked(current_idx), &
183 current_has_incoming(current_idx), .true.)
184 end if
185 write(output_unit, '(a)') RESET
186 else if (is_selected(current_idx)) then
187 ! Multi-selected item (not the cursor) - show with cyan background
188 write(output_unit, '(a)', advance='no') REVERSE // trim(color_code) // trim(fname)
189 ! Add git indicators if in repo
190 if (in_git_repo) then
191 call write_git_indicators(current_is_staged(current_idx), &
192 current_is_unstaged(current_idx), &
193 current_is_untracked(current_idx), &
194 current_has_incoming(current_idx), .true.)
195 end if
196 write(output_unit, '(a)') RESET
197 else
198 ! Normal rendering
199 write(output_unit, '(a)', advance='no') trim(color_code) // trim(fname)
200 ! Add git indicators if in repo
201 if (in_git_repo) then
202 call write_git_indicators(current_is_staged(current_idx), &
203 current_is_unstaged(current_idx), &
204 current_is_untracked(current_idx), &
205 current_has_incoming(current_idx), .false.)
206 end if
207 write(output_unit, '(a)') RESET
208 end if
209 else
210 write(output_unit, *)
211 end if
212 end do
213
214 ! Footer
215 if (move_mode) then
216 write(output_unit, '(a)') RED // "MOVE MODE: " // RESET // &
217 DIM // "↑↓:next/prev dir →:enter dir ←:parent ~:home /:root v:move here q:cancel" // RESET
218 else if (selection_count > 0) then
219 ! Selection mode footer - show multi-select help
220 write(output_unit, '(a)') BLUE // "MULTI-SELECT: " // RESET // &
221 DIM // "Space:toggle Shift+↑↓:block select y:copy x:cut p:paste r:delete | " // RESET // &
222 DIM // "→:enter ←:back ~:home /:root c:cd q:quit" // RESET
223 else if (in_git_repo) then
224 write(output_unit, '(a)') DIM // trim(repo_name) // ":" // trim(branch_name) // " | " // RESET // &
225 DIM // "Space:select Shift+↑↓:block | ↑↓:nav →:enter ←:back ~:home /:root s:search 8:favorites *:star o:open n:rename r:remove v:move y:copy x:cut p:paste .:hidden a:add u:unstage m:commit d:diff f:fetch l:pull h:push c:cd q:quit" // RESET
226 else
227 write(output_unit, '(a)') DIM // "Space:select Shift+↑↓:block | ↑↓:nav →:enter ←:back ~:home /:root s:search 8:favorites *:star o:open n:rename r:remove v:move y:copy x:cut p:paste .:hidden c:cd q:quit" // RESET
228 end if
229
230 contains
231 function itoa(n) result(str)
232 integer, intent(in) :: n
233 character(len=10) :: str
234 write(str, '(i0)') n
235 end function itoa
236 end subroutine draw_interface
237
238 function get_file_color(filename, is_dir, is_exec) result(color)
239 character(len=*), intent(in) :: filename
240 logical, intent(in) :: is_dir, is_exec
241 character(len=20) :: color
242
243 ! Directories: Blue and bold
244 if (is_dir) then
245 color = BOLD // BLUE
246 ! Dotfiles: Grey
247 else if (filename(1:1) == '.') then
248 color = GREY
249 ! Executable files: Green
250 else if (is_exec) then
251 color = GREEN
252 ! All other files: White
253 else
254 color = WHITE
255 end if
256 end function get_file_color
257
258 end module ui_display
259