change approach to cursor selection to give consistent cross-platform experience
- SHA
e13b7ce059bf1cc0ef0ff0ae60321685d666fd1f- Parents
-
6f6093e - Tree
12697be
e13b7ce
e13b7ce059bf1cc0ef0ff0ae60321685d666fd1f6f6093e
12697be| Status | File | + | - |
|---|---|---|---|
| M |
app/main.f90
|
5 | 0 |
| M |
src/git/git_ops.f90
|
6 | 5 |
| M |
src/terminal/term_control.f90
|
6 | 2 |
| M |
src/ui/display.f90
|
12 | 14 |
app/main.f90modified@@ -55,6 +55,11 @@ program fortress | ||
| 55 | 55 | call detect_git_repo(current_dir, in_git_repo, repo_name, branch_name) |
| 56 | 56 | call setup_raw_mode() |
| 57 | 57 | |
| 58 | + ! Initialize selection array to false | |
| 59 | + do i = 1, MAX_FILES | |
| 60 | + is_selected(i) = .false. | |
| 61 | + end do | |
| 62 | + | |
| 58 | 63 | ! Main loop |
| 59 | 64 | do while (running) |
| 60 | 65 | ! Get files |
src/git/git_ops.f90modified@@ -239,31 +239,32 @@ contains | ||
| 239 | 239 | subroutine write_git_indicators(staged, unstaged, untracked, has_incoming, highlighted) |
| 240 | 240 | logical, intent(in) :: staged, unstaged, untracked, has_incoming, highlighted |
| 241 | 241 | |
| 242 | - ! Write indicators without RESET (caller handles that) | |
| 242 | + ! When highlighted (cursor), don't change colors - just write symbols | |
| 243 | + ! When not highlighted, use colors with immediate RESET | |
| 243 | 244 | if (staged) then |
| 244 | 245 | if (highlighted) then |
| 245 | - write(output_unit, '(a)', advance='no') GREEN // " ↑" | |
| 246 | + write(output_unit, '(a)', advance='no') " ↑" | |
| 246 | 247 | else |
| 247 | 248 | write(output_unit, '(a)', advance='no') GREEN // " ↑" // RESET |
| 248 | 249 | end if |
| 249 | 250 | end if |
| 250 | 251 | if (unstaged) then |
| 251 | 252 | if (highlighted) then |
| 252 | - write(output_unit, '(a)', advance='no') RED // " ✗" | |
| 253 | + write(output_unit, '(a)', advance='no') " ✗" | |
| 253 | 254 | else |
| 254 | 255 | write(output_unit, '(a)', advance='no') RED // " ✗" // RESET |
| 255 | 256 | end if |
| 256 | 257 | end if |
| 257 | 258 | if (untracked) then |
| 258 | 259 | if (highlighted) then |
| 259 | - write(output_unit, '(a)', advance='no') GREY // " ✗" | |
| 260 | + write(output_unit, '(a)', advance='no') " ✗" | |
| 260 | 261 | else |
| 261 | 262 | write(output_unit, '(a)', advance='no') GREY // " ✗" // RESET |
| 262 | 263 | end if |
| 263 | 264 | end if |
| 264 | 265 | if (has_incoming) then |
| 265 | 266 | if (highlighted) then |
| 266 | - write(output_unit, '(a)', advance='no') YELLOW // " ↓" | |
| 267 | + write(output_unit, '(a)', advance='no') " ↓" | |
| 267 | 268 | else |
| 268 | 269 | write(output_unit, '(a)', advance='no') YELLOW // " ↓" // RESET |
| 269 | 270 | end if |
src/terminal/term_control.f90modified@@ -4,8 +4,8 @@ module terminal_control | ||
| 4 | 4 | private |
| 5 | 5 | |
| 6 | 6 | public :: get_term_size, setup_raw_mode, restore_terminal, read_arrow_key, read_arrow_key_with_shift |
| 7 | - public :: ESC, CLEAR, BOLD, DIM, REVERSE, RESET | |
| 8 | - public :: BLUE, GREEN, RED, GREY, WHITE, YELLOW | |
| 7 | + public :: ESC, CLEAR, BOLD, DIM, REVERSE, NOREVERSE, RESET, UNDERLINE | |
| 8 | + public :: BLUE, GREEN, RED, GREY, WHITE, YELLOW, BG_WHITE, BLACK | |
| 9 | 9 | public :: invalidate_term_cache |
| 10 | 10 | |
| 11 | 11 | ! ANSI escape codes |
@@ -13,7 +13,9 @@ module terminal_control | ||
| 13 | 13 | character(len=*), parameter :: CLEAR = ESC // "[2J" // ESC // "[H" |
| 14 | 14 | character(len=*), parameter :: BOLD = ESC // "[1m" |
| 15 | 15 | character(len=*), parameter :: DIM = ESC // "[2m" |
| 16 | + character(len=*), parameter :: UNDERLINE = ESC // "[4m" | |
| 16 | 17 | character(len=*), parameter :: REVERSE = ESC // "[7m" |
| 18 | + character(len=*), parameter :: NOREVERSE = ESC // "[27m" ! Explicitly turn off reverse video | |
| 17 | 19 | character(len=*), parameter :: RESET = ESC // "[0m" |
| 18 | 20 | character(len=*), parameter :: BLUE = ESC // "[34m" |
| 19 | 21 | character(len=*), parameter :: GREEN = ESC // "[32m" |
@@ -21,6 +23,8 @@ module terminal_control | ||
| 21 | 23 | character(len=*), parameter :: GREY = ESC // "[90m" |
| 22 | 24 | character(len=*), parameter :: WHITE = ESC // "[37m" |
| 23 | 25 | character(len=*), parameter :: YELLOW = ESC // "[33m" |
| 26 | + character(len=*), parameter :: BLACK = ESC // "[30m" | |
| 27 | + character(len=*), parameter :: BG_WHITE = ESC // "[47m" ! White background | |
| 24 | 28 | |
| 25 | 29 | ! Terminal size cache |
| 26 | 30 | integer, save :: cached_rows = 0 |
src/ui/display.f90modified@@ -1,6 +1,7 @@ | ||
| 1 | 1 | module ui_display |
| 2 | 2 | use iso_fortran_env, only: output_unit |
| 3 | - use terminal_control | |
| 3 | + use terminal_control, only: DIM, BOLD, RESET, UNDERLINE, & | |
| 4 | + BLUE, GREEN, RED, GREY, WHITE, YELLOW | |
| 4 | 5 | use git_ops, only: write_git_indicators |
| 5 | 6 | use filesystem_ops, only: MAX_PATH, MAX_FILES |
| 6 | 7 | implicit none |
@@ -100,11 +101,15 @@ contains | ||
| 100 | 101 | write(output_unit, '(a)', advance='no') repeat(" ", left_w) |
| 101 | 102 | end if |
| 102 | 103 | |
| 104 | + ! RESET before separator to clear any state from parent pane | |
| 105 | + write(output_unit, '(a)', advance='no') RESET | |
| 106 | + | |
| 103 | 107 | ! Separator |
| 104 | 108 | write(output_unit, '(a)', advance='no') " │ " |
| 105 | 109 | |
| 106 | 110 | ! Current pane |
| 107 | 111 | if (current_idx >= 1 .and. current_idx <= current_count) then |
| 112 | + | |
| 108 | 113 | fname = current_files(current_idx) |
| 109 | 114 | if (current_is_dir(current_idx) .and. fname /= "." .and. fname /= "..") then |
| 110 | 115 | fname = trim(fname) // "/" |
@@ -132,8 +137,8 @@ contains | ||
| 132 | 137 | write(output_unit, '(a)', advance='no') RED // BOLD // trim(fname) // RESET |
| 133 | 138 | write(output_unit, '(a)') "" |
| 134 | 139 | else if (move_mode .and. current_idx == move_dest_selected) then |
| 135 | - ! Destination cursor - show with white background | |
| 136 | - write(output_unit, '(a)', advance='no') REVERSE // WHITE // trim(fname) | |
| 140 | + ! Destination cursor - show with bold+underline | |
| 141 | + write(output_unit, '(a)', advance='no') BOLD // UNDERLINE // WHITE // trim(fname) | |
| 137 | 142 | if (in_git_repo) then |
| 138 | 143 | call write_git_indicators(current_is_staged(current_idx), & |
| 139 | 144 | current_is_unstaged(current_idx), & |
@@ -142,15 +147,8 @@ contains | ||
| 142 | 147 | end if |
| 143 | 148 | write(output_unit, '(a)') RESET |
| 144 | 149 | else if (current_idx == selected .and. .not. move_mode) then |
| 145 | - ! Normal selection cursor (not in move mode) | |
| 146 | - ! If file is cut, show selected with red background instead of default color | |
| 147 | - ! Only highlight for single-item cuts (multi-cuts shown in header) | |
| 148 | - if (has_clipboard .and. clipboard_is_cut .and. clipboard_count == 1 .and. & | |
| 149 | - trim(current_files(current_idx)) == trim(clipboard_source_name)) then | |
| 150 | - write(output_unit, '(a)', advance='no') REVERSE // RED // trim(fname) | |
| 151 | - else | |
| 152 | - write(output_unit, '(a)', advance='no') REVERSE // trim(color_code) // trim(fname) | |
| 153 | - end if | |
| 150 | + ! Normal selection cursor (not in move mode) - use bold+underline with original color | |
| 151 | + write(output_unit, '(a)', advance='no') BOLD // UNDERLINE // trim(color_code) // trim(fname) | |
| 154 | 152 | ! Add git indicators if in repo |
| 155 | 153 | if (in_git_repo) then |
| 156 | 154 | call write_git_indicators(current_is_staged(current_idx), & |
@@ -160,8 +158,8 @@ contains | ||
| 160 | 158 | end if |
| 161 | 159 | write(output_unit, '(a)') RESET |
| 162 | 160 | else if (is_selected(current_idx)) then |
| 163 | - ! Multi-selected item (not the cursor) - show with cyan background | |
| 164 | - write(output_unit, '(a)', advance='no') REVERSE // trim(color_code) // trim(fname) | |
| 161 | + ! Multi-selected item (not the cursor) - show with underline | |
| 162 | + write(output_unit, '(a)', advance='no') UNDERLINE // trim(color_code) // trim(fname) | |
| 165 | 163 | ! Add git indicators if in repo |
| 166 | 164 | if (in_git_repo) then |
| 167 | 165 | call write_git_indicators(current_is_staged(current_idx), & |