fortrangoingonforty/fortress / e7598cc

Browse files

clean this up jesus

Authored by espadonne
SHA
e7598ccb53918c03fd8a3d092ea8ec67d6e7c524
Parents
07219b0
Tree
0b81b1c

7 changed files

StatusFile+-
A .gitignore 1 0
A run.sh 34 0
D src/fortress_clean.f90 0 278
D src/fortress_v2.f90 0 289
M src/main.f90 247 114
D src/simple_main.f90 0 119
D src/test_right_pane.f90 0 31
.gitignoreadded
@@ -0,0 +1,1 @@
1
+tests/
run.shadded
@@ -0,0 +1,34 @@
1
+#!/bin/bash
2
+
3
+echo "========================================"
4
+echo "         FORTRESS File Manager          "
5
+echo "        Written in Modern Fortran       "
6
+echo "========================================"
7
+echo
8
+echo "Features:"
9
+echo "  • Dual-pane interface (parent | current)"
10
+echo "  • Full keyboard navigation"
11
+echo "  • Directory traversal"
12
+echo "  • Clean, responsive display"
13
+echo
14
+echo "Controls:"
15
+echo "  ↑/↓ or j/k : Navigate files"
16
+echo "  → or l     : Enter directory"
17
+echo "  ← or h     : Go to parent"
18
+echo "  q          : Quit"
19
+echo
20
+echo "Building FORTRESS..."
21
+fpm build --flag "-O2" 2>&1 | grep -E "(done|error|Error|ERROR)" || true
22
+
23
+# Find the built executable
24
+FORTRESS_BIN=$(find ./build -name fortress -type f -path "*/app/*" 2>/dev/null | head -1)
25
+
26
+if [ -n "$FORTRESS_BIN" ] && [ -f "$FORTRESS_BIN" ]; then
27
+    echo
28
+    echo "Starting FORTRESS..."
29
+    echo "===================="
30
+    $FORTRESS_BIN
31
+else
32
+    echo "Build failed. Please run 'fpm build' to see full error messages."
33
+    exit 1
34
+fi
src/fortress_clean.f90deleted
@@ -1,278 +0,0 @@
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.f90deleted
@@ -1,289 +0,0 @@
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
@@ -1,145 +1,278 @@
1
-program fortress_main
1
+program fortress_clean
22
     use iso_fortran_env, only: output_unit, error_unit
3
-    use terminal_screen, only: init_screen, cleanup_screen, clear_screen
4
-    use terminal_input, only: get_key, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_ENTER, KEY_QUIT
5
-    use filesystem_ops, only: list_directory, get_parent_dir, get_current_dir, is_directory, &
6
-                              file_entry, MAX_FILES
7
-    use ui_panes_buffered, only: draw_panes_buffered
8
-
93
     implicit none
104
 
11
-    logical :: running
12
-    logical :: needs_full_redraw
13
-    integer :: key
14
-    character(len=256) :: current_dir
15
-    character(len=256) :: parent_dir
16
-    character(len=256) :: new_dir
17
-    integer :: selected_index
18
-    integer :: parent_selected_index
19
-    type(file_entry), dimension(MAX_FILES) :: current_files, parent_files
20
-    integer :: file_count, parent_file_count, i
21
-    character(len=256) :: base_name
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"
2214
 
23
-    ! Initialize
24
-    running = .true.
25
-    needs_full_redraw = .true.
26
-    selected_index = 1
27
-    parent_selected_index = 1
28
-    current_dir = get_current_dir()
29
-    parent_dir = get_parent_dir(current_dir)
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
3025
 
31
-    ! Set up terminal
32
-    call init_screen()
26
+    ! Initialize
27
+    current_dir = get_pwd()
28
+    parent_dir = get_parent_path(current_dir)
3329
 
34
-    ! Initial clear screen
35
-    call clear_screen()
30
+    ! Setup terminal
31
+    call execute_command_line("stty -icanon -echo min 1 time 0 2>/dev/null")
3632
 
3733
     ! Main loop
3834
     do while (running)
39
-        ! Get current directory contents
40
-        current_files = list_directory(current_dir)
41
-        parent_files = list_directory(parent_dir)
42
-
43
-        ! Count actual files
44
-        file_count = 0
45
-        do i = 1, MAX_FILES
46
-            if (len_trim(current_files(i)%name) == 0) exit
47
-            file_count = i
48
-        end do
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
49141
 
