fortrangoingonforty/fortress / 8f78bae

Browse files

fix tmux top row cut off with more explicti handling

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
8f78baed41d4f9f54f5e28c7c7349e56dbb1c570
Parents
8b792d5
Tree
c124420

2 changed files

StatusFile+-
M app/main.f90 228 7
M src/terminal/term_control.f90 4 1
app/main.f90modified
@@ -59,6 +59,11 @@ program fortress
5959
     integer :: favorite_count = 0
6060
     logical, dimension(MAX_FILES) :: current_is_favorite, parent_is_favorite
6161
 
62
+    ! Rename mode state
63
+    logical :: in_rename_mode = .false.
64
+    character(len=MAX_PATH) :: rename_buffer = ''
65
+    integer :: rename_cursor_pos = 0
66
+
6267
     character(len=1) :: key
6368
     integer :: i, rows, cols, visible_height
6469
     logical :: is_shift_pressed, is_alt_pressed
@@ -175,12 +180,95 @@ program fortress
175180
                            has_clipboard, clipboard_is_cut, clipboard_source_name, clipboard_count, &
176181
                            is_selected, selection_count, &
177182
                            current_is_favorite, parent_is_favorite, &
178
-                           search_buffer, search_length)
183
+                           search_buffer, search_length, &
184
+                           in_rename_mode, rename_buffer, rename_cursor_pos)
179185
 
180186
         ! Get input (with error handling for End-of-record after Enter key)
181187
         read(*, '(a1)', advance='no', iostat=i) key
182
-        ! Only cycle on End-of-record (negative iostat), which happens after pressing Enter
183
-        ! Don't skip on positive errors or when we successfully read a character
188
+
189
+        ! Rename mode key handling - intercept ALL keys when in rename mode (BEFORE error handling)
190
+        if (in_rename_mode) then
191
+            ! In rename mode, handle read errors differently
192
+            if (i < 0) then
193
+                ! End-of-record in rename mode - treat as Enter
194
+                key = achar(13)
195
+            else if (i > 0) then
196
+                ! Read error - ignore and continue
197
+                cycle
198
+            end if
199
+
200
+            ! Handle ESC to cancel rename
201
+            if (ichar(key) == 27) then
202
+                in_rename_mode = .false.
203
+                rename_buffer = ''
204
+                rename_cursor_pos = 0
205
+                cycle
206
+            ! Handle Enter to confirm rename
207
+            else if (ichar(key) == 10 .or. ichar(key) == 13) then
208
+                ! Execute rename if name changed
209
+                if (len_trim(rename_buffer) > 0 .and. trim(rename_buffer) /= trim(current_files(selected))) then
210
+                    block
211
+                        character(len=MAX_PATH) :: old_path, new_path, old_name
212
+                        character(len=MAX_PATH*2) :: mv_cmd
213
+                        integer :: stat
214
+
215
+                        ! Store the old name for cursor tracking
216
+                        old_name = current_files(selected)
217
+                        old_path = join_path(current_dir, old_name)
218
+                        new_path = join_path(current_dir, trim(rename_buffer))
219
+
220
+                        ! Use -f flag for case-only renames, double quotes for paths
221
+                        mv_cmd = 'mv -f "' // trim(old_path) // '" "' // trim(new_path) // '"'
222
+                        call execute_command_line(trim(mv_cmd), exitstat=stat, wait=.true.)
223
+
224
+                        ! After rename succeeds, find the renamed file in the refreshed list
225
+                        if (stat == 0) then
226
+                            temp_dir = new_path
227
+                            selected = -2  ! Signal to find this file in the next iteration
228
+                        end if
229
+                    end block
230
+                end if
231
+                in_rename_mode = .false.
232
+                rename_buffer = ''
233
+                rename_cursor_pos = 0
234
+                cycle
235
+            ! Handle Backspace
236
+            else if (ichar(key) == 127 .or. ichar(key) == 8) then
237
+                if (rename_cursor_pos > 0) then
238
+                    if (rename_cursor_pos == len_trim(rename_buffer)) then
239
+                        ! Cursor at end - simple delete
240
+                        rename_buffer = rename_buffer(1:len_trim(rename_buffer)-1)
241
+                    else
242
+                        ! Cursor in middle - delete and shift left
243
+                        rename_buffer = rename_buffer(1:rename_cursor_pos-1) // &
244
+                                       rename_buffer(rename_cursor_pos+1:len_trim(rename_buffer))
245
+                    end if
246
+                    rename_cursor_pos = rename_cursor_pos - 1
247
+                end if
248
+                cycle
249
+            ! Handle printable characters - insert at cursor position
250
+            else if ((ichar(key) >= ichar('a') .and. ichar(key) <= ichar('z')) .or. &
251
+                     (ichar(key) >= ichar('A') .and. ichar(key) <= ichar('Z')) .or. &
252
+                     (ichar(key) >= ichar('0') .and. ichar(key) <= ichar('9')) .or. &
253
+                     key == '_' .or. key == '-' .or. key == '.' .or. key == ' ') then
254
+                if (len_trim(rename_buffer) < MAX_PATH - 1) then
255
+                    if (rename_cursor_pos == len_trim(rename_buffer)) then
256
+                        ! Cursor at end - simple append
257
+                        rename_buffer = trim(rename_buffer) // key
258
+                    else
259
+                        ! Cursor in middle - insert and shift right
260
+                        rename_buffer = rename_buffer(1:rename_cursor_pos) // key // &
261
+                                       rename_buffer(rename_cursor_pos+1:len_trim(rename_buffer))
262
+                    end if
263
+                    rename_cursor_pos = rename_cursor_pos + 1
264
+                end if
265
+                cycle
266
+            end if
267
+            ! Ignore all other keys in rename mode
268
+            cycle
269
+        end if
270
+
271
+        ! Handle read errors for normal mode
184272
         if (i < 0) cycle  ! End-of-record - skip and try again
