totally scrap old approach. minimal two pane working
- SHA
07219b06f5d5cb6a7ef2c5ec79a3c32f7008e5a4- Parents
-
83516a6 - Tree
fd6614d
07219b0
07219b06f5d5cb6a7ef2c5ec79a3c32f7008e5a483516a6
fd6614d| Status | File | + | - |
|---|---|---|---|
| M |
src/filesystem/operations.f90
|
13 | 8 |
| A |
src/fortress_clean.f90
|
278 | 0 |
| A |
src/fortress_v2.f90
|
289 | 0 |
| M |
src/main.f90
|
2 | 2 |
| A |
src/simple_main.f90
|
119 | 0 |
| A |
src/test_right_pane.f90
|
31 | 0 |
| M |
src/ui/panes.f90
|
40 | 56 |
| A |
src/ui/panes_buffered.f90
|
169 | 0 |
| A |
src/ui/panes_debug.f90
|
88 | 0 |
src/filesystem/operations.f90modified@@ -22,7 +22,7 @@ contains | ||
| 22 | 22 | type(file_entry), dimension(MAX_FILES) :: entries |
| 23 | 23 | character(len=MAX_PATH) :: temp_file |
| 24 | 24 | character(len=MAX_PATH) :: line, full_path |
| 25 | - integer :: unit, ios, count, i | |
| 25 | + integer :: unit, ios, count, i, cmd_stat | |
| 26 | 26 | |
| 27 | 27 | ! Initialize entries |
| 28 | 28 | do i = 1, MAX_FILES |
@@ -31,11 +31,15 @@ contains | ||
| 31 | 31 | entries(i)%size = 0 |
| 32 | 32 | end do |
| 33 | 33 | |
| 34 | - ! Create a temporary file to store ls output | |
| 35 | - temp_file = "/tmp/fortress_ls_temp.txt" | |
| 34 | + ! Create a temporary file to store ls output in HOME directory | |
| 35 | + ! This should work even in raw terminal mode | |
| 36 | + call get_environment_variable("HOME", temp_file) | |
| 37 | + temp_file = trim(temp_file) // "/.fortress_ls_temp.txt" | |
| 36 | 38 | |
| 37 | - ! Use ls -1 for simpler parsing | |
| 38 | - call execute_command_line("ls -1a '" // trim(path) // "' > " // trim(temp_file), wait=.true.) | |
| 39 | + ! Use ls -1 for simpler parsing with error checking | |
| 40 | + ! Use explicit sh to ensure it works in raw terminal mode | |
| 41 | + call execute_command_line("sh -c 'ls -1a """ // trim(path) // """ > " // trim(temp_file) // " 2>&1'", & | |
| 42 | + exitstat=cmd_stat, wait=.true.) | |
| 39 | 43 | |
| 40 | 44 | ! Open and read the temp file |
| 41 | 45 | open(newunit=unit, file=temp_file, status='old', action='read', iostat=ios) |
@@ -94,7 +98,7 @@ contains | ||
| 94 | 98 | is_dir = .false. |
| 95 | 99 | |
| 96 | 100 | ! Use test command to check if it's a directory |
| 97 | - call execute_command_line("test -d '" // trim(path) // "'", & | |
| 101 | + call execute_command_line("sh -c 'test -d """ // trim(path) // """'", & | |
| 98 | 102 | exitstat=stat, wait=.true.) |
| 99 | 103 | is_dir = (stat == 0) |
| 100 | 104 | end function is_directory |
@@ -105,8 +109,9 @@ contains | ||
| 105 | 109 | integer :: unit, ios |
| 106 | 110 | |
| 107 | 111 | ! Create a temporary file to store pwd output |
| 108 | - temp_file = "/tmp/fortress_pwd_temp.txt" | |
| 109 | - call execute_command_line("pwd > " // trim(temp_file), wait=.true.) | |
| 112 | + call get_environment_variable("HOME", temp_file) | |
| 113 | + temp_file = trim(temp_file) // "/.fortress_pwd_temp.txt" | |
| 114 | + call execute_command_line("sh -c 'pwd > " // trim(temp_file) // "'", wait=.true.) | |
| 110 | 115 | |
| 111 | 116 | ! Read the current directory |
| 112 | 117 | open(newunit=unit, file=temp_file, status='old', action='read', iostat=ios) |
src/fortress_clean.f90added@@ -0,0 +1,278 @@ | ||
| 1 | +program fortress_clean | |
| 2 | + use iso_fortran_env, only: output_unit, error_unit | |
| 3 | + implicit none | |
| 4 | + | |
| 5 | + ! Constants | |
| 6 | + integer, parameter :: MAX_PATH = 512 | |
| 7 | + integer, parameter :: MAX_FILES = 500 | |
| 8 | + character(len=*), parameter :: ESC = char(27) | |
| 9 | + character(len=*), parameter :: CLEAR = ESC // "[2J" // ESC // "[H" | |
| 10 | + character(len=*), parameter :: BOLD = ESC // "[1m" | |
| 11 | + character(len=*), parameter :: DIM = ESC // "[2m" | |
| 12 | + character(len=*), parameter :: REVERSE = ESC // "[7m" | |
| 13 | + character(len=*), parameter :: RESET = ESC // "[0m" | |
| 14 | + | |
| 15 | + ! Variables | |
| 16 | + character(len=MAX_PATH) :: current_dir, parent_dir, temp_dir | |
| 17 | + character(len=MAX_PATH), dimension(MAX_FILES) :: current_files, parent_files | |
| 18 | + logical, dimension(MAX_FILES) :: current_is_dir, parent_is_dir | |
| 19 | + integer :: current_count, parent_count | |
| 20 | + integer :: selected = 1 | |
| 21 | + integer :: parent_selected = -1 | |
| 22 | + character(len=1) :: key | |
| 23 | + logical :: running = .true. | |
| 24 | + integer :: i, rows, cols | |
| 25 | + | |
| 26 | + ! Initialize | |
| 27 | + current_dir = get_pwd() | |
| 28 | + parent_dir = get_parent_path(current_dir) | |
| 29 | + | |
| 30 | + ! Setup terminal | |
| 31 | + call execute_command_line("stty -icanon -echo min 1 time 0 2>/dev/null") | |
| 32 | + | |
| 33 | + ! Main loop | |
| 34 | + do while (running) | |
| 35 | + ! Get files | |
| 36 | + call get_file_list(current_dir, current_files, current_is_dir, current_count) | |
| 37 | + call get_file_list(parent_dir, parent_files, parent_is_dir, parent_count) | |
| 38 | + | |
| 39 | + ! Find current dir in parent | |
| 40 | + parent_selected = find_in_parent(current_dir, parent_files, parent_count) | |
| 41 | + | |
| 42 | + ! Get terminal size | |
| 43 | + call get_term_size(rows, cols) | |
| 44 | + | |
| 45 | + ! Draw interface | |
| 46 | + write(output_unit, '(a)', advance='no') CLEAR | |
| 47 | + call draw_interface(rows, cols) | |
| 48 | + | |
| 49 | + ! Get input | |
| 50 | + read(*, '(a1)', advance='no') key | |
| 51 | + | |
| 52 | + ! Handle input | |
| 53 | + select case(ichar(key)) | |
| 54 | + case(27) ! ESC sequence | |
| 55 | + call read_arrow_key(key) | |
| 56 | + select case(key) | |
| 57 | + case('A') ! Up | |
| 58 | + if (selected > 1) selected = selected - 1 | |
| 59 | + case('B') ! Down | |
| 60 | + if (selected < current_count) selected = selected + 1 | |
| 61 | + case('C') ! Right - enter | |
| 62 | + if (current_is_dir(selected)) then | |
| 63 | + if (trim(current_files(selected)) == "..") then | |
| 64 | + temp_dir = current_dir | |
| 65 | + current_dir = parent_dir | |
| 66 | + parent_dir = get_parent_path(current_dir) | |
| 67 | + selected = max(1, find_in_parent(temp_dir, current_files, MAX_FILES)) | |
| 68 | + else if (trim(current_files(selected)) /= ".") then | |
| 69 | + parent_dir = current_dir | |
| 70 | + current_dir = join_path(current_dir, current_files(selected)) | |
| 71 | + selected = 1 | |
| 72 | + end if | |
| 73 | + end if | |
| 74 | + case('D') ! Left - back | |
| 75 | + if (current_dir /= "/") then | |
| 76 | + temp_dir = current_dir | |
| 77 | + current_dir = parent_dir | |
| 78 | + parent_dir = get_parent_path(current_dir) | |
| 79 | + selected = max(1, find_in_parent(temp_dir, current_files, MAX_FILES)) | |
| 80 | + end if | |
| 81 | + end select | |
| 82 | + case(113, 81) ! 'q' or 'Q' | |
| 83 | + running = .false. | |
| 84 | + end select | |
| 85 | + end do | |
| 86 | + | |
| 87 | + ! Cleanup | |
| 88 | + call execute_command_line("stty icanon echo 2>/dev/null") | |
| 89 | + write(output_unit, '(a)', advance='no') CLEAR | |
| 90 | + write(output_unit, '(a)') "Thanks for using FORTRESS!" | |
| 91 | + | |
| 92 | +contains | |
| 93 | + | |
| 94 | + function get_pwd() result(path) | |
| 95 | + character(len=MAX_PATH) :: path | |
| 96 | + integer :: unit, ios | |
| 97 | + | |
| 98 | + call execute_command_line("pwd > .fortress_pwd 2>/dev/null", wait=.true.) | |
| 99 | + open(newunit=unit, file=".fortress_pwd", status='old', iostat=ios) | |
| 100 | + if (ios == 0) then | |
| 101 | + read(unit, '(a)') path | |
| 102 | + close(unit) | |
| 103 | + else | |
| 104 | + path = "." | |
| 105 | + end if | |
| 106 | + call execute_command_line("rm -f .fortress_pwd 2>/dev/null") | |
| 107 | + end function get_pwd | |
| 108 | + | |
| 109 | + function get_parent_path(path) result(parent) | |
| 110 | + character(len=*), intent(in) :: path | |
| 111 | + character(len=MAX_PATH) :: parent | |
| 112 | + integer :: pos | |
| 113 | + | |
| 114 | + pos = index(path, "/", back=.true.) | |
| 115 | + if (pos > 1) then | |
| 116 | + parent = path(1:pos-1) | |
| 117 | + else if (pos == 1) then | |
| 118 | + parent = "/" | |
| 119 | + else | |
| 120 | + parent = "." | |
| 121 | + end if | |
| 122 | + end function get_parent_path | |
| 123 | + | |
| 124 | + function join_path(base, name) result(full) | |
| 125 | + character(len=*), intent(in) :: base, name | |
| 126 | + character(len=MAX_PATH) :: full | |
| 127 | + | |
| 128 | + if (base == "/") then | |
| 129 | + full = "/" // trim(name) | |
| 130 | + else | |
| 131 | + full = trim(base) // "/" // trim(name) | |
| 132 | + end if | |
| 133 | + end function join_path | |
| 134 | + | |
| 135 | + function find_in_parent(dir, files, count) result(idx) | |
| 136 | + character(len=*), intent(in) :: dir | |
| 137 | + character(len=*), dimension(*), intent(in) :: files | |
| 138 | + integer, intent(in) :: count | |
| 139 | + integer :: idx, pos | |
| 140 | + character(len=256) :: basename | |
| 141 | + | |
| 142 | + pos = index(dir, "/", back=.true.) | |
| 143 | + if (pos > 0) then | |
| 144 | + basename = dir(pos+1:) | |
| 145 | + else | |
| 146 | + basename = dir | |
| 147 | + end if | |
| 148 | + | |
| 149 | + do idx = 1, count | |
| 150 | + if (trim(files(idx)) == trim(basename)) return | |
| 151 | + end do | |
| 152 | + idx = 1 | |
| 153 | + end function find_in_parent | |
| 154 | + | |
| 155 | + subroutine get_file_list(dir, files, is_dir, count) | |
| 156 | + character(len=*), intent(in) :: dir | |
| 157 | + character(len=*), dimension(*), intent(out) :: files | |
| 158 | + logical, dimension(*), intent(out) :: is_dir | |
| 159 | + integer, intent(out) :: count | |
| 160 | + integer :: unit, ios, stat | |
| 161 | + character(len=MAX_PATH) :: fullpath | |
| 162 | + | |
| 163 | + call execute_command_line("ls -1a '" // trim(dir) // "' > .fortress_ls 2>/dev/null", wait=.true.) | |
| 164 | + | |
| 165 | + open(newunit=unit, file=".fortress_ls", status='old', iostat=ios) | |
| 166 | + if (ios /= 0) then | |
| 167 | + count = 0 | |
| 168 | + return | |
| 169 | + end if | |
| 170 | + | |
| 171 | + count = 0 | |
| 172 | + do | |
| 173 | + count = count + 1 | |
| 174 | + if (count > MAX_FILES) exit | |
| 175 | + read(unit, '(a)', iostat=ios) files(count) | |
| 176 | + if (ios /= 0) then | |
| 177 | + count = count - 1 | |
| 178 | + exit | |
| 179 | + end if | |
| 180 | + | |
| 181 | + fullpath = join_path(dir, files(count)) | |
| 182 | + call execute_command_line("test -d '" // trim(fullpath) // "'", exitstat=stat, wait=.true.) | |
| 183 | + is_dir(count) = (stat == 0) | |
| 184 | + end do | |
| 185 | + | |
| 186 | + close(unit) | |
| 187 | + call execute_command_line("rm -f .fortress_ls 2>/dev/null") | |
| 188 | + end subroutine get_file_list | |
| 189 | + | |
| 190 | + subroutine get_term_size(r, c) | |
| 191 | + integer, intent(out) :: r, c | |
| 192 | + integer :: unit, ios | |
| 193 | + | |
| 194 | + call execute_command_line("tput lines > .fortress_size 2>/dev/null", wait=.true.) | |
| 195 | + open(newunit=unit, file=".fortress_size", status='old', iostat=ios) | |
| 196 | + if (ios == 0) then | |
| 197 | + read(unit, *) r | |
| 198 | + close(unit) | |
| 199 | + else | |
| 200 | + r = 24 | |
| 201 | + end if | |
| 202 | + | |
| 203 | + call execute_command_line("tput cols > .fortress_size 2>/dev/null", wait=.true.) | |
| 204 | + open(newunit=unit, file=".fortress_size", status='old', iostat=ios) | |
| 205 | + if (ios == 0) then | |
| 206 | + read(unit, *) c | |
| 207 | + close(unit) | |
| 208 | + else | |
| 209 | + c = 80 | |
| 210 | + end if | |
| 211 | + | |
| 212 | + call execute_command_line("rm -f .fortress_size 2>/dev/null") | |
| 213 | + end subroutine get_term_size | |
| 214 | + | |
| 215 | + subroutine draw_interface(r, c) | |
| 216 | + integer, intent(in) :: r, c | |
| 217 | + integer :: left_w, i | |
| 218 | + character(len=256) :: fname | |
| 219 | + | |
| 220 | + left_w = c * 3 / 10 | |
| 221 | + | |
| 222 | + ! Header | |
| 223 | + write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) | |
| 224 | + | |
| 225 | + ! Files | |
| 226 | + do i = 1, min(r-3, max(parent_count, current_count)) | |
| 227 | + ! Parent pane | |
| 228 | + if (i <= parent_count) then | |
| 229 | + fname = parent_files(i) | |
| 230 | + if (parent_is_dir(i) .and. fname /= "." .and. fname /= "..") then | |
| 231 | + fname = trim(fname) // "/" | |
| 232 | + end if | |
| 233 | + if (i == parent_selected) then | |
| 234 | + write(output_unit, '(a)', advance='no') DIM // BOLD // fname(1:min(len_trim(fname),left_w)) // RESET | |
| 235 | + else | |
| 236 | + write(output_unit, '(a)', advance='no') DIM // fname(1:min(len_trim(fname),left_w)) // RESET | |
| 237 | + end if | |
| 238 | + write(output_unit, '(a)', advance='no') repeat(" ", max(0, left_w - len_trim(fname))) | |
| 239 | + else | |
| 240 | + write(output_unit, '(a)', advance='no') repeat(" ", left_w) | |
| 241 | + end if | |
| 242 | + | |
| 243 | + ! Separator | |
| 244 | + write(output_unit, '(a)', advance='no') " │ " | |
| 245 | + | |
| 246 | + ! Current pane | |
| 247 | + if (i <= current_count) then | |
| 248 | + fname = current_files(i) | |
| 249 | + if (current_is_dir(i) .and. fname /= "." .and. fname /= "..") then | |
| 250 | + fname = trim(fname) // "/" | |
| 251 | + end if | |
| 252 | + if (i == selected) then | |
| 253 | + write(output_unit, '(a)') REVERSE // trim(fname) // RESET | |
| 254 | + else | |
| 255 | + write(output_unit, '(a)') trim(fname) | |
| 256 | + end if | |
| 257 | + else | |
| 258 | + write(output_unit, *) | |
| 259 | + end if | |
| 260 | + end do | |
| 261 | + | |
| 262 | + ! Footer | |
| 263 | + write(output_unit, '(a,i0,a)') DIM // "↑↓:nav →:enter ←:back q:quit [", selected, "/" // trim(adjustl(char(current_count))) // "]" // RESET | |
| 264 | + end subroutine draw_interface | |
| 265 | + | |
| 266 | + subroutine read_arrow_key(k) | |
| 267 | + character(len=1), intent(out) :: k | |
| 268 | + character(len=1) :: ch | |
| 269 | + | |
| 270 | + read(*, '(a1)', advance='no') ch | |
| 271 | + if (ch == '[') then | |
| 272 | + read(*, '(a1)', advance='no') k | |
| 273 | + else | |
| 274 | + k = ch | |
| 275 | + end if | |
| 276 | + end subroutine read_arrow_key | |
| 277 | + | |
| 278 | +end program fortress_clean | |
src/fortress_v2.f90added@@ -0,0 +1,289 @@ | ||
| 1 | +program fortress_v2 | |
| 2 | + use iso_fortran_env, only: output_unit | |
| 3 | + implicit none | |
| 4 | + | |
| 5 | + ! File entry type | |
| 6 | + type :: file_entry | |
| 7 | + character(len=256) :: name = "" | |
| 8 | + logical :: is_dir = .false. | |
| 9 | + end type file_entry | |
| 10 | + | |
| 11 | + ! Constants | |
| 12 | + integer, parameter :: MAX_FILES = 200 | |
| 13 | + character(len=*), parameter :: ESC = char(27) | |
| 14 | + | |
| 15 | + ! State variables | |
| 16 | + type(file_entry), dimension(MAX_FILES) :: current_files, parent_files | |
| 17 | + integer :: current_count, parent_count | |
| 18 | + integer :: selected = 1 | |
| 19 | + character(len=512) :: current_path, parent_path | |
| 20 | + logical :: running = .true. | |
| 21 | + character(len=10) :: key | |
| 22 | + integer :: rows, cols | |
| 23 | + integer :: i | |
| 24 | + | |
| 25 | + ! Initialize | |
| 26 | + call get_cwd(current_path) | |
| 27 | + call get_parent(current_path, parent_path) | |
| 28 | + | |
| 29 | + ! Setup terminal | |
| 30 | + call system("stty -icanon -echo min 1 time 0") | |
| 31 | + call get_terminal_size(rows, cols) | |
| 32 | + | |
| 33 | + ! Main loop | |
| 34 | + do while (running) | |
| 35 | + ! Get directory contents | |
| 36 | + call list_dir(current_path, current_files, current_count) | |
| 37 | + call list_dir(parent_path, parent_files, parent_count) | |
| 38 | + | |
| 39 | + ! Draw screen | |
| 40 | + call clear_screen() | |
| 41 | + call draw_header(current_path) | |
| 42 | + call draw_panes(parent_files, parent_count, current_files, current_count, selected, rows, cols) | |
| 43 | + call draw_footer() | |
| 44 | + | |
| 45 | + ! Get keyboard input | |
| 46 | + call get_key(key) | |
| 47 | + | |
| 48 | + ! Handle input | |
| 49 | + select case(trim(key)) | |
| 50 | + case('A') ! Up arrow | |
| 51 | + if (selected > 1) selected = selected - 1 | |
| 52 | + case('B') ! Down arrow | |
| 53 | + if (selected < current_count) selected = selected + 1 | |
| 54 | + case('C') ! Right arrow - enter directory | |
| 55 | + if (selected <= current_count .and. current_files(selected)%is_dir) then | |
| 56 | + if (trim(current_files(selected)%name) /= ".") then | |
| 57 | + call change_dir(current_path, current_files(selected)%name, parent_path) | |
| 58 | + selected = 1 | |
| 59 | + end if | |
| 60 | + end if | |
| 61 | + case('D') ! Left arrow - go to parent | |
| 62 | + if (trim(current_path) /= "/") then | |
| 63 | + current_path = parent_path | |
| 64 | + call get_parent(current_path, parent_path) | |
| 65 | + selected = 1 | |
| 66 | + end if | |
| 67 | + case('q', 'Q') | |
| 68 | + running = .false. | |
| 69 | + end select | |
| 70 | + end do | |
| 71 | + | |
| 72 | + ! Cleanup | |
| 73 | + call system("stty icanon echo") | |
| 74 | + call clear_screen() | |
| 75 | + write(output_unit, '(a)') "Thanks for using FORTRESS v2!" | |
| 76 | + | |
| 77 | +contains | |
| 78 | + | |
| 79 | + subroutine get_cwd(path) | |
| 80 | + character(len=*), intent(out) :: path | |
| 81 | + integer :: unit, ios | |
| 82 | + | |
| 83 | + call execute_command_line("pwd > pwd_temp.txt", wait=.true.) | |
| 84 | + open(newunit=unit, file="pwd_temp.txt", status='old', iostat=ios) | |
| 85 | + if (ios == 0) then | |
| 86 | + read(unit, '(a)') path | |
| 87 | + close(unit) | |
| 88 | + else | |
| 89 | + path = "." | |
| 90 | + end if | |
| 91 | + call execute_command_line("rm -f pwd_temp.txt", wait=.false.) | |
| 92 | + end subroutine get_cwd | |
| 93 | + | |
| 94 | + subroutine get_parent(path, parent) | |
| 95 | + character(len=*), intent(in) :: path | |
| 96 | + character(len=*), intent(out) :: parent | |
| 97 | + integer :: last_slash | |
| 98 | + | |
| 99 | + last_slash = index(path, '/', back=.true.) | |
| 100 | + if (last_slash > 1) then | |
| 101 | + parent = path(1:last_slash-1) | |
| 102 | + else | |
| 103 | + parent = "/" | |
| 104 | + end if | |
| 105 | + end subroutine get_parent | |
| 106 | + | |
| 107 | + subroutine change_dir(current, new_name, parent) | |
| 108 | + character(len=*), intent(inout) :: current | |
| 109 | + character(len=*), intent(in) :: new_name | |
| 110 | + character(len=*), intent(inout) :: parent | |
| 111 | + | |
| 112 | + if (trim(new_name) == "..") then | |
| 113 | + if (trim(current) /= "/") then | |
| 114 | + parent = current | |
| 115 | + call get_parent(current, parent) | |
| 116 | + current = parent | |
| 117 | + call get_parent(current, parent) | |
| 118 | + end if | |
| 119 | + else | |
| 120 | + parent = current | |
| 121 | + if (trim(current) == "/") then | |
| 122 | + current = "/" // trim(new_name) | |
| 123 | + else | |
| 124 | + current = trim(current) // "/" // trim(new_name) | |
| 125 | + end if | |
| 126 | + end if | |
| 127 | + end subroutine change_dir | |
| 128 | + | |
| 129 | + subroutine list_dir(path, files, count) | |
| 130 | + character(len=*), intent(in) :: path | |
| 131 | + type(file_entry), dimension(MAX_FILES), intent(out) :: files | |
| 132 | + integer, intent(out) :: count | |
| 133 | + integer :: unit, ios | |
| 134 | + character(len=512) :: cmd | |
| 135 | + | |
| 136 | + ! Initialize | |
| 137 | + files%name = "" | |
| 138 | + files%is_dir = .false. | |
| 139 | + | |
| 140 | + ! List files | |
| 141 | + write(cmd, '(a)') "ls -1a " // trim(path) // " 2>/dev/null > ls_temp.txt" | |
| 142 | + call execute_command_line(trim(cmd), wait=.true.) | |
| 143 | + | |
| 144 | + open(newunit=unit, file="ls_temp.txt", status='old', iostat=ios) | |
| 145 | + if (ios /= 0) then | |
| 146 | + count = 0 | |
| 147 | + return | |
| 148 | + end if | |
| 149 | + | |
| 150 | + count = 0 | |
| 151 | + do | |
| 152 | + count = count + 1 | |
| 153 | + if (count > MAX_FILES) exit | |
| 154 | + read(unit, '(a)', iostat=ios) files(count)%name | |
| 155 | + if (ios /= 0) then | |
| 156 | + count = count - 1 | |
| 157 | + exit | |
| 158 | + end if | |
| 159 | + ! Check if directory | |
| 160 | + call is_directory(path, files(count)%name, files(count)%is_dir) | |
| 161 | + end do | |
| 162 | + | |
| 163 | + close(unit) | |
| 164 | + call execute_command_line("rm -f ls_temp.txt", wait=.false.) | |
| 165 | + end subroutine list_dir | |
| 166 | + | |
| 167 | + subroutine is_directory(base_path, name, is_dir) | |
| 168 | + character(len=*), intent(in) :: base_path, name | |
| 169 | + logical, intent(out) :: is_dir | |
| 170 | + character(len=512) :: full_path | |
| 171 | + integer :: stat | |
| 172 | + | |
| 173 | + if (trim(base_path) == "/") then | |
| 174 | + full_path = "/" // trim(name) | |
| 175 | + else | |
| 176 | + full_path = trim(base_path) // "/" // trim(name) | |
| 177 | + end if | |
| 178 | + | |
| 179 | + call execute_command_line("test -d '" // trim(full_path) // "'", & | |
| 180 | + exitstat=stat, wait=.true.) | |
| 181 | + is_dir = (stat == 0) | |
| 182 | + end subroutine is_directory | |
| 183 | + | |
| 184 | + subroutine get_terminal_size(rows, cols) | |
| 185 | + integer, intent(out) :: rows, cols | |
| 186 | + integer :: unit, ios | |
| 187 | + | |
| 188 | + call execute_command_line("tput lines > size_temp.txt", wait=.true.) | |
| 189 | + open(newunit=unit, file="size_temp.txt", status='old', iostat=ios) | |
| 190 | + if (ios == 0) then | |
| 191 | + read(unit, *) rows | |
| 192 | + close(unit) | |
| 193 | + else | |
| 194 | + rows = 24 | |
| 195 | + end if | |
| 196 | + | |
| 197 | + call execute_command_line("tput cols > size_temp.txt", wait=.true.) | |
| 198 | + open(newunit=unit, file="size_temp.txt", status='old', iostat=ios) | |
| 199 | + if (ios == 0) then | |
| 200 | + read(unit, *) cols | |
| 201 | + close(unit) | |
| 202 | + else | |
| 203 | + cols = 80 | |
| 204 | + end if | |
| 205 | + | |
| 206 | + call execute_command_line("rm -f size_temp.txt", wait=.false.) | |
| 207 | + end subroutine get_terminal_size | |
| 208 | + | |
| 209 | + subroutine clear_screen() | |
| 210 | + write(output_unit, '(a)', advance='no') ESC // "[2J" // ESC // "[H" | |
| 211 | + end subroutine clear_screen | |
| 212 | + | |
| 213 | + subroutine draw_header(path) | |
| 214 | + character(len=*), intent(in) :: path | |
| 215 | + write(output_unit, '(a)') ESC // "[1m" // "FORTRESS v2" // ESC // "[0m - " // trim(path) | |
| 216 | + write(output_unit, '(a)') repeat("=", 70) | |
| 217 | + end subroutine draw_header | |
| 218 | + | |
| 219 | + subroutine draw_panes(p_files, p_count, c_files, c_count, sel, rows, cols) | |
| 220 | + type(file_entry), dimension(*), intent(in) :: p_files, c_files | |
| 221 | + integer, intent(in) :: p_count, c_count, sel, rows, cols | |
| 222 | + integer :: i | |
| 223 | + integer :: left_width, right_width | |
| 224 | + character(len=30) :: left_text | |
| 225 | + character(len=50) :: right_text | |
| 226 | + | |
| 227 | + left_width = cols * 3 / 10 | |
| 228 | + right_width = cols - left_width - 3 | |
| 229 | + | |
| 230 | + do i = 1, min(rows - 5, max(p_count, c_count)) | |
| 231 | + ! Left pane | |
| 232 | + if (i <= p_count) then | |
| 233 | + left_text = p_files(i)%name | |
| 234 | + if (p_files(i)%is_dir .and. trim(left_text) /= "." .and. trim(left_text) /= "..") then | |
| 235 | + left_text = trim(left_text) // "/" | |
| 236 | + end if | |
| 237 | + if (len_trim(left_text) > left_width) then | |
| 238 | + left_text = left_text(1:left_width-3) // "..." | |
| 239 | + end if | |
| 240 | + write(output_unit, '(a)', advance='no') ESC // "[2m" // left_text(1:left_width) // ESC // "[0m" | |
| 241 | + else | |
| 242 | + write(output_unit, '(a)', advance='no') repeat(" ", left_width) | |
| 243 | + end if | |
| 244 | + | |
| 245 | + ! Separator | |
| 246 | + write(output_unit, '(a)', advance='no') " | " | |
| 247 | + | |
| 248 | + ! Right pane | |
| 249 | + if (i <= c_count) then | |
| 250 | + right_text = c_files(i)%name | |
| 251 | + if (c_files(i)%is_dir .and. trim(right_text) /= "." .and. trim(right_text) /= "..") then | |
| 252 | + right_text = trim(right_text) // "/" | |
| 253 | + end if | |
| 254 | + | |
| 255 | + if (i == sel) then | |
| 256 | + write(output_unit, '(a)') ESC // "[7m" // trim(right_text) // ESC // "[0m" | |
| 257 | + else | |
| 258 | + write(output_unit, '(a)') trim(right_text) | |
| 259 | + end if | |
| 260 | + else | |
| 261 | + write(output_unit, *) | |
| 262 | + end if | |
| 263 | + end do | |
| 264 | + end subroutine draw_panes | |
| 265 | + | |
| 266 | + subroutine draw_footer() | |
| 267 | + write(output_unit, *) | |
| 268 | + write(output_unit, '(a)') ESC // "[2m" // "↑↓: navigate | →: enter | ←: back | q: quit" // ESC // "[0m" | |
| 269 | + end subroutine draw_footer | |
| 270 | + | |
| 271 | + subroutine get_key(key) | |
| 272 | + character(len=*), intent(out) :: key | |
| 273 | + character(len=1) :: ch | |
| 274 | + | |
| 275 | + key = "" | |
| 276 | + read(*, '(a1)', advance='no', iostat=i) ch | |
| 277 | + | |
| 278 | + if (ch == ESC) then | |
| 279 | + read(*, '(a1)', advance='no', iostat=i) ch | |
| 280 | + if (ch == '[') then | |
| 281 | + read(*, '(a1)', advance='no', iostat=i) ch | |
| 282 | + key = ch | |
| 283 | + end if | |
| 284 | + else | |
| 285 | + key = ch | |
| 286 | + end if | |
| 287 | + end subroutine get_key | |
| 288 | + | |
| 289 | +end program fortress_v2 | |
src/main.f90modified@@ -4,7 +4,7 @@ program fortress_main | ||
| 4 | 4 | use terminal_input, only: get_key, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ENTER, KEY_QUIT |
| 5 | 5 | use filesystem_ops, only: list_directory, get_parent_dir, get_current_dir, is_directory, & |
| 6 | 6 | file_entry, MAX_FILES |
| 7 | - use ui_panes, only: draw_panes, update_selection | |
| 7 | + use ui_panes_buffered, only: draw_panes_buffered | |
| 8 | 8 | |
| 9 | 9 | implicit none |
| 10 | 10 | |
@@ -69,7 +69,7 @@ program fortress_main | ||
| 69 | 69 | needs_full_redraw = .false. |
| 70 | 70 | end if |
| 71 | 71 | |
| 72 | - call draw_panes(parent_dir, current_dir, selected_index, parent_selected_index) | |
| 72 | + call draw_panes_buffered(parent_dir, current_dir, selected_index, parent_selected_index) | |
| 73 | 73 | |
| 74 | 74 | key = get_key() |
| 75 | 75 | |
src/simple_main.f90added@@ -0,0 +1,119 @@ | ||
| 1 | +program simple_fortress | |
| 2 | + use iso_fortran_env, only: output_unit, input_unit | |
| 3 | + implicit none | |
| 4 | + | |
| 5 | + character(len=256) :: current_path | |
| 6 | + character(len=256) :: parent_path | |
| 7 | + character(len=256), dimension(100) :: files | |
| 8 | + character(len=256), dimension(100) :: parent_files | |
| 9 | + integer :: num_files, num_parent_files | |
| 10 | + integer :: selected = 1 | |
| 11 | + integer :: i | |
| 12 | + character(len=1) :: key | |
| 13 | + logical :: running = .true. | |
| 14 | + | |
| 15 | + ! Start in current directory | |
| 16 | + current_path = "." | |
| 17 | + parent_path = ".." | |
| 18 | + | |
| 19 | + ! Main loop | |
| 20 | + do while (running) | |
| 21 | + ! Get file lists | |
| 22 | + call get_files(current_path, files, num_files) | |
| 23 | + call get_files(parent_path, parent_files, num_parent_files) | |
| 24 | + | |
| 25 | + ! Clear screen (simple way) | |
| 26 | + write(output_unit, '(a)') char(27) // "[2J" // char(27) // "[H" | |
| 27 | + | |
| 28 | + ! Draw header | |
| 29 | + write(output_unit, '(a)') "FORTRESS - Simple File Browser" | |
| 30 | + write(output_unit, '(a)') "==============================" | |
| 31 | + write(output_unit, *) | |
| 32 | + | |
| 33 | + ! Draw files in two columns | |
| 34 | + write(output_unit, '(a)') "Parent Directory | Current Directory" | |
| 35 | + write(output_unit, '(a)') "------------------------- | -------------------------" | |
| 36 | + | |
| 37 | + do i = 1, max(num_parent_files, num_files) | |
| 38 | + if (i <= num_parent_files) then | |
| 39 | + write(output_unit, '(a25)', advance='no') adjustl(parent_files(i)) | |
| 40 | + else | |
| 41 | + write(output_unit, '(a25)', advance='no') " " | |
| 42 | + end if | |
| 43 | + | |
| 44 | + write(output_unit, '(a)', advance='no') " | " | |
| 45 | + | |
| 46 | + if (i <= num_files) then | |
| 47 | + if (i == selected) then | |
| 48 | + write(output_unit, '(a)') "> " // trim(files(i)) | |
| 49 | + else | |
| 50 | + write(output_unit, '(a)') " " // trim(files(i)) | |
| 51 | + end if | |
| 52 | + else | |
| 53 | + write(output_unit, *) | |
| 54 | + end if | |
| 55 | + end do | |
| 56 | + | |
| 57 | + ! Show controls | |
| 58 | + write(output_unit, *) | |
| 59 | + write(output_unit, '(a)') "Controls: j=down, k=up, q=quit" | |
| 60 | + write(output_unit, '(a)', advance='no') "Command: " | |
| 61 | + | |
| 62 | + ! Get input | |
| 63 | + read(input_unit, '(a1)') key | |
| 64 | + | |
| 65 | + select case(key) | |
| 66 | + case('j', 'J') | |
| 67 | + if (selected < num_files) selected = selected + 1 | |
| 68 | + case('k', 'K') | |
| 69 | + if (selected > 1) selected = selected - 1 | |
| 70 | + case('q', 'Q') | |
| 71 | + running = .false. | |
| 72 | + end select | |
| 73 | + end do | |
| 74 | + | |
| 75 | + write(output_unit, '(a)') "Goodbye!" | |
| 76 | + | |
| 77 | +contains | |
| 78 | + | |
| 79 | + subroutine get_files(path, file_list, count) | |
| 80 | + character(len=*), intent(in) :: path | |
| 81 | + character(len=256), dimension(100), intent(out) :: file_list | |
| 82 | + integer, intent(out) :: count | |
| 83 | + | |
| 84 | + character(len=512) :: cmd | |
| 85 | + character(len=256) :: temp_file | |
| 86 | + integer :: unit, ios | |
| 87 | + | |
| 88 | + ! Create temp file name | |
| 89 | + temp_file = "temp_files.txt" | |
| 90 | + | |
| 91 | + ! List files to temp file | |
| 92 | + write(cmd, '(a)') "ls -1 " // trim(path) // " > " // trim(temp_file) // " 2>/dev/null" | |
| 93 | + call execute_command_line(cmd, wait=.true.) | |
| 94 | + | |
| 95 | + ! Read files | |
| 96 | + open(newunit=unit, file=temp_file, status='old', iostat=ios) | |
| 97 | + if (ios /= 0) then | |
| 98 | + count = 0 | |
| 99 | + return | |
| 100 | + end if | |
| 101 | + | |
| 102 | + count = 0 | |
| 103 | + do | |
| 104 | + count = count + 1 | |
| 105 | + if (count > 100) exit | |
| 106 | + read(unit, '(a)', iostat=ios) file_list(count) | |
| 107 | + if (ios /= 0) then | |
| 108 | + count = count - 1 | |
| 109 | + exit | |
| 110 | + end if | |
| 111 | + end do | |
| 112 | + | |
| 113 | + close(unit) | |
| 114 | + | |
| 115 | + ! Clean up | |
| 116 | + call execute_command_line("rm -f " // trim(temp_file), wait=.false.) | |
| 117 | + end subroutine get_files | |
| 118 | + | |
| 119 | +end program simple_fortress | |
src/test_right_pane.f90added@@ -0,0 +1,31 @@ | ||
| 1 | +program test_right_pane | |
| 2 | + use terminal_screen, only: init_screen, cleanup_screen, clear_screen | |
| 3 | + use ui_panes_buffered, only: draw_panes_buffered | |
| 4 | + | |
| 5 | + implicit none | |
| 6 | + character(len=256) :: current_dir, parent_dir | |
| 7 | + | |
| 8 | + ! Set test directories | |
| 9 | + current_dir = "." | |
| 10 | + parent_dir = ".." | |
| 11 | + | |
| 12 | + ! Initialize screen | |
| 13 | + call init_screen() | |
| 14 | + call clear_screen() | |
| 15 | + | |
| 16 | + ! Draw the panes with buffered rendering | |
| 17 | + write(*, '(a)') "Testing buffered rendering - both panes should show files:" | |
| 18 | + write(*, *) | |
| 19 | + | |
| 20 | + call draw_panes_buffered(parent_dir, current_dir, 2, 1) | |
| 21 | + | |
| 22 | + ! Wait for user input | |
| 23 | + write(*, *) | |
| 24 | + write(*, '(a)') "Press Enter to exit test..." | |
| 25 | + read(*, *) | |
| 26 | + | |
| 27 | + ! Cleanup | |
| 28 | + call cleanup_screen() | |
| 29 | + write(*, '(a)') "Test complete - if you saw files in both panes, buffered rendering works!" | |
| 30 | + | |
| 31 | +end program test_right_pane | |
src/ui/panes.f90modified@@ -81,103 +81,87 @@ contains | ||
| 81 | 81 | character(len=*), intent(in) :: dir_path |
| 82 | 82 | logical, intent(in) :: is_dimmed |
| 83 | 83 | type(file_entry), dimension(MAX_FILES) :: files |
| 84 | - integer :: i, row, j, actual_width | |
| 85 | - character(len=256) :: display_name | |
| 84 | + integer :: i, row, col_pos | |
| 85 | + character(len=256) :: fname | |
| 86 | 86 | |
| 87 | 87 | files = list_directory(dir_path) |
| 88 | 88 | |
| 89 | - ! Ensure width is reasonable | |
| 90 | - actual_width = min(width, 68) ! Limit to reasonable size | |
| 91 | - | |
| 92 | 89 | row = start_row |
| 93 | 90 | do i = 1, min(MAX_FILES, height) |
| 94 | 91 | if (len_trim(files(i)%name) == 0) exit |
| 95 | 92 | if (row > start_row + height - 1) exit |
| 96 | 93 | |
| 94 | + ! Move to position | |
| 97 | 95 | call move_cursor(row, start_col) |
| 98 | 96 | |
| 99 | - ! Format display name | |
| 100 | - display_name = trim(files(i)%name) | |
| 97 | + ! Get filename | |
| 98 | + fname = files(i)%name | |
| 101 | 99 | |
| 102 | - ! Add slash for directories (except . and ..) | |
| 100 | + ! Add slash for directories (not for . and ..) | |
| 103 | 101 | if (files(i)%is_dir) then |
| 104 | - if (trim(display_name) /= "." .and. trim(display_name) /= "..") then | |
| 105 | - display_name = trim(display_name) // "/" | |
| 102 | + if (trim(fname) /= "." .and. trim(fname) /= "..") then | |
| 103 | + fname = trim(fname) // "/" | |
| 106 | 104 | end if |
| 107 | 105 | end if |
| 108 | 106 | |
| 109 | 107 | ! Truncate if too long |
| 110 | - if (len_trim(display_name) > actual_width - 1) then | |
| 111 | - display_name = display_name(1:actual_width-4) // "..." | |
| 108 | + if (len_trim(fname) > width - 2) then | |
| 109 | + fname = fname(1:width-5) // "..." | |
| 112 | 110 | end if |
| 113 | 111 | |
| 114 | - ! Draw the file entry | |
| 112 | + ! Draw the entry with proper colors | |
| 115 | 113 | if (i == selected .and. .not. is_dimmed) then |
| 116 | - ! Active pane selection - highlight bar | |
| 117 | - ! Build the complete line first | |
| 118 | - if (files(i)%is_dir) then | |
| 119 | - write(output_unit, '(a)', advance='no') REVERSE // BLUE // trim(display_name) | |
| 120 | - ! Pad to fill the selection bar | |
| 121 | - do j = len_trim(display_name) + 1, actual_width - 1 | |
| 122 | - write(output_unit, '(a1)', advance='no') ' ' | |
| 123 | - end do | |
| 124 | - write(output_unit, '(a)', advance='no') RESET | |
| 125 | - else | |
| 126 | - write(output_unit, '(a)', advance='no') REVERSE // trim(display_name) | |
| 127 | - ! Pad to fill the selection bar | |
| 128 | - do j = len_trim(display_name) + 1, actual_width - 1 | |
| 129 | - write(output_unit, '(a1)', advance='no') ' ' | |
| 130 | - end do | |
| 131 | - write(output_unit, '(a)', advance='no') RESET | |
| 132 | - end if | |
| 133 | - else if (i == selected .and. is_dimmed) then | |
| 134 | - ! Dimmed pane selection - subtle highlight | |
| 114 | + ! Active selection - highlight whole line | |
| 135 | 115 | if (files(i)%is_dir) then |
| 136 | - write(output_unit, '(a)', advance='no') DIM // BLUE // BOLD // & | |
| 137 | - display_name(1:len_trim(display_name)) // RESET | |
| 116 | + write(output_unit, '(a)', advance='no') REVERSE // BLUE // trim(fname) // RESET | |
| 138 | 117 | else |
| 139 | - write(output_unit, '(a)', advance='no') DIM // BOLD // & | |
| 140 | - display_name(1:len_trim(display_name)) // RESET | |
| 118 | + write(output_unit, '(a)', advance='no') REVERSE // trim(fname) // RESET | |
| 141 | 119 | end if |
| 142 | - ! Clear rest of line | |
| 143 | - do j = len_trim(display_name) + 1, actual_width - 1 | |
| 144 | - write(output_unit, '(a)', advance='no') ' ' | |
| 120 | + ! Pad the rest of the line | |
| 121 | + do col_pos = len_trim(fname) + 1, min(width - 1, 70) | |
| 122 | + write(output_unit, '(a)', advance='no') REVERSE // ' ' // RESET | |
| 145 | 123 | end do |
| 146 | 124 | else |
| 147 | - ! Normal display | |
| 125 | + ! Normal or dimmed entry | |
| 148 | 126 | if (is_dimmed) then |
| 149 | - ! Dimmed pane | |
| 150 | - if (files(i)%is_dir) then | |
| 151 | - write(output_unit, '(a)', advance='no') DIM // BLUE // & | |
| 152 | - display_name(1:len_trim(display_name)) // RESET | |
| 127 | + if (i == selected) then | |
| 128 | + ! Dimmed selection | |
| 129 | + if (files(i)%is_dir) then | |
| 130 | + write(output_unit, '(a)', advance='no') DIM // BOLD // BLUE // trim(fname) // RESET | |
| 131 | + else | |
| 132 | + write(output_unit, '(a)', advance='no') DIM // BOLD // trim(fname) // RESET | |
| 133 | + end if | |
| 153 | 134 | else |
| 154 | - write(output_unit, '(a)', advance='no') DIM // & | |
| 155 | - display_name(1:len_trim(display_name)) // RESET | |
| 135 | + ! Dimmed normal | |
| 136 | + if (files(i)%is_dir) then | |
| 137 | + write(output_unit, '(a)', advance='no') DIM // BLUE // trim(fname) // RESET | |
| 138 | + else | |
| 139 | + write(output_unit, '(a)', advance='no') DIM // trim(fname) // RESET | |
| 140 | + end if | |
| 156 | 141 | end if |
| 157 | 142 | else |
| 158 | - ! Active pane | |
| 143 | + ! Active pane, normal entry | |
| 159 | 144 | if (files(i)%is_dir) then |
| 160 | - write(output_unit, '(a)', advance='no') BLUE // & | |
| 161 | - display_name(1:len_trim(display_name)) // RESET | |
| 145 | + write(output_unit, '(a)', advance='no') BLUE // trim(fname) // RESET | |
| 162 | 146 | else |
| 163 | - write(output_unit, '(a)', advance='no') & | |
| 164 | - display_name(1:len_trim(display_name)) | |
| 147 | + write(output_unit, '(a)', advance='no') trim(fname) | |
| 165 | 148 | end if |
| 166 | 149 | end if |
| 150 | + | |
| 167 | 151 | ! Clear rest of line |
| 168 | - do j = len_trim(display_name) + 1, actual_width - 1 | |
| 169 | - write(output_unit, '(a)', advance='no') ' ' | |
| 152 | + do col_pos = len_trim(fname) + 1, min(width - 1, 70) | |
| 153 | + write(output_unit, '(a1)', advance='no') ' ' | |
| 170 | 154 | end do |
| 171 | 155 | end if |
| 172 | 156 | |
| 173 | 157 | row = row + 1 |
| 174 | 158 | end do |
| 175 | 159 | |
| 176 | - ! Clear any remaining rows in this pane | |
| 160 | + ! Clear any remaining rows | |
| 177 | 161 | do while (row <= start_row + height - 1) |
| 178 | 162 | call move_cursor(row, start_col) |
| 179 | - do j = 1, actual_width - 1 | |
| 180 | - write(output_unit, '(a)', advance='no') ' ' | |
| 163 | + do col_pos = 1, min(width - 1, 70) | |
| 164 | + write(output_unit, '(a1)', advance='no') ' ' | |
| 181 | 165 | end do |
| 182 | 166 | row = row + 1 |
| 183 | 167 | end do |
src/ui/panes_buffered.f90added@@ -0,0 +1,169 @@ | ||
| 1 | +module ui_panes_buffered | |
| 2 | + use iso_fortran_env, only: output_unit, error_unit | |
| 3 | + use terminal_screen, only: move_cursor, get_terminal_size | |
| 4 | + use filesystem_ops, only: file_entry, list_directory, MAX_FILES | |
| 5 | + implicit none | |
| 6 | + private | |
| 7 | + | |
| 8 | + public :: draw_panes_buffered | |
| 9 | + | |
| 10 | + ! ANSI color codes | |
| 11 | + character(len=*), parameter :: ESC = char(27) | |
| 12 | + character(len=*), parameter :: RESET = ESC // "[0m" | |
| 13 | + character(len=*), parameter :: BOLD = ESC // "[1m" | |
| 14 | + character(len=*), parameter :: DIM = ESC // "[2m" | |
| 15 | + character(len=*), parameter :: REVERSE = ESC // "[7m" | |
| 16 | + character(len=*), parameter :: BLUE = ESC // "[34m" | |
| 17 | + | |
| 18 | +contains | |
| 19 | + | |
| 20 | + subroutine draw_panes_buffered(parent_dir, current_dir, selected_index, parent_selected) | |
| 21 | + character(len=*), intent(in) :: parent_dir, current_dir | |
| 22 | + integer, intent(in) :: selected_index | |
| 23 | + integer, intent(in), optional :: parent_selected | |
| 24 | + integer :: rows, cols | |
| 25 | + integer :: left_width, right_width | |
| 26 | + integer :: i | |
| 27 | + integer :: parent_sel | |
| 28 | + ! Handle optional parent selection | |
| 29 | + if (present(parent_selected)) then | |
| 30 | + parent_sel = parent_selected | |
| 31 | + else | |
| 32 | + parent_sel = -1 | |
| 33 | + end if | |
| 34 | + | |
| 35 | + call get_terminal_size(rows, cols) | |
| 36 | + | |
| 37 | + ! Calculate pane widths (30/70 split) | |
| 38 | + left_width = cols * 3 / 10 | |
| 39 | + right_width = cols - left_width - 1 ! -1 for separator | |
| 40 | + | |
| 41 | + ! Draw header | |
| 42 | + call move_cursor(1, 1) | |
| 43 | + write(output_unit, '(a)') BOLD // "FORTRESS" // RESET // " - " // trim(current_dir) | |
| 44 | + | |
| 45 | + ! Draw both panes | |
| 46 | + call draw_both_panes(2, left_width, right_width, rows - 2, & | |
| 47 | + parent_dir, current_dir, parent_sel, selected_index) | |
| 48 | + | |
| 49 | + ! Draw footer | |
| 50 | + call move_cursor(rows, 1) | |
| 51 | + write(output_unit, '(a)') DIM // "↑↓:navigate →:enter ←:back Ctrl-Q:quit" // RESET | |
| 52 | + | |
| 53 | + flush(output_unit) | |
| 54 | + end subroutine draw_panes_buffered | |
| 55 | + | |
| 56 | + subroutine draw_both_panes(start_row, left_width, right_width, height, & | |
| 57 | + parent_dir, current_dir, parent_sel, current_sel) | |
| 58 | + integer, intent(in) :: start_row, left_width, right_width, height | |
| 59 | + character(len=*), intent(in) :: parent_dir, current_dir | |
| 60 | + integer, intent(in) :: parent_sel, current_sel | |
| 61 | + | |
| 62 | + type(file_entry), dimension(MAX_FILES) :: parent_files, current_files | |
| 63 | + integer :: row, i, j, file_count | |
| 64 | + character(len=256) :: left_text, right_text | |
| 65 | + | |
| 66 | + ! Get files for both directories | |
| 67 | + parent_files = list_directory(parent_dir) | |
| 68 | + current_files = list_directory(current_dir) | |
| 69 | + | |
| 70 | + ! Count current files | |
| 71 | + file_count = 0 | |
| 72 | + do i = 1, MAX_FILES | |
| 73 | + if (len_trim(current_files(i)%name) == 0) exit | |
| 74 | + file_count = i | |
| 75 | + end do | |
| 76 | + | |
| 77 | + ! Draw each row | |
| 78 | + do row = start_row, start_row + height - 1 | |
| 79 | + i = row - start_row + 1 | |
| 80 | + | |
| 81 | + ! Build left pane text | |
| 82 | + left_text = "" | |
| 83 | + if (i <= MAX_FILES .and. len_trim(parent_files(i)%name) > 0) then | |
| 84 | + left_text = format_file_entry(parent_files(i), i == parent_sel, .true., left_width) | |
| 85 | + end if | |
| 86 | + | |
| 87 | + ! Build right pane text | |
| 88 | + right_text = "" | |
| 89 | + if (i <= MAX_FILES .and. len_trim(current_files(i)%name) > 0) then | |
| 90 | + right_text = format_file_entry(current_files(i), i == current_sel, .false., right_width) | |
| 91 | + end if | |
| 92 | + | |
| 93 | + ! Build complete line | |
| 94 | + call move_cursor(row, 1) | |
| 95 | + | |
| 96 | + ! Write left pane | |
| 97 | + write(output_unit, '(a)', advance='no') left_text | |
| 98 | + | |
| 99 | + ! Pad to separator | |
| 100 | + do j = len_trim(left_text) + 1, left_width | |
| 101 | + write(output_unit, '(a1)', advance='no') ' ' | |
| 102 | + end do | |
| 103 | + | |
| 104 | + ! Write separator | |
| 105 | + write(output_unit, '(a)', advance='no') DIM // "│" // RESET | |
| 106 | + | |
| 107 | + ! Write right pane | |
| 108 | + write(output_unit, '(a)', advance='no') right_text | |
| 109 | + | |
| 110 | + ! Clear rest of line | |
| 111 | + do j = len_trim(right_text) + 1, right_width | |
| 112 | + write(output_unit, '(a1)', advance='no') ' ' | |
| 113 | + end do | |
| 114 | + end do | |
| 115 | + end subroutine draw_both_panes | |
| 116 | + | |
| 117 | + function format_file_entry(entry, is_selected, is_dimmed, max_width) result(formatted) | |
| 118 | + type(file_entry), intent(in) :: entry | |
| 119 | + logical, intent(in) :: is_selected, is_dimmed | |
| 120 | + integer, intent(in) :: max_width | |
| 121 | + character(len=512) :: formatted | |
| 122 | + character(len=256) :: name | |
| 123 | + | |
| 124 | + | |
| 125 | + name = entry%name | |
| 126 | + if (entry%is_dir .and. trim(name) /= "." .and. trim(name) /= "..") then | |
| 127 | + name = trim(name) // "/" | |
| 128 | + end if | |
| 129 | + | |
| 130 | + ! Truncate if needed | |
| 131 | + if (len_trim(name) > max_width - 1) then | |
| 132 | + name = name(1:max_width-4) // "..." | |
| 133 | + end if | |
| 134 | + | |
| 135 | + ! Format with colors | |
| 136 | + if (is_selected .and. .not. is_dimmed) then | |
| 137 | + ! Active selection | |
| 138 | + if (entry%is_dir) then | |
| 139 | + formatted = REVERSE // BLUE // trim(name) // RESET | |
| 140 | + else | |
| 141 | + formatted = REVERSE // trim(name) // RESET | |
| 142 | + end if | |
| 143 | + else if (is_dimmed) then | |
| 144 | + ! Dimmed pane | |
| 145 | + if (is_selected) then | |
| 146 | + if (entry%is_dir) then | |
| 147 | + formatted = DIM // BOLD // BLUE // trim(name) // RESET | |
| 148 | + else | |
| 149 | + formatted = DIM // BOLD // trim(name) // RESET | |
| 150 | + end if | |
| 151 | + else | |
| 152 | + if (entry%is_dir) then | |
| 153 | + formatted = DIM // BLUE // trim(name) // RESET | |
| 154 | + else | |
| 155 | + formatted = DIM // trim(name) // RESET | |
| 156 | + end if | |
| 157 | + end if | |
| 158 | + else | |
| 159 | + ! Normal active pane | |
| 160 | + if (entry%is_dir) then | |
| 161 | + formatted = BLUE // trim(name) // RESET | |
| 162 | + else | |
| 163 | + formatted = trim(name) | |
| 164 | + end if | |
| 165 | + end if | |
| 166 | + | |
| 167 | + end function format_file_entry | |
| 168 | + | |
| 169 | +end module ui_panes_buffered | |
src/ui/panes_debug.f90added@@ -0,0 +1,88 @@ | ||
| 1 | +module ui_panes_debug | |
| 2 | + use iso_fortran_env, only: output_unit, error_unit | |
| 3 | + use terminal_screen, only: move_cursor, get_terminal_size | |
| 4 | + use filesystem_ops, only: file_entry, list_directory, MAX_FILES | |
| 5 | + implicit none | |
| 6 | + private | |
| 7 | + | |
| 8 | + public :: draw_file_list_debug | |
| 9 | + | |
| 10 | + ! ANSI color codes | |
| 11 | + character(len=*), parameter :: ESC = char(27) | |
| 12 | + character(len=*), parameter :: RESET = ESC // "[0m" | |
| 13 | + character(len=*), parameter :: BLUE = ESC // "[34m" | |
| 14 | + character(len=*), parameter :: REVERSE = ESC // "[7m" | |
| 15 | + character(len=*), parameter :: DIM = ESC // "[2m" | |
| 16 | + character(len=*), parameter :: BOLD = ESC // "[1m" | |
| 17 | + | |
| 18 | +contains | |
| 19 | + | |
| 20 | + subroutine draw_file_list_debug(start_row, start_col, width, height, dir_path, selected, is_dimmed) | |
| 21 | + integer, intent(in) :: start_row, start_col, width, height, selected | |
| 22 | + character(len=*), intent(in) :: dir_path | |
| 23 | + logical, intent(in) :: is_dimmed | |
| 24 | + type(file_entry), dimension(MAX_FILES) :: files | |
| 25 | + integer :: i, row, col_pos, file_count | |
| 26 | + character(len=256) :: fname | |
| 27 | + | |
| 28 | + ! Debug info | |
| 29 | + write(error_unit, *) "=== draw_file_list_debug ===" | |
| 30 | + write(error_unit, *) "start_row=", start_row, " start_col=", start_col | |
| 31 | + write(error_unit, *) "width=", width, " height=", height | |
| 32 | + write(error_unit, *) "dir_path='", trim(dir_path), "'" | |
| 33 | + write(error_unit, *) "selected=", selected, " is_dimmed=", is_dimmed | |
| 34 | + | |
| 35 | + files = list_directory(dir_path) | |
| 36 | + | |
| 37 | + ! Count files | |
| 38 | + file_count = 0 | |
| 39 | + do i = 1, MAX_FILES | |
| 40 | + if (len_trim(files(i)%name) == 0) exit | |
| 41 | + file_count = i | |
| 42 | + end do | |
| 43 | + write(error_unit, *) "Files found: ", file_count | |
| 44 | + | |
| 45 | + row = start_row | |
| 46 | + do i = 1, min(MAX_FILES, height) | |
| 47 | + if (len_trim(files(i)%name) == 0) exit | |
| 48 | + if (row > start_row + height - 1) exit | |
| 49 | + | |
| 50 | + ! Move to position | |
| 51 | + call move_cursor(row, start_col) | |
| 52 | + | |
| 53 | + ! Get filename | |
| 54 | + fname = files(i)%name | |
| 55 | + | |
| 56 | + write(error_unit, '(a,i0,a,a,a,l1)') "File ", i, ": '", trim(fname), "' is_dir=", files(i)%is_dir | |
| 57 | + | |
| 58 | + ! Add slash for directories (not for . and ..) | |
| 59 | + if (files(i)%is_dir) then | |
| 60 | + if (trim(fname) /= "." .and. trim(fname) /= "..") then | |
| 61 | + fname = trim(fname) // "/" | |
| 62 | + end if | |
| 63 | + end if | |
| 64 | + | |
| 65 | + ! Truncate if too long | |
| 66 | + if (len_trim(fname) > width - 2) then | |
| 67 | + fname = fname(1:width-5) // "..." | |
| 68 | + end if | |
| 69 | + | |
| 70 | + write(error_unit, '(a,i0,a,i0,a,a)') "Writing at row ", row, " col ", start_col, ": ", trim(fname) | |
| 71 | + | |
| 72 | + ! Simple write for debugging | |
| 73 | + if (i == selected .and. .not. is_dimmed) then | |
| 74 | + write(output_unit, '(a)', advance='no') REVERSE // trim(fname) // RESET | |
| 75 | + else if (files(i)%is_dir) then | |
| 76 | + write(output_unit, '(a)', advance='no') BLUE // trim(fname) // RESET | |
| 77 | + else | |
| 78 | + write(output_unit, '(a)', advance='no') trim(fname) | |
| 79 | + end if | |
| 80 | + | |
| 81 | + row = row + 1 | |
| 82 | + end do | |
| 83 | + | |
| 84 | + write(error_unit, *) "=== end draw_file_list_debug ===" | |
| 85 | + flush(error_unit) | |
| 86 | + end subroutine draw_file_list_debug | |
| 87 | + | |
| 88 | +end module ui_panes_debug | |