50
-        parent_file_count = 0
51
-        do i = 1, MAX_FILES
52
-            if (len_trim(parent_files(i)%name) == 0) exit
53
-            parent_file_count = i
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
54151
         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
55162
 
56
-        ! Find current directory in parent listing for highlighting
57
-        call get_basename(current_dir, base_name)
58
-        parent_selected_index = 1
59
-        do i = 1, parent_file_count
60
-            if (trim(parent_files(i)%name) == trim(base_name)) then
61
-                parent_selected_index = i
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
62178
                 exit
63179
             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)
64184
         end do
65185
 
66
-        ! Only clear screen if needed (reduces flashing)
67
-        if (needs_full_redraw) then
68
-            call clear_screen()
69
-            needs_full_redraw = .false.
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
70201
         end if
71202
 
72
-        call draw_panes_buffered(parent_dir, current_dir, selected_index, parent_selected_index)
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
73211
 
74
-        key = get_key()
212
+        call execute_command_line("rm -f .fortress_size 2>/dev/null")
213
+    end subroutine get_term_size
75214
 
76
-        select case(key)
77
-        case(KEY_UP)
78
-            if (selected_index > 1) then
79
-                selected_index = selected_index - 1
80
-            end if
81
-        case(KEY_DOWN)
82
-            if (selected_index < file_count) then
83
-                selected_index = selected_index + 1
84
-            end if
85
-        case(KEY_LEFT)
86
-            ! Go to parent directory
87
-            if (trim(current_dir) /= "/") then
88
-                current_dir = parent_dir
89
-                parent_dir = get_parent_dir(current_dir)
90
-                selected_index = parent_selected_index
91
-                needs_full_redraw = .true.
92
-            end if
93
-        case(KEY_RIGHT, KEY_ENTER)
94
-            ! Enter directory or open file
95
-            if (selected_index <= file_count) then
96
-                if (current_files(selected_index)%is_dir) then
97
-                    ! Navigate into directory
98
-                    if (trim(current_files(selected_index)%name) == "..") then
99
-                        ! Same as pressing left arrow
100
-                        if (trim(current_dir) /= "/") then
101
-                            current_dir = parent_dir
102
-                            parent_dir = get_parent_dir(current_dir)
103
-                            selected_index = parent_selected_index
104
-                            needs_full_redraw = .true.
105
-                        end if
106
-                    else if (trim(current_files(selected_index)%name) /= ".") then
107
-                        ! Enter subdirectory
108
-                        new_dir = trim(current_dir) // "/" // trim(current_files(selected_index)%name)
109
-                        if (is_directory(new_dir)) then
110
-                            parent_dir = current_dir
111
-                            current_dir = new_dir
112
-                            selected_index = 1
113
-                            needs_full_redraw = .true.
114
-                        end if
115
-                    end if
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
116235
                 else
117
-                    ! Open file - TODO: implement file opening with $EDITOR
236
+                    write(output_unit, '(a)', advance='no') DIM // fname(1:min(len_trim(fname),left_w)) // RESET
118237
                 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)
119241
             end if
120
-        case(KEY_QUIT)
121
-            running = .false.
122
-        end select
123
-    end do
124242
 
125
-    ! Cleanup
126
-    call cleanup_screen()
243
+            ! Separator
244
+            write(output_unit, '(a)', advance='no') " │ "
127245
 
128
-    write(output_unit, *) "Thanks for using FORTRESS!"
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
129261
 
130
-contains
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
131265
 
132
-    subroutine get_basename(path, basename)
133
-        character(len=*), intent(in) :: path
134
-        character(len=*), intent(out) :: basename
135
-        integer :: last_slash
266
+    subroutine read_arrow_key(k)
267
+        character(len=1), intent(out) :: k
268
+        character(len=1) :: ch
136269
 
137
-        last_slash = index(path, '/', back=.true.)
138
-        if (last_slash > 0 .and. last_slash < len_trim(path)) then
139
-            basename = path(last_slash+1:)
270
+        read(*, '(a1)', advance='no') ch
271
+        if (ch == '[') then
272
+            read(*, '(a1)', advance='no') k
140273
         else
141
-            basename = path
274
+            k = ch
142275
         end if
143
-    end subroutine get_basename
276
+    end subroutine read_arrow_key
144277
 
145
-end program fortress_main
278
+end program fortress_clean
src/simple_main.f90deleted
@@ -1,119 +0,0 @@
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.f90deleted
@@ -1,31 +0,0 @@
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