@@ -13,6 +13,7 @@ module command_palette_module |
| 13 | | 13 | |
| 14 | integer, parameter :: MAX_COMMANDS = 100 | 14 | integer, parameter :: MAX_COMMANDS = 100 |
| 15 | integer, parameter :: MAX_VISIBLE = 10 | 15 | integer, parameter :: MAX_VISIBLE = 10 |
| | 16 | + integer, parameter :: PALETTE_WIDTH = 60 ! Fixed width for centered palette |
| 16 | | 17 | |
| 17 | type :: command_t | 18 | type :: command_t |
| 18 | character(len=:), allocatable :: name | 19 | character(len=:), allocatable :: name |
@@ -291,37 +292,65 @@ contains |
| 291 | end select | 292 | end select |
| 292 | end subroutine command_palette_handle_key | 293 | end subroutine command_palette_handle_key |
| 293 | | 294 | |
| 294 | - subroutine render_command_palette(palette, screen_rows) | 295 | + subroutine render_command_palette(palette, screen_rows, screen_cols) |
| 295 | type(command_palette_t), intent(in) :: palette | 296 | type(command_palette_t), intent(in) :: palette |
| 296 | - integer, intent(in) :: screen_rows | 297 | + integer, intent(in) :: screen_rows, screen_cols |
| 297 | - integer :: i, visible_start, visible_end, row | 298 | + integer :: i, visible_start, visible_end, row, start_col, start_row |
| | 299 | + integer :: content_width, display_width |
| 298 | character(len=256) :: line, category_tag | 300 | character(len=256) :: line, category_tag |
| 299 | type(command_t) :: cmd | 301 | type(command_t) :: cmd |
| 300 | - | 302 | + character(len=:), allocatable :: border_top, border_bottom |
| 301 | - ! Clear and draw header | 303 | + ! ANSI escape codes |
| 302 | - call terminal_move_cursor(screen_rows - MAX_VISIBLE - 2, 1) | 304 | + character(len=*), parameter :: ESC = char(27) |
| 303 | - call terminal_write(repeat(' ', 80)) | 305 | + character(len=*), parameter :: CYAN = ESC // '[36m' |
| 304 | - call terminal_move_cursor(screen_rows - MAX_VISIBLE - 2, 1) | 306 | + character(len=*), parameter :: YELLOW = ESC // '[33m' |
| 305 | - call terminal_write('Command Palette (type to search, Esc to cancel)') | 307 | + character(len=*), parameter :: INVERSE = ESC // '[7m' |
| 306 | - | 308 | + character(len=*), parameter :: RESET = ESC // '[0m' |
| 307 | - ! Draw search query | 309 | + |
| 308 | - call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 1) | 310 | + ! Calculate centering - top-center like VSCode |
| 309 | - call terminal_write(repeat(' ', 80)) | 311 | + content_width = min(PALETTE_WIDTH, screen_cols - 4) |
| 310 | - call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 1) | 312 | + start_col = max(1, (screen_cols - content_width) / 2) |
| 311 | - call terminal_write('> ' // trim(palette%search_query)) | 313 | + start_row = 2 ! Start near top (below tab bar if present) |
| | 314 | + |
| | 315 | + ! Build border strings |
| | 316 | + border_top = '┌' // repeat('─', content_width - 2) // '┐' |
| | 317 | + border_bottom = '└' // repeat('─', content_width - 2) // '┘' |
| | 318 | + |
| | 319 | + ! Draw top border |
| | 320 | + call terminal_move_cursor(start_row, start_col) |
| | 321 | + call terminal_write(border_top) |
| | 322 | + |
| | 323 | + ! Draw header line with cyan title |
| | 324 | + row = start_row + 1 |
| | 325 | + call terminal_move_cursor(row, start_col) |
| | 326 | + call terminal_write('│' // CYAN // ' Command Palette' // RESET) |
| | 327 | + display_width = 17 ! " Command Palette" length |
| | 328 | + call terminal_write(repeat(' ', content_width - display_width - 2) // '│') |
| | 329 | + |
| | 330 | + ! Draw search query line with yellow prompt |
| | 331 | + row = row + 1 |
| | 332 | + call terminal_move_cursor(row, start_col) |
| | 333 | + call terminal_write('│' // YELLOW // ' > ' // RESET) |
| | 334 | + call terminal_write(trim(palette%search_query)) |
| | 335 | + display_width = 3 + len_trim(palette%search_query) |
| | 336 | + call terminal_write(repeat(' ', content_width - display_width - 2) // '│') |
| | 337 | + |
| | 338 | + ! Draw separator |
| | 339 | + row = row + 1 |
| | 340 | + call terminal_move_cursor(row, start_col) |
| | 341 | + call terminal_write('├' // repeat('─', content_width - 2) // '┤') |
| 312 | | 342 | |
| 313 | ! Calculate visible range | 343 | ! Calculate visible range |
| 314 | visible_start = palette%scroll_offset + 1 | 344 | visible_start = palette%scroll_offset + 1 |
| 315 | visible_end = min(visible_start + MAX_VISIBLE - 1, palette%num_filtered) | 345 | visible_end = min(visible_start + MAX_VISIBLE - 1, palette%num_filtered) |
| 316 | | 346 | |
| 317 | ! Draw commands | 347 | ! Draw commands |
| 318 | - row = screen_rows - MAX_VISIBLE | | |
| 319 | do i = visible_start, visible_end | 348 | do i = visible_start, visible_end |
| | 349 | + row = row + 1 |
| 320 | cmd = palette%filtered_commands(i) | 350 | cmd = palette%filtered_commands(i) |
| 321 | | 351 | |
| 322 | - call terminal_move_cursor(row, 1) | 352 | + call terminal_move_cursor(row, start_col) |
| 323 | - call terminal_write(repeat(' ', 80)) | 353 | + call terminal_write('│') |
| 324 | - call terminal_move_cursor(row, 1) | | |
| 325 | | 354 | |
| 326 | ! Build line with category, name, and shortcut | 355 | ! Build line with category, name, and shortcut |
| 327 | if (len_trim(cmd%category) > 0) then | 356 | if (len_trim(cmd%category) > 0) then |
@@ -330,37 +359,46 @@ contains |
| 330 | category_tag = '' | 359 | category_tag = '' |
| 331 | end if | 360 | end if |
| 332 | | 361 | |
| 333 | - if (i == palette%selected_index) then | 362 | + write(line, '(A,A,A)') ' ', trim(category_tag), trim(cmd%name) |
| 334 | - ! Highlight selected item | | |
| 335 | - write(line, '(A,A,A)') '> ', trim(category_tag), trim(cmd%name) | | |
| 336 | - else | | |
| 337 | - write(line, '(A,A,A)') ' ', trim(category_tag), trim(cmd%name) | | |
| 338 | - end if | | |
| 339 | | 363 | |
| 340 | ! Add shortcut if available | 364 | ! Add shortcut if available |
| 341 | if (len_trim(cmd%shortcut) > 0) then | 365 | if (len_trim(cmd%shortcut) > 0) then |
| 342 | - write(line, '(A,A,A)') trim(line), ' (', trim(cmd%shortcut) // ')' | 366 | + write(line, '(A,A,A)') trim(line), ' ', trim(cmd%shortcut) |
| 343 | end if | 367 | end if |
| 344 | | 368 | |
| 345 | - call terminal_write(trim(line)) | 369 | + ! Truncate if too long |
| 346 | - row = row + 1 | 370 | + display_width = min(len_trim(line), content_width - 3) |
| | 371 | + |
| | 372 | + if (i == palette%selected_index) then |
| | 373 | + ! Highlight selected item with inverse colors |
| | 374 | + call terminal_write(INVERSE // line(1:display_width)) |
| | 375 | + call terminal_write(repeat(' ', content_width - display_width - 2) // RESET // '│') |
| | 376 | + else |
| | 377 | + call terminal_write(line(1:display_width)) |
| | 378 | + call terminal_write(repeat(' ', content_width - display_width - 2) // '│') |
| | 379 | + end if |
| 347 | end do | 380 | end do |
| 348 | | 381 | |
| 349 | - ! Clear remaining lines | 382 | + ! Fill remaining visible slots with empty rows |
| 350 | - do while (row <= screen_rows) | 383 | + do i = visible_end + 1, visible_start + MAX_VISIBLE - 1 |
| 351 | - call terminal_move_cursor(row, 1) | | |
| 352 | - call terminal_write(repeat(' ', 80)) | | |
| 353 | row = row + 1 | 384 | row = row + 1 |
| | 385 | + call terminal_move_cursor(row, start_col) |
| | 386 | + call terminal_write('│' // repeat(' ', content_width - 2) // '│') |
| 354 | end do | 387 | end do |
| 355 | | 388 | |
| 356 | - ! Position cursor at end of search query | 389 | + ! Draw bottom border |
| 357 | - call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 3 + palette%search_pos) | 390 | + row = row + 1 |
| | 391 | + call terminal_move_cursor(row, start_col) |
| | 392 | + call terminal_write(border_bottom) |
| | 393 | + |
| | 394 | + ! Position cursor at end of search query (inside the box) |
| | 395 | + call terminal_move_cursor(start_row + 2, start_col + 4 + palette%search_pos) |
| 358 | end subroutine render_command_palette | 396 | end subroutine render_command_palette |
| 359 | | 397 | |
| 360 | - function show_command_palette_interactive(palette, screen_rows) result(selected_cmd_id) | 398 | + function show_command_palette_interactive(palette, screen_rows, screen_cols) result(selected_cmd_id) |
| 361 | use input_handler_module, only: get_key_input | 399 | use input_handler_module, only: get_key_input |
| 362 | type(command_palette_t), intent(inout) :: palette | 400 | type(command_palette_t), intent(inout) :: palette |
| 363 | - integer, intent(in) :: screen_rows | 401 | + integer, intent(in) :: screen_rows, screen_cols |
| 364 | character(len=:), allocatable :: selected_cmd_id | 402 | character(len=:), allocatable :: selected_cmd_id |
| 365 | character(len=32) :: key_input | 403 | character(len=32) :: key_input |
| 366 | integer :: ch, status | 404 | integer :: ch, status |
@@ -368,7 +406,7 @@ contains |
| 368 | type(command_t) :: cmd | 406 | type(command_t) :: cmd |
| 369 | | 407 | |
| 370 | call show_command_palette(palette) | 408 | call show_command_palette(palette) |
| 371 | - call render_command_palette(palette, screen_rows) | 409 | + call render_command_palette(palette, screen_rows, screen_cols) |
| 372 | | 410 | |
| 373 | do | 411 | do |
| 374 | call get_key_input(key_input, status) | 412 | call get_key_input(key_input, status) |
@@ -406,7 +444,7 @@ contains |
| 406 | end if | 444 | end if |
| 407 | end if | 445 | end if |
| 408 | | 446 | |
| 409 | - call render_command_palette(palette, screen_rows) | 447 | + call render_command_palette(palette, screen_rows, screen_cols) |
| 410 | end do | 448 | end do |
| 411 | end function show_command_palette_interactive | 449 | end function show_command_palette_interactive |
| 412 | | 450 | |