| 1 | module workspace_symbols_panel_module |
| 2 | use iso_fortran_env, only: int32 |
| 3 | use terminal_io_module |
| 4 | implicit none |
| 5 | private |
| 6 | |
| 7 | public :: workspace_symbols_panel_t, workspace_symbol_t |
| 8 | public :: init_workspace_symbols_panel, cleanup_workspace_symbols_panel |
| 9 | public :: show_workspace_symbols_panel, hide_workspace_symbols_panel |
| 10 | public :: is_workspace_symbols_panel_visible, workspace_symbols_panel_handle_key |
| 11 | public :: set_workspace_symbols, get_selected_symbol |
| 12 | public :: render_workspace_symbols_panel, get_search_query |
| 13 | public :: add_search_char, delete_search_char, clear_search |
| 14 | |
| 15 | integer, parameter :: MAX_SYMBOLS = 1000 |
| 16 | |
| 17 | type :: workspace_symbol_t |
| 18 | character(len=:), allocatable :: name |
| 19 | character(len=:), allocatable :: kind_name ! "Function", "Class", etc. |
| 20 | character(len=:), allocatable :: container_name |
| 21 | character(len=:), allocatable :: file_uri |
| 22 | character(len=:), allocatable :: file_path ! Extracted from URI |
| 23 | integer :: line = 0 |
| 24 | integer :: column = 0 |
| 25 | integer :: kind = 0 |
| 26 | integer :: score = 0 ! For fuzzy matching |
| 27 | end type workspace_symbol_t |
| 28 | |
| 29 | type :: workspace_symbols_panel_t |
| 30 | logical :: visible = .false. |
| 31 | integer :: panel_width = 60 |
| 32 | integer :: panel_start_col = 1 |
| 33 | integer :: max_visible = 20 |
| 34 | type(workspace_symbol_t), allocatable :: all_symbols(:) |
| 35 | integer :: num_symbols = 0 |
| 36 | type(workspace_symbol_t), allocatable :: filtered_symbols(:) |
| 37 | integer :: num_filtered = 0 |
| 38 | integer :: selected_index = 1 |
| 39 | integer :: scroll_offset = 0 |
| 40 | character(len=256) :: search_query = '' |
| 41 | integer :: search_pos = 0 |
| 42 | logical :: needs_lsp_query = .false. ! Flag to trigger LSP request |
| 43 | end type workspace_symbols_panel_t |
| 44 | |
| 45 | contains |
| 46 | |
| 47 | subroutine init_workspace_symbols_panel(panel) |
| 48 | type(workspace_symbols_panel_t), intent(out) :: panel |
| 49 | |
| 50 | panel%visible = .false. |
| 51 | panel%num_symbols = 0 |
| 52 | panel%num_filtered = 0 |
| 53 | panel%selected_index = 1 |
| 54 | panel%scroll_offset = 0 |
| 55 | panel%search_query = '' |
| 56 | panel%search_pos = 0 |
| 57 | panel%needs_lsp_query = .false. |
| 58 | |
| 59 | allocate(panel%all_symbols(MAX_SYMBOLS)) |
| 60 | allocate(panel%filtered_symbols(MAX_SYMBOLS)) |
| 61 | end subroutine init_workspace_symbols_panel |
| 62 | |
| 63 | subroutine cleanup_workspace_symbols_panel(panel) |
| 64 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 65 | integer :: i |
| 66 | |
| 67 | if (allocated(panel%all_symbols)) then |
| 68 | do i = 1, panel%num_symbols |
| 69 | if (allocated(panel%all_symbols(i)%name)) deallocate(panel%all_symbols(i)%name) |
| 70 | if (allocated(panel%all_symbols(i)%kind_name)) deallocate(panel%all_symbols(i)%kind_name) |
| 71 | if (allocated(panel%all_symbols(i)%container_name)) deallocate(panel%all_symbols(i)%container_name) |
| 72 | if (allocated(panel%all_symbols(i)%file_uri)) deallocate(panel%all_symbols(i)%file_uri) |
| 73 | if (allocated(panel%all_symbols(i)%file_path)) deallocate(panel%all_symbols(i)%file_path) |
| 74 | end do |
| 75 | deallocate(panel%all_symbols) |
| 76 | end if |
| 77 | |
| 78 | if (allocated(panel%filtered_symbols)) deallocate(panel%filtered_symbols) |
| 79 | end subroutine cleanup_workspace_symbols_panel |
| 80 | |
| 81 | subroutine show_workspace_symbols_panel(panel, screen_width, screen_height) |
| 82 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 83 | integer, intent(in) :: screen_width, screen_height |
| 84 | |
| 85 | panel%visible = .true. |
| 86 | panel%panel_width = min(70, screen_width / 2) |
| 87 | panel%panel_start_col = screen_width - panel%panel_width + 1 |
| 88 | panel%max_visible = screen_height - 5 ! Room for header, search, hints |
| 89 | panel%search_query = '' |
| 90 | panel%search_pos = 0 |
| 91 | panel%selected_index = 1 |
| 92 | panel%scroll_offset = 0 |
| 93 | panel%needs_lsp_query = .true. ! Request initial symbols |
| 94 | |
| 95 | ! Initially show all symbols |
| 96 | call filter_symbols(panel) |
| 97 | end subroutine show_workspace_symbols_panel |
| 98 | |
| 99 | subroutine hide_workspace_symbols_panel(panel) |
| 100 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 101 | panel%visible = .false. |
| 102 | end subroutine hide_workspace_symbols_panel |
| 103 | |
| 104 | function is_workspace_symbols_panel_visible(panel) result(visible) |
| 105 | type(workspace_symbols_panel_t), intent(in) :: panel |
| 106 | logical :: visible |
| 107 | visible = panel%visible |
| 108 | end function is_workspace_symbols_panel_visible |
| 109 | |
| 110 | function get_search_query(panel) result(query) |
| 111 | type(workspace_symbols_panel_t), intent(in) :: panel |
| 112 | character(len=:), allocatable :: query |
| 113 | if (panel%search_pos > 0) then |
| 114 | query = trim(panel%search_query(1:panel%search_pos)) |
| 115 | else |
| 116 | query = '' |
| 117 | end if |
| 118 | end function get_search_query |
| 119 | |
| 120 | subroutine add_search_char(panel, ch) |
| 121 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 122 | character, intent(in) :: ch |
| 123 | |
| 124 | if (panel%search_pos < 255) then |
| 125 | panel%search_pos = panel%search_pos + 1 |
| 126 | panel%search_query(panel%search_pos:panel%search_pos) = ch |
| 127 | panel%needs_lsp_query = .true. |
| 128 | call filter_symbols(panel) |
| 129 | end if |
| 130 | end subroutine add_search_char |
| 131 | |
| 132 | subroutine delete_search_char(panel) |
| 133 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 134 | |
| 135 | if (panel%search_pos > 0) then |
| 136 | panel%search_query(panel%search_pos:panel%search_pos) = ' ' |
| 137 | panel%search_pos = panel%search_pos - 1 |
| 138 | panel%needs_lsp_query = .true. |
| 139 | call filter_symbols(panel) |
| 140 | end if |
| 141 | end subroutine delete_search_char |
| 142 | |
| 143 | subroutine clear_search(panel) |
| 144 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 145 | panel%search_query = '' |
| 146 | panel%search_pos = 0 |
| 147 | panel%needs_lsp_query = .true. |
| 148 | call filter_symbols(panel) |
| 149 | end subroutine clear_search |
| 150 | |
| 151 | subroutine set_workspace_symbols(panel, symbols, count) |
| 152 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 153 | type(workspace_symbol_t), intent(in) :: symbols(:) |
| 154 | integer, intent(in) :: count |
| 155 | integer :: i, copy_count |
| 156 | |
| 157 | copy_count = min(count, MAX_SYMBOLS) |
| 158 | panel%num_symbols = copy_count |
| 159 | |
| 160 | do i = 1, copy_count |
| 161 | ! Deep copy each symbol |
| 162 | if (allocated(panel%all_symbols(i)%name)) deallocate(panel%all_symbols(i)%name) |
| 163 | if (allocated(symbols(i)%name)) then |
| 164 | allocate(character(len=len(symbols(i)%name)) :: panel%all_symbols(i)%name) |
| 165 | panel%all_symbols(i)%name = symbols(i)%name |
| 166 | end if |
| 167 | |
| 168 | if (allocated(panel%all_symbols(i)%kind_name)) deallocate(panel%all_symbols(i)%kind_name) |
| 169 | if (allocated(symbols(i)%kind_name)) then |
| 170 | allocate(character(len=len(symbols(i)%kind_name)) :: panel%all_symbols(i)%kind_name) |
| 171 | panel%all_symbols(i)%kind_name = symbols(i)%kind_name |
| 172 | end if |
| 173 | |
| 174 | if (allocated(panel%all_symbols(i)%container_name)) deallocate(panel%all_symbols(i)%container_name) |
| 175 | if (allocated(symbols(i)%container_name)) then |
| 176 | allocate(character(len=len(symbols(i)%container_name)) :: panel%all_symbols(i)%container_name) |
| 177 | panel%all_symbols(i)%container_name = symbols(i)%container_name |
| 178 | end if |
| 179 | |
| 180 | if (allocated(panel%all_symbols(i)%file_uri)) deallocate(panel%all_symbols(i)%file_uri) |
| 181 | if (allocated(symbols(i)%file_uri)) then |
| 182 | allocate(character(len=len(symbols(i)%file_uri)) :: panel%all_symbols(i)%file_uri) |
| 183 | panel%all_symbols(i)%file_uri = symbols(i)%file_uri |
| 184 | end if |
| 185 | |
| 186 | if (allocated(panel%all_symbols(i)%file_path)) deallocate(panel%all_symbols(i)%file_path) |
| 187 | if (allocated(symbols(i)%file_path)) then |
| 188 | allocate(character(len=len(symbols(i)%file_path)) :: panel%all_symbols(i)%file_path) |
| 189 | panel%all_symbols(i)%file_path = symbols(i)%file_path |
| 190 | end if |
| 191 | |
| 192 | panel%all_symbols(i)%line = symbols(i)%line |
| 193 | panel%all_symbols(i)%column = symbols(i)%column |
| 194 | panel%all_symbols(i)%kind = symbols(i)%kind |
| 195 | panel%all_symbols(i)%score = symbols(i)%score |
| 196 | end do |
| 197 | |
| 198 | ! Refilter with current query |
| 199 | call filter_symbols(panel) |
| 200 | end subroutine set_workspace_symbols |
| 201 | |
| 202 | subroutine filter_symbols(panel) |
| 203 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 204 | character(len=:), allocatable :: query |
| 205 | integer :: i, score |
| 206 | |
| 207 | if (panel%search_pos > 0) then |
| 208 | query = trim(panel%search_query(1:panel%search_pos)) |
| 209 | else |
| 210 | query = '' |
| 211 | end if |
| 212 | |
| 213 | panel%num_filtered = 0 |
| 214 | |
| 215 | do i = 1, panel%num_symbols |
| 216 | if (allocated(panel%all_symbols(i)%name)) then |
| 217 | score = fuzzy_match_score(panel%all_symbols(i)%name, query) |
| 218 | if (score > 0) then |
| 219 | panel%num_filtered = panel%num_filtered + 1 |
| 220 | panel%filtered_symbols(panel%num_filtered) = panel%all_symbols(i) |
| 221 | panel%filtered_symbols(panel%num_filtered)%score = score |
| 222 | end if |
| 223 | end if |
| 224 | end do |
| 225 | |
| 226 | ! Sort by score (simple bubble sort for now) |
| 227 | call sort_symbols_by_score(panel%filtered_symbols, panel%num_filtered) |
| 228 | |
| 229 | ! Reset selection |
| 230 | panel%selected_index = 1 |
| 231 | panel%scroll_offset = 0 |
| 232 | end subroutine filter_symbols |
| 233 | |
| 234 | function fuzzy_match_score(text, pattern) result(score) |
| 235 | character(len=*), intent(in) :: text, pattern |
| 236 | integer :: score |
| 237 | integer :: i, j, text_len, pattern_len |
| 238 | integer :: consecutive_matches |
| 239 | character :: text_lower, pattern_lower |
| 240 | |
| 241 | score = 0 |
| 242 | text_len = len_trim(text) |
| 243 | pattern_len = len_trim(pattern) |
| 244 | |
| 245 | ! Empty pattern matches everything |
| 246 | if (pattern_len == 0) then |
| 247 | score = 100 |
| 248 | return |
| 249 | end if |
| 250 | |
| 251 | j = 1 |
| 252 | consecutive_matches = 0 |
| 253 | |
| 254 | do i = 1, text_len |
| 255 | if (j > pattern_len) exit |
| 256 | |
| 257 | ! Case-insensitive comparison |
| 258 | text_lower = to_lower(text(i:i)) |
| 259 | pattern_lower = to_lower(pattern(j:j)) |
| 260 | |
| 261 | if (text_lower == pattern_lower) then |
| 262 | score = score + 10 |
| 263 | consecutive_matches = consecutive_matches + 1 |
| 264 | |
| 265 | ! Bonus for consecutive matches |
| 266 | if (consecutive_matches > 1) then |
| 267 | score = score + 5 |
| 268 | end if |
| 269 | |
| 270 | ! Bonus for matching at word start |
| 271 | if (i == 1 .or. text(i-1:i-1) == ' ' .or. text(i-1:i-1) == '_') then |
| 272 | score = score + 15 |
| 273 | end if |
| 274 | |
| 275 | j = j + 1 |
| 276 | else |
| 277 | consecutive_matches = 0 |
| 278 | end if |
| 279 | end do |
| 280 | |
| 281 | ! Only match if all pattern characters were found |
| 282 | if (j <= pattern_len) then |
| 283 | score = 0 |
| 284 | end if |
| 285 | end function fuzzy_match_score |
| 286 | |
| 287 | function to_lower(ch) result(lower) |
| 288 | character, intent(in) :: ch |
| 289 | character :: lower |
| 290 | integer :: code |
| 291 | |
| 292 | code = iachar(ch) |
| 293 | if (code >= iachar('A') .and. code <= iachar('Z')) then |
| 294 | lower = achar(code + 32) |
| 295 | else |
| 296 | lower = ch |
| 297 | end if |
| 298 | end function to_lower |
| 299 | |
| 300 | subroutine sort_symbols_by_score(symbols, count) |
| 301 | type(workspace_symbol_t), intent(inout) :: symbols(:) |
| 302 | integer, intent(in) :: count |
| 303 | type(workspace_symbol_t) :: temp |
| 304 | integer :: i, j |
| 305 | |
| 306 | ! Bubble sort (good enough for small lists) |
| 307 | do i = 1, count - 1 |
| 308 | do j = i + 1, count |
| 309 | if (symbols(j)%score > symbols(i)%score) then |
| 310 | temp = symbols(i) |
| 311 | symbols(i) = symbols(j) |
| 312 | symbols(j) = temp |
| 313 | end if |
| 314 | end do |
| 315 | end do |
| 316 | end subroutine sort_symbols_by_score |
| 317 | |
| 318 | function get_selected_symbol(panel) result(sym) |
| 319 | type(workspace_symbols_panel_t), intent(in) :: panel |
| 320 | type(workspace_symbol_t) :: sym |
| 321 | |
| 322 | if (panel%num_filtered > 0 .and. panel%selected_index <= panel%num_filtered) then |
| 323 | sym = panel%filtered_symbols(panel%selected_index) |
| 324 | end if |
| 325 | end function get_selected_symbol |
| 326 | |
| 327 | function workspace_symbols_panel_handle_key(panel, key) result(handled) |
| 328 | type(workspace_symbols_panel_t), intent(inout) :: panel |
| 329 | character(len=*), intent(in) :: key |
| 330 | logical :: handled |
| 331 | |
| 332 | handled = .false. |
| 333 | if (.not. panel%visible) return |
| 334 | |
| 335 | select case(trim(key)) |
| 336 | case('j', 'down', 'ctrl-n') |
| 337 | ! Always handle to clamp at boundary |
| 338 | handled = .true. |
| 339 | if (panel%selected_index < panel%num_filtered) then |
| 340 | panel%selected_index = panel%selected_index + 1 |
| 341 | if (panel%selected_index > panel%scroll_offset + panel%max_visible) then |
| 342 | panel%scroll_offset = panel%selected_index - panel%max_visible |
| 343 | end if |
| 344 | end if |
| 345 | |
| 346 | case('k', 'up', 'ctrl-p') |
| 347 | ! Always handle to clamp at boundary |
| 348 | handled = .true. |
| 349 | if (panel%selected_index > 1) then |
| 350 | panel%selected_index = panel%selected_index - 1 |
| 351 | if (panel%selected_index <= panel%scroll_offset) then |
| 352 | panel%scroll_offset = max(0, panel%selected_index - 1) |
| 353 | end if |
| 354 | end if |
| 355 | |
| 356 | case('ctrl-u') |
| 357 | ! Clear search |
| 358 | call clear_search(panel) |
| 359 | handled = .true. |
| 360 | |
| 361 | case('esc', 'escape') |
| 362 | call hide_workspace_symbols_panel(panel) |
| 363 | handled = .true. |
| 364 | |
| 365 | case('enter') |
| 366 | ! Signal to jump - handled by command_handler |
| 367 | handled = .true. |
| 368 | |
| 369 | case('backspace', 'ctrl-h') |
| 370 | call delete_search_char(panel) |
| 371 | handled = .true. |
| 372 | |
| 373 | case default |
| 374 | ! Check if it's a printable character for search |
| 375 | if (len_trim(key) == 1) then |
| 376 | if (iachar(key(1:1)) >= 32 .and. iachar(key(1:1)) < 127) then |
| 377 | call add_search_char(panel, key(1:1)) |
| 378 | handled = .true. |
| 379 | end if |
| 380 | end if |
| 381 | end select |
| 382 | end function workspace_symbols_panel_handle_key |
| 383 | |
| 384 | subroutine render_workspace_symbols_panel(panel, screen_height) |
| 385 | type(workspace_symbols_panel_t), intent(in) :: panel |
| 386 | integer, intent(in) :: screen_height |
| 387 | integer :: row, i, start_idx, end_idx |
| 388 | character(len=256) :: line, file_info |
| 389 | character(len=1), parameter :: ESC = char(27) |
| 390 | |
| 391 | if (.not. panel%visible) return |
| 392 | |
| 393 | ! Draw title bar with background color |
| 394 | row = 1 |
| 395 | call terminal_move_cursor(row, panel%panel_start_col) |
| 396 | call terminal_write(ESC // '[48;5;237m' // ESC // '[1m') ! Dark bg, bold |
| 397 | line = " Workspace Symbols" |
| 398 | if (panel%num_filtered > 0) then |
| 399 | write(line, '(A,I0,A)') trim(line) // " (", panel%num_filtered, ")" |
| 400 | end if |
| 401 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 402 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 403 | call terminal_write(ESC // '[0m') |
| 404 | |
| 405 | ! Draw search input |
| 406 | row = 2 |
| 407 | call terminal_move_cursor(row, panel%panel_start_col) |
| 408 | call terminal_write(ESC // '[48;5;236m') ! Slightly lighter for input |
| 409 | if (panel%search_pos > 0) then |
| 410 | line = " > " // trim(panel%search_query(1:panel%search_pos)) |
| 411 | else |
| 412 | line = " > " // ESC // '[90m' // "(type to filter)" // ESC // '[0m' // ESC // '[48;5;236m' |
| 413 | end if |
| 414 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 415 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 416 | call terminal_write(ESC // '[0m') |
| 417 | |
| 418 | ! Draw separator |
| 419 | row = 3 |
| 420 | call terminal_move_cursor(row, panel%panel_start_col) |
| 421 | call terminal_write(ESC // '[48;5;237m' // repeat("-", panel%panel_width) // ESC // '[0m') |
| 422 | |
| 423 | ! Calculate visible range |
| 424 | start_idx = panel%scroll_offset + 1 |
| 425 | end_idx = min(panel%scroll_offset + panel%max_visible, panel%num_filtered) |
| 426 | |
| 427 | ! Render symbols |
| 428 | if (panel%num_filtered > 0) then |
| 429 | do i = start_idx, end_idx |
| 430 | row = row + 1 |
| 431 | call terminal_move_cursor(row, panel%panel_start_col) |
| 432 | |
| 433 | ! Background color based on selection |
| 434 | if (i == panel%selected_index) then |
| 435 | call terminal_write(ESC // '[48;5;240m') ! Highlight |
| 436 | else |
| 437 | call terminal_write(ESC // '[48;5;235m') ! Normal |
| 438 | end if |
| 439 | |
| 440 | ! Build display line: icon + name + file:line |
| 441 | line = " " |
| 442 | |
| 443 | ! Add kind indicator |
| 444 | if (allocated(panel%filtered_symbols(i)%kind_name)) then |
| 445 | line = trim(line) // "[" // & |
| 446 | trim(panel%filtered_symbols(i)%kind_name(1: & |
| 447 | min(3, len_trim(panel%filtered_symbols(i)%kind_name)))) // "] " |
| 448 | else |
| 449 | line = trim(line) // " " |
| 450 | end if |
| 451 | |
| 452 | ! Add symbol name |
| 453 | if (allocated(panel%filtered_symbols(i)%name)) then |
| 454 | line = trim(line) // trim(panel%filtered_symbols(i)%name) |
| 455 | end if |
| 456 | |
| 457 | ! Add file info on selected item |
| 458 | if (i == panel%selected_index) then |
| 459 | if (allocated(panel%filtered_symbols(i)%file_path)) then |
| 460 | write(file_info, '(A,A,A,I0)') " (", & |
| 461 | trim(get_basename(panel%filtered_symbols(i)%file_path)), & |
| 462 | ":", panel%filtered_symbols(i)%line |
| 463 | file_info = trim(file_info) // ")" |
| 464 | if (len_trim(line) + len_trim(file_info) < panel%panel_width - 1) then |
| 465 | line = trim(line) // ESC // '[90m' // trim(file_info) // ESC // '[0m' // ESC // '[48;5;240m' |
| 466 | end if |
| 467 | end if |
| 468 | end if |
| 469 | |
| 470 | ! Write line and pad |
| 471 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 472 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 473 | call terminal_write(ESC // '[0m') |
| 474 | end do |
| 475 | |
| 476 | ! Fill empty rows |
| 477 | do while (row < screen_height - 1) |
| 478 | row = row + 1 |
| 479 | call terminal_move_cursor(row, panel%panel_start_col) |
| 480 | call terminal_write(ESC // '[48;5;235m' // repeat(" ", panel%panel_width) // ESC // '[0m') |
| 481 | end do |
| 482 | else |
| 483 | ! No symbols message |
| 484 | row = row + 1 |
| 485 | call terminal_move_cursor(row, panel%panel_start_col) |
| 486 | call terminal_write(ESC // '[48;5;235m' // ESC // '[90m') |
| 487 | if (panel%search_pos == 0) then |
| 488 | line = " Type to search..." |
| 489 | else if (panel%num_symbols == 0) then |
| 490 | line = " Searching..." |
| 491 | else |
| 492 | line = " No matching symbols" |
| 493 | end if |
| 494 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 495 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 496 | call terminal_write(ESC // '[0m') |
| 497 | |
| 498 | do while (row < screen_height - 1) |
| 499 | row = row + 1 |
| 500 | call terminal_move_cursor(row, panel%panel_start_col) |
| 501 | call terminal_write(ESC // '[48;5;235m' // repeat(" ", panel%panel_width) // ESC // '[0m') |
| 502 | end do |
| 503 | end if |
| 504 | |
| 505 | ! Draw hint bar at bottom |
| 506 | call terminal_move_cursor(screen_height, panel%panel_start_col) |
| 507 | call terminal_write(ESC // '[48;5;237m' // ESC // '[90m') |
| 508 | line = " Enter:open Esc:close Ctrl-U:clear" |
| 509 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 510 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 511 | call terminal_write(ESC // '[0m') |
| 512 | end subroutine render_workspace_symbols_panel |
| 513 | |
| 514 | function get_basename(path) result(basename) |
| 515 | character(len=*), intent(in) :: path |
| 516 | character(len=256) :: basename |
| 517 | integer :: i, last_slash |
| 518 | |
| 519 | last_slash = 0 |
| 520 | do i = len_trim(path), 1, -1 |
| 521 | if (path(i:i) == '/') then |
| 522 | last_slash = i |
| 523 | exit |
| 524 | end if |
| 525 | end do |
| 526 | |
| 527 | if (last_slash > 0 .and. last_slash < len_trim(path)) then |
| 528 | basename = path(last_slash+1:len_trim(path)) |
| 529 | else |
| 530 | basename = path |
| 531 | end if |
| 532 | end function get_basename |
| 533 | |
| 534 | end module workspace_symbols_panel_module |
| 535 |