185273
         if (i > 0) cycle  ! Other read errors - skip and try again
186274
 
@@ -236,8 +324,138 @@ program fortress
236324
 
237325
             call read_key_with_modifiers(key, is_shift_pressed, is_alt_pressed)
238326
 
239
-            ! Handle Alt+key combinations - skip arrow processing and handle below
240
-            if (.not. is_alt_pressed) then
327
+            ! Handle Alt+key combinations
328
+            if (is_alt_pressed) then
329
+                ! Process Alt keys here (key is now encoded as achar(1-26))
330
+                select case(ichar(key))
331
+                case(7)  ! Alt+g - toggle git mode
332
+                    if (in_git_repo) then
333
+                        if (mode == 'normal') then
334
+                            mode = 'git'
335
+                        else
336
+                            mode = 'normal'
337
+                        end if
338
+                    end if
339
+                case(14)  ! Alt+n - enter rename mode
340
+                    if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
341
+                        in_rename_mode = .true.
342
+                        rename_buffer = current_files(selected)
343
+                        rename_cursor_pos = len_trim(rename_buffer)
344
+                    end if
345
+                case(22)  ! Alt+v - view file
346
+                    if (.not. current_is_dir(selected)) then
347
+                        if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
348
+                            call open_file_in_default_app(join_path(current_dir, current_files(selected)))
349
+                        end if
350
+                    end if
351
+                case(19)  ! Alt+s - fzf search
352
+                    call fzf_search(current_dir, temp_dir)
353
+                    if (len_trim(temp_dir) > 0) then
354
+                        parent_dir = get_parent_path(temp_dir)
355
+                        current_dir = parent_dir
356
+                        parent_dir = get_parent_path(current_dir)
357
+                        selected = -2
358
+                        call detect_git_repo(current_dir, in_git_repo, repo_name, branch_name)
359
+                    end if
360
+                case(3)  ! Alt+c - cd on exit
361
+                    if (current_is_dir(selected)) then
362
+                        if (trim(current_files(selected)) == "..") then
363
+                            exit_dir = parent_dir
364
+                        else if (trim(current_files(selected)) == ".") then
365
+                            exit_dir = current_dir
366
+                        else
367
+                            exit_dir = join_path(current_dir, current_files(selected))
368
+                        end if
369
+                        cd_on_exit = .true.
370
+                        running = .false.
371
+                    end if
372
+                case(18)  ! Alt+r - delete
373
+                    if (selection_count > 0) then
374
+                        call delete_multi_with_confirmation(current_dir, current_files, current_is_dir, &
375
+                                                           is_selected, selection_count, current_count)
376
+                        call clear_all_selections(is_selected, selection_count, in_selection_mode)
377
+                    else if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
378
+                        call delete_with_confirmation(current_dir, current_files(selected), current_is_dir(selected))
379
+                    end if
380
+                case(13)  ! Alt+m - move mode
381
+                    if (move_mode) then
382
+                        call execute_move_file(move_source_path, current_dir, current_files(move_dest_selected), &
383
+                                              current_is_dir(move_dest_selected))
384
+                        move_mode = .false.
385
+                    else if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
386
+                        move_source_path = join_path(current_dir, current_files(selected))
387
+                        move_source_name = current_files(selected)
388
+                        move_mode = .true.
389
+                        move_dest_selected = find_first_directory(current_files, current_is_dir, current_count)
390
+                    end if
391
+                case(25)  ! Alt+y - copy
392
+                    if (selection_count > 0) then
393
+                        clipboard_count = 0
394
+                        do i = 1, current_count
395
+                            if (is_selected(i) .and. trim(current_files(i)) /= "." .and. &
396
+                                trim(current_files(i)) /= "..") then
397
+                                clipboard_count = clipboard_count + 1
398
+                                clipboard_paths(clipboard_count) = join_path(current_dir, current_files(i))
399
+                                clipboard_names(clipboard_count) = current_files(i)
400
+                            end if
401
+                        end do
402
+                        clipboard_is_cut = .false.
403
+                        has_clipboard = .true.
404
+                        call clear_all_selections(is_selected, selection_count, in_selection_mode)
405
+                    else if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
406
+                        clipboard_count = 1
407
+                        clipboard_paths(1) = join_path(current_dir, current_files(selected))
408
+                        clipboard_names(1) = current_files(selected)
409
+                        clipboard_source_path = clipboard_paths(1)
410
+                        clipboard_source_name = clipboard_names(1)
411
+                        clipboard_is_cut = .false.
412
+                        has_clipboard = .true.
413
+                    end if
414
+                case(24)  ! Alt+x - cut
415
+                    if (selection_count > 0) then
416
+                        clipboard_count = 0
417
+                        do i = 1, current_count
418
+                            if (is_selected(i) .and. trim(current_files(i)) /= "." .and. &
419
+                                trim(current_files(i)) /= "..") then
420
+                                clipboard_count = clipboard_count + 1
421
+                                clipboard_paths(clipboard_count) = join_path(current_dir, current_files(i))
422
+                                clipboard_names(clipboard_count) = current_files(i)
423
+                            end if
424
+                        end do
425
+                        clipboard_is_cut = .true.
426
+                        has_clipboard = .true.
427
+                        call clear_all_selections(is_selected, selection_count, in_selection_mode)
428
+                    else if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
429
+                        clipboard_count = 1
430
+                        clipboard_paths(1) = join_path(current_dir, current_files(selected))
431
+                        clipboard_names(1) = current_files(selected)
432
+                        clipboard_source_path = clipboard_paths(1)
433
+                        clipboard_source_name = clipboard_names(1)
434
+                        clipboard_is_cut = .true.
435
+                        has_clipboard = .true.
436
+                    end if
437
+                case(16)  ! Alt+p - paste
438
+                    if (has_clipboard) then
439
+                        if (clipboard_count > 1) then
440
+                            call execute_multi_paste(clipboard_paths, clipboard_names, clipboard_count, &
441
+                                                    clipboard_is_cut, current_dir, current_files(selected), &
442
+                                                    current_is_dir(selected))
443
+                        else
444
+                            call execute_paste(clipboard_paths(1), clipboard_is_cut, current_dir, &
445
+                                              current_files(selected), current_is_dir(selected))
446
+                        end if
447
+                        if (clipboard_is_cut) then
448
+                            has_clipboard = .false.
449
+                            clipboard_count = 0
450
+                        end if
451
+                    end if
452
+                end select
453
+                ! Alt key processed, skip rest of case(27)
454
+                cycle
455
+            end if
456
+
457
+            ! Handle arrow keys (only if not Alt key)
458
+            if (.true.) then
241459
                 ! Process arrow keys normally
242460
                 if (move_mode) then
243461
                 ! In move mode, navigate directories only
@@ -346,9 +564,12 @@ program fortress
346564
                     mode = 'normal'
347565
                 end if
348566
             end if
349
-        case(14)  ! Alt+n - rename file/directory
567
+        case(14)  ! Alt+n - enter rename mode
350568
             if (trim(current_files(selected)) /= "." .and. trim(current_files(selected)) /= "..") then
351
-                call rename_file_prompt(current_dir, current_files(selected))
569
+                ! Enter rename mode - pre-fill with current filename
570
+                in_rename_mode = .true.
571
+                rename_buffer = current_files(selected)
572
+                rename_cursor_pos = len_trim(rename_buffer)
352573
             end if
353574
         case(22)  ! Alt+v - view file (always available)
354575
             if (.not. current_is_dir(selected)) then
src/terminal/term_control.f90modified
@@ -13,7 +13,7 @@ module terminal_control
1313
 
1414
     ! ANSI escape codes
1515
     character(len=*), parameter :: ESC = char(27)
16
-    character(len=*), parameter :: CLEAR = ESC // "[H" // ESC // "[J"  ! Move to home, clear to end
16
+    character(len=*), parameter :: CLEAR = ESC // "[1;1H" // ESC // "[2J"  ! Explicit position (1,1) + full clear
1717
     character(len=*), parameter :: ALT_SCREEN_ON = ESC // "[?1049h"    ! Enable alt screen buffer
1818
     character(len=*), parameter :: ALT_SCREEN_OFF = ESC // "[?1049l"   ! Disable alt screen buffer
1919
     character(len=*), parameter :: BOLD = ESC // "[1m"
@@ -92,6 +92,9 @@ contains
9292
     subroutine setup_raw_mode()
9393
         ! Enable alternative screen buffer (prevents scrollback pollution and flashing)
9494
         write(output_unit, '(a)', advance='no') ALT_SCREEN_ON
95
+        ! Immediately clear and position cursor to (1,1) for consistency across terminals
96
+        write(output_unit, '(a)', advance='no') ESC // "[2J" // ESC // "[1;1H"
97
+        call flush(output_unit)
9598
         ! Blocking mode for stable operation
9699
         call execute_command_line("stty -icanon -echo min 1 time 0 2>/dev/null", wait=.true.)
97100
     end subroutine setup_raw_mode