| 1 | module symbols_panel_module |
| 2 | use iso_fortran_env, only: int32 |
| 3 | use terminal_io_module, only: terminal_move_cursor, terminal_write |
| 4 | implicit none |
| 5 | private |
| 6 | |
| 7 | public :: symbols_panel_t |
| 8 | public :: init_symbols_panel, cleanup_symbols_panel |
| 9 | public :: show_symbols_panel, hide_symbols_panel, toggle_symbols_panel |
| 10 | public :: is_symbols_panel_visible, symbols_panel_handle_key |
| 11 | public :: set_symbols, clear_symbols |
| 12 | public :: get_selected_symbol_location |
| 13 | public :: document_symbol_t |
| 14 | public :: render_symbols_panel |
| 15 | |
| 16 | ! LSP Symbol kinds |
| 17 | integer, parameter :: SYMBOL_FILE = 1 |
| 18 | integer, parameter :: SYMBOL_MODULE = 2 |
| 19 | integer, parameter :: SYMBOL_NAMESPACE = 3 |
| 20 | integer, parameter :: SYMBOL_PACKAGE = 4 |
| 21 | integer, parameter :: SYMBOL_CLASS = 5 |
| 22 | integer, parameter :: SYMBOL_METHOD = 6 |
| 23 | integer, parameter :: SYMBOL_PROPERTY = 7 |
| 24 | integer, parameter :: SYMBOL_FIELD = 8 |
| 25 | integer, parameter :: SYMBOL_CONSTRUCTOR = 9 |
| 26 | integer, parameter :: SYMBOL_ENUM = 10 |
| 27 | integer, parameter :: SYMBOL_INTERFACE = 11 |
| 28 | integer, parameter :: SYMBOL_FUNCTION = 12 |
| 29 | integer, parameter :: SYMBOL_VARIABLE = 13 |
| 30 | integer, parameter :: SYMBOL_CONSTANT = 14 |
| 31 | integer, parameter :: SYMBOL_STRING = 15 |
| 32 | integer, parameter :: SYMBOL_NUMBER = 16 |
| 33 | integer, parameter :: SYMBOL_BOOLEAN = 17 |
| 34 | integer, parameter :: SYMBOL_ARRAY = 18 |
| 35 | integer, parameter :: SYMBOL_OBJECT = 19 |
| 36 | integer, parameter :: SYMBOL_KEY = 20 |
| 37 | integer, parameter :: SYMBOL_NULL = 21 |
| 38 | integer, parameter :: SYMBOL_ENUMMEMBER = 22 |
| 39 | integer, parameter :: SYMBOL_STRUCT = 23 |
| 40 | integer, parameter :: SYMBOL_EVENT = 24 |
| 41 | integer, parameter :: SYMBOL_OPERATOR = 25 |
| 42 | integer, parameter :: SYMBOL_TYPEPARAMETER = 26 |
| 43 | |
| 44 | ! Document symbol type |
| 45 | type :: document_symbol_t |
| 46 | character(len=:), allocatable :: name |
| 47 | character(len=:), allocatable :: detail ! Additional info |
| 48 | integer :: kind = SYMBOL_VARIABLE |
| 49 | integer :: line = 1 |
| 50 | integer :: column = 1 |
| 51 | integer :: end_line = 1 |
| 52 | integer :: end_column = 1 |
| 53 | ! For hierarchical symbols |
| 54 | type(document_symbol_t), allocatable :: children(:) |
| 55 | integer :: num_children = 0 |
| 56 | integer :: depth = 0 ! Nesting level for display |
| 57 | logical :: is_expanded = .true. ! For tree view |
| 58 | end type document_symbol_t |
| 59 | |
| 60 | ! Symbols panel |
| 61 | type :: symbols_panel_t |
| 62 | logical :: visible = .false. |
| 63 | integer :: selected_index = 1 |
| 64 | integer :: panel_width = 40 |
| 65 | integer :: panel_start_col = 1 |
| 66 | |
| 67 | ! Symbol data |
| 68 | type(document_symbol_t), allocatable :: symbols(:) |
| 69 | type(document_symbol_t), allocatable :: flat_symbols(:) ! Flattened for navigation |
| 70 | integer :: num_symbols = 0 |
| 71 | integer :: num_flat_symbols = 0 |
| 72 | |
| 73 | ! Display settings |
| 74 | integer :: scroll_offset = 0 |
| 75 | integer :: max_visible = 20 |
| 76 | logical :: show_details = .false. |
| 77 | end type symbols_panel_t |
| 78 | |
| 79 | contains |
| 80 | |
| 81 | subroutine init_symbols_panel(panel) |
| 82 | type(symbols_panel_t), intent(out) :: panel |
| 83 | |
| 84 | panel%visible = .false. |
| 85 | panel%selected_index = 1 |
| 86 | panel%scroll_offset = 0 |
| 87 | panel%num_symbols = 0 |
| 88 | panel%num_flat_symbols = 0 |
| 89 | panel%show_details = .false. |
| 90 | end subroutine init_symbols_panel |
| 91 | |
| 92 | subroutine cleanup_symbols_panel(panel) |
| 93 | type(symbols_panel_t), intent(inout) :: panel |
| 94 | call clear_symbols(panel) |
| 95 | panel%visible = .false. |
| 96 | end subroutine cleanup_symbols_panel |
| 97 | |
| 98 | recursive subroutine cleanup_symbol(symbol) |
| 99 | type(document_symbol_t), intent(inout) :: symbol |
| 100 | integer :: i |
| 101 | |
| 102 | if (allocated(symbol%name)) deallocate(symbol%name) |
| 103 | if (allocated(symbol%detail)) deallocate(symbol%detail) |
| 104 | |
| 105 | if (allocated(symbol%children)) then |
| 106 | do i = 1, symbol%num_children |
| 107 | call cleanup_symbol(symbol%children(i)) |
| 108 | end do |
| 109 | deallocate(symbol%children) |
| 110 | end if |
| 111 | end subroutine cleanup_symbol |
| 112 | |
| 113 | subroutine clear_symbols(panel) |
| 114 | type(symbols_panel_t), intent(inout) :: panel |
| 115 | integer :: i |
| 116 | |
| 117 | if (allocated(panel%symbols)) then |
| 118 | do i = 1, panel%num_symbols |
| 119 | call cleanup_symbol(panel%symbols(i)) |
| 120 | end do |
| 121 | deallocate(panel%symbols) |
| 122 | end if |
| 123 | |
| 124 | if (allocated(panel%flat_symbols)) then |
| 125 | ! Flat symbols are references, don't cleanup twice |
| 126 | deallocate(panel%flat_symbols) |
| 127 | end if |
| 128 | |
| 129 | panel%num_symbols = 0 |
| 130 | panel%num_flat_symbols = 0 |
| 131 | panel%selected_index = 1 |
| 132 | panel%scroll_offset = 0 |
| 133 | end subroutine clear_symbols |
| 134 | |
| 135 | subroutine set_symbols(panel, symbols, num_symbols) |
| 136 | type(symbols_panel_t), intent(inout) :: panel |
| 137 | type(document_symbol_t), intent(in) :: symbols(:) |
| 138 | integer, intent(in) :: num_symbols |
| 139 | integer :: i, flat_count |
| 140 | |
| 141 | ! Clear existing symbols |
| 142 | call clear_symbols(panel) |
| 143 | |
| 144 | if (num_symbols > 0) then |
| 145 | allocate(panel%symbols(num_symbols)) |
| 146 | panel%num_symbols = num_symbols |
| 147 | |
| 148 | ! Deep copy symbols |
| 149 | do i = 1, num_symbols |
| 150 | call copy_symbol(panel%symbols(i), symbols(i)) |
| 151 | end do |
| 152 | |
| 153 | ! Count total flat symbols |
| 154 | flat_count = count_flat_symbols(panel%symbols, num_symbols) |
| 155 | allocate(panel%flat_symbols(flat_count)) |
| 156 | panel%num_flat_symbols = flat_count |
| 157 | |
| 158 | ! Flatten for navigation (start at index 1) |
| 159 | block |
| 160 | integer :: start_idx |
| 161 | start_idx = 1 |
| 162 | call flatten_symbols(panel%symbols, num_symbols, panel%flat_symbols, start_idx) |
| 163 | end block |
| 164 | end if |
| 165 | |
| 166 | panel%selected_index = 1 |
| 167 | panel%scroll_offset = 0 |
| 168 | end subroutine set_symbols |
| 169 | |
| 170 | recursive subroutine copy_symbol(dest, src) |
| 171 | type(document_symbol_t), intent(out) :: dest |
| 172 | type(document_symbol_t), intent(in) :: src |
| 173 | integer :: i |
| 174 | |
| 175 | if (allocated(src%name)) then |
| 176 | allocate(character(len=len(src%name)) :: dest%name) |
| 177 | dest%name = src%name |
| 178 | end if |
| 179 | |
| 180 | if (allocated(src%detail)) then |
| 181 | allocate(character(len=len(src%detail)) :: dest%detail) |
| 182 | dest%detail = src%detail |
| 183 | end if |
| 184 | |
| 185 | dest%kind = src%kind |
| 186 | dest%line = src%line |
| 187 | dest%column = src%column |
| 188 | dest%end_line = src%end_line |
| 189 | dest%end_column = src%end_column |
| 190 | dest%depth = src%depth |
| 191 | dest%is_expanded = src%is_expanded |
| 192 | dest%num_children = src%num_children |
| 193 | |
| 194 | if (allocated(src%children) .and. src%num_children > 0) then |
| 195 | allocate(dest%children(src%num_children)) |
| 196 | do i = 1, src%num_children |
| 197 | call copy_symbol(dest%children(i), src%children(i)) |
| 198 | end do |
| 199 | end if |
| 200 | end subroutine copy_symbol |
| 201 | |
| 202 | recursive function count_flat_symbols(symbols, num_symbols) result(count) |
| 203 | type(document_symbol_t), intent(in) :: symbols(:) |
| 204 | integer, intent(in) :: num_symbols |
| 205 | integer :: count, i |
| 206 | |
| 207 | count = num_symbols |
| 208 | do i = 1, num_symbols |
| 209 | if (allocated(symbols(i)%children) .and. symbols(i)%is_expanded) then |
| 210 | count = count + count_flat_symbols(symbols(i)%children, symbols(i)%num_children) |
| 211 | end if |
| 212 | end do |
| 213 | end function count_flat_symbols |
| 214 | |
| 215 | recursive subroutine flatten_symbols(symbols, num_symbols, flat_array, idx) |
| 216 | type(document_symbol_t), intent(in), target :: symbols(:) |
| 217 | integer, intent(in) :: num_symbols |
| 218 | type(document_symbol_t), intent(out) :: flat_array(:) |
| 219 | integer, intent(inout) :: idx |
| 220 | integer :: i |
| 221 | |
| 222 | do i = 1, num_symbols |
| 223 | if (idx <= size(flat_array)) then |
| 224 | flat_array(idx) = symbols(i) |
| 225 | idx = idx + 1 |
| 226 | |
| 227 | if (allocated(symbols(i)%children) .and. symbols(i)%is_expanded) then |
| 228 | call flatten_symbols(symbols(i)%children, symbols(i)%num_children, flat_array, idx) |
| 229 | end if |
| 230 | end if |
| 231 | end do |
| 232 | end subroutine flatten_symbols |
| 233 | |
| 234 | subroutine show_symbols_panel(panel, screen_width, screen_height) |
| 235 | type(symbols_panel_t), intent(inout) :: panel |
| 236 | integer, intent(in) :: screen_width, screen_height |
| 237 | |
| 238 | panel%panel_width = min(50, screen_width / 3) |
| 239 | panel%panel_start_col = screen_width - panel%panel_width + 1 |
| 240 | panel%max_visible = screen_height - 4 ! Leave room for title and border |
| 241 | panel%visible = .true. |
| 242 | end subroutine show_symbols_panel |
| 243 | |
| 244 | subroutine hide_symbols_panel(panel) |
| 245 | type(symbols_panel_t), intent(inout) :: panel |
| 246 | panel%visible = .false. |
| 247 | end subroutine hide_symbols_panel |
| 248 | |
| 249 | subroutine toggle_symbols_panel(panel, screen_width, screen_height) |
| 250 | type(symbols_panel_t), intent(inout) :: panel |
| 251 | integer, intent(in) :: screen_width, screen_height |
| 252 | |
| 253 | if (panel%visible) then |
| 254 | call hide_symbols_panel(panel) |
| 255 | else |
| 256 | call show_symbols_panel(panel, screen_width, screen_height) |
| 257 | end if |
| 258 | end subroutine toggle_symbols_panel |
| 259 | |
| 260 | function is_symbols_panel_visible(panel) result(visible) |
| 261 | type(symbols_panel_t), intent(in) :: panel |
| 262 | logical :: visible |
| 263 | visible = panel%visible |
| 264 | end function is_symbols_panel_visible |
| 265 | |
| 266 | subroutine render_symbols_panel(panel, screen_height) |
| 267 | type(symbols_panel_t), intent(in) :: panel |
| 268 | integer, intent(in) :: screen_height |
| 269 | integer :: row, i, start_idx, end_idx |
| 270 | character(len=256) :: line |
| 271 | character(len=20) :: location |
| 272 | character(len=5) :: icon |
| 273 | character(len=1), parameter :: ESC = char(27) |
| 274 | |
| 275 | if (.not. panel%visible) return |
| 276 | |
| 277 | ! Draw title bar with background color |
| 278 | row = 1 |
| 279 | call terminal_move_cursor(row, panel%panel_start_col) |
| 280 | call terminal_write(ESC // '[48;5;237m' // ESC // '[1m') ! Dark bg, bold |
| 281 | line = " Document Symbols" |
| 282 | if (panel%num_flat_symbols > 0) then |
| 283 | write(line, '(A,I0,A)') trim(line) // " (", panel%num_flat_symbols, ")" |
| 284 | end if |
| 285 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 286 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 287 | call terminal_write(ESC // '[0m') |
| 288 | |
| 289 | ! Draw separator |
| 290 | row = 2 |
| 291 | call terminal_move_cursor(row, panel%panel_start_col) |
| 292 | call terminal_write(ESC // '[48;5;237m' // repeat("-", panel%panel_width) // ESC // '[0m') |
| 293 | |
| 294 | ! Calculate visible range |
| 295 | start_idx = panel%scroll_offset + 1 |
| 296 | end_idx = min(panel%scroll_offset + panel%max_visible, panel%num_flat_symbols) |
| 297 | |
| 298 | ! Render symbols |
| 299 | if (panel%num_flat_symbols > 0) then |
| 300 | do i = start_idx, end_idx |
| 301 | row = row + 1 |
| 302 | call terminal_move_cursor(row, panel%panel_start_col) |
| 303 | |
| 304 | ! Background color based on selection |
| 305 | if (i == panel%selected_index) then |
| 306 | call terminal_write(ESC // '[48;5;240m') ! Highlight background |
| 307 | else |
| 308 | call terminal_write(ESC // '[48;5;235m') ! Normal background |
| 309 | end if |
| 310 | |
| 311 | ! Get symbol icon |
| 312 | icon = get_symbol_icon(panel%flat_symbols(i)%kind) |
| 313 | |
| 314 | ! Build line with indentation |
| 315 | line = " " |
| 316 | if (panel%flat_symbols(i)%depth > 0) then |
| 317 | line = trim(line) // repeat(" ", panel%flat_symbols(i)%depth) |
| 318 | end if |
| 319 | |
| 320 | ! Add expansion indicator for containers with children |
| 321 | if (allocated(panel%flat_symbols(i)%children) .and. & |
| 322 | panel%flat_symbols(i)%num_children > 0) then |
| 323 | if (panel%flat_symbols(i)%is_expanded) then |
| 324 | line = trim(line) // "v " |
| 325 | else |
| 326 | line = trim(line) // "> " |
| 327 | end if |
| 328 | else |
| 329 | line = trim(line) // " " |
| 330 | end if |
| 331 | |
| 332 | ! Add icon and name |
| 333 | if (allocated(panel%flat_symbols(i)%name)) then |
| 334 | line = trim(line) // icon // " " // trim(panel%flat_symbols(i)%name) |
| 335 | else |
| 336 | line = trim(line) // icon // " (unnamed)" |
| 337 | end if |
| 338 | |
| 339 | ! Add location if room |
| 340 | if (panel%show_details .or. i == panel%selected_index) then |
| 341 | write(location, '(A,I0)') " :", panel%flat_symbols(i)%line |
| 342 | if (len_trim(line) + len_trim(location) < panel%panel_width - 1) then |
| 343 | line = trim(line) // location |
| 344 | end if |
| 345 | end if |
| 346 | |
| 347 | ! Write line and pad to width |
| 348 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 349 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 350 | call terminal_write(ESC // '[0m') |
| 351 | end do |
| 352 | |
| 353 | ! Fill empty rows |
| 354 | do while (row < screen_height - 1) |
| 355 | row = row + 1 |
| 356 | call terminal_move_cursor(row, panel%panel_start_col) |
| 357 | call render_empty_line(panel%panel_width) |
| 358 | end do |
| 359 | else |
| 360 | ! No symbols message |
| 361 | row = row + 1 |
| 362 | call terminal_move_cursor(row, panel%panel_start_col) |
| 363 | call terminal_write(ESC // '[48;5;235m' // ESC // '[90m') |
| 364 | line = " No symbols found" |
| 365 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 366 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 367 | call terminal_write(ESC // '[0m') |
| 368 | |
| 369 | do while (row < screen_height - 1) |
| 370 | row = row + 1 |
| 371 | call terminal_move_cursor(row, panel%panel_start_col) |
| 372 | call render_empty_line(panel%panel_width) |
| 373 | end do |
| 374 | end if |
| 375 | |
| 376 | ! Draw hint bar at bottom |
| 377 | call terminal_move_cursor(screen_height, panel%panel_start_col) |
| 378 | call terminal_write(ESC // '[48;5;237m' // ESC // '[90m') |
| 379 | line = " j/k:nav Enter:jump Esc:close" |
| 380 | call terminal_write(line(1:min(len_trim(line), panel%panel_width))) |
| 381 | call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line)))) |
| 382 | call terminal_write(ESC // '[0m') |
| 383 | end subroutine render_symbols_panel |
| 384 | |
| 385 | function get_symbol_icon(kind) result(icon) |
| 386 | integer, intent(in) :: kind |
| 387 | character(len=5) :: icon |
| 388 | |
| 389 | select case(kind) |
| 390 | case(SYMBOL_FILE) |
| 391 | icon = "[F] " |
| 392 | case(SYMBOL_MODULE) |
| 393 | icon = "[M] " |
| 394 | case(SYMBOL_NAMESPACE) |
| 395 | icon = "[N] " |
| 396 | case(SYMBOL_PACKAGE) |
| 397 | icon = "[P] " |
| 398 | case(SYMBOL_CLASS) |
| 399 | icon = "[C] " |
| 400 | case(SYMBOL_METHOD) |
| 401 | icon = "m() " |
| 402 | case(SYMBOL_PROPERTY) |
| 403 | icon = ".p " |
| 404 | case(SYMBOL_FIELD) |
| 405 | icon = ".f " |
| 406 | case(SYMBOL_CONSTRUCTOR) |
| 407 | icon = "new " |
| 408 | case(SYMBOL_ENUM) |
| 409 | icon = "[E] " |
| 410 | case(SYMBOL_INTERFACE) |
| 411 | icon = "[I] " |
| 412 | case(SYMBOL_FUNCTION) |
| 413 | icon = "fn " |
| 414 | case(SYMBOL_VARIABLE) |
| 415 | icon = "var " |
| 416 | case(SYMBOL_CONSTANT) |
| 417 | icon = "const" |
| 418 | case(SYMBOL_STRING) |
| 419 | icon = "str " |
| 420 | case(SYMBOL_NUMBER) |
| 421 | icon = "num " |
| 422 | case(SYMBOL_BOOLEAN) |
| 423 | icon = "bool" |
| 424 | case(SYMBOL_ARRAY) |
| 425 | icon = "[] " |
| 426 | case(SYMBOL_OBJECT) |
| 427 | icon = "{} " |
| 428 | case(SYMBOL_STRUCT) |
| 429 | icon = "[S] " |
| 430 | case(SYMBOL_EVENT) |
| 431 | icon = "evt " |
| 432 | case(SYMBOL_OPERATOR) |
| 433 | icon = "op " |
| 434 | case(SYMBOL_TYPEPARAMETER) |
| 435 | icon = "<T> " |
| 436 | case default |
| 437 | icon = "- " |
| 438 | end select |
| 439 | end function get_symbol_icon |
| 440 | |
| 441 | subroutine render_empty_line(width) |
| 442 | integer, intent(in) :: width |
| 443 | character(len=1), parameter :: ESC = char(27) |
| 444 | |
| 445 | call terminal_write(ESC // '[48;5;235m' // repeat(" ", width) // ESC // '[0m') |
| 446 | end subroutine render_empty_line |
| 447 | |
| 448 | function symbols_panel_handle_key(panel, key) result(handled) |
| 449 | type(symbols_panel_t), intent(inout) :: panel |
| 450 | character(len=*), intent(in) :: key |
| 451 | logical :: handled |
| 452 | |
| 453 | handled = .false. |
| 454 | if (.not. panel%visible) return |
| 455 | |
| 456 | select case(trim(key)) |
| 457 | case('j', 'down') |
| 458 | ! Always handle to clamp at boundary |
| 459 | handled = .true. |
| 460 | if (panel%selected_index < panel%num_flat_symbols) then |
| 461 | panel%selected_index = panel%selected_index + 1 |
| 462 | ! Adjust scroll if needed |
| 463 | if (panel%selected_index > panel%scroll_offset + panel%max_visible) then |
| 464 | panel%scroll_offset = panel%selected_index - panel%max_visible |
| 465 | end if |
| 466 | end if |
| 467 | |
| 468 | case('k', 'up') |
| 469 | ! Always handle to clamp at boundary |
| 470 | handled = .true. |
| 471 | if (panel%selected_index > 1) then |
| 472 | panel%selected_index = panel%selected_index - 1 |
| 473 | ! Adjust scroll if needed |
| 474 | if (panel%selected_index <= panel%scroll_offset) then |
| 475 | panel%scroll_offset = max(0, panel%selected_index - 1) |
| 476 | end if |
| 477 | end if |
| 478 | |
| 479 | case('g') |
| 480 | ! Go to top |
| 481 | panel%selected_index = 1 |
| 482 | panel%scroll_offset = 0 |
| 483 | handled = .true. |
| 484 | |
| 485 | case('G') |
| 486 | ! Go to bottom |
| 487 | if (panel%num_flat_symbols > 0) then |
| 488 | panel%selected_index = panel%num_flat_symbols |
| 489 | panel%scroll_offset = max(0, panel%num_flat_symbols - panel%max_visible) |
| 490 | handled = .true. |
| 491 | end if |
| 492 | |
| 493 | case('space', 'l', 'right') |
| 494 | ! Toggle expansion of current symbol |
| 495 | if (panel%selected_index > 0 .and. panel%selected_index <= panel%num_flat_symbols) then |
| 496 | if (allocated(panel%flat_symbols(panel%selected_index)%children) .and. & |
| 497 | panel%flat_symbols(panel%selected_index)%num_children > 0) then |
| 498 | panel%flat_symbols(panel%selected_index)%is_expanded = & |
| 499 | .not. panel%flat_symbols(panel%selected_index)%is_expanded |
| 500 | ! Rebuild flat list |
| 501 | call rebuild_flat_list(panel) |
| 502 | handled = .true. |
| 503 | end if |
| 504 | end if |
| 505 | |
| 506 | case('h', 'left') |
| 507 | ! Collapse current or parent |
| 508 | handled = .true. |
| 509 | |
| 510 | case('d') |
| 511 | ! Toggle details |
| 512 | panel%show_details = .not. panel%show_details |
| 513 | handled = .true. |
| 514 | |
| 515 | case('esc', 'escape') |
| 516 | panel%visible = .false. |
| 517 | handled = .true. |
| 518 | |
| 519 | case('enter') |
| 520 | ! Jump to symbol handled by caller |
| 521 | handled = .true. |
| 522 | end select |
| 523 | end function symbols_panel_handle_key |
| 524 | |
| 525 | subroutine rebuild_flat_list(panel) |
| 526 | type(symbols_panel_t), intent(inout) :: panel |
| 527 | integer :: flat_count, idx |
| 528 | |
| 529 | if (.not. allocated(panel%symbols)) return |
| 530 | |
| 531 | ! Count new flat size |
| 532 | flat_count = count_flat_symbols(panel%symbols, panel%num_symbols) |
| 533 | |
| 534 | ! Reallocate if size changed |
| 535 | if (flat_count /= panel%num_flat_symbols) then |
| 536 | if (allocated(panel%flat_symbols)) deallocate(panel%flat_symbols) |
| 537 | allocate(panel%flat_symbols(flat_count)) |
| 538 | panel%num_flat_symbols = flat_count |
| 539 | end if |
| 540 | |
| 541 | ! Rebuild flat list |
| 542 | idx = 1 |
| 543 | call flatten_symbols(panel%symbols, panel%num_symbols, panel%flat_symbols, idx) |
| 544 | |
| 545 | ! Adjust selected index if out of bounds |
| 546 | if (panel%selected_index > panel%num_flat_symbols) then |
| 547 | panel%selected_index = max(1, panel%num_flat_symbols) |
| 548 | end if |
| 549 | end subroutine rebuild_flat_list |
| 550 | |
| 551 | function get_selected_symbol_location(panel, line, col) result(has_location) |
| 552 | type(symbols_panel_t), intent(in) :: panel |
| 553 | integer(int32), intent(out) :: line, col |
| 554 | logical :: has_location |
| 555 | |
| 556 | has_location = .false. |
| 557 | line = 1 |
| 558 | col = 1 |
| 559 | |
| 560 | if (panel%selected_index > 0 .and. panel%selected_index <= panel%num_flat_symbols) then |
| 561 | line = panel%flat_symbols(panel%selected_index)%line |
| 562 | col = panel%flat_symbols(panel%selected_index)%column |
| 563 | has_location = .true. |
| 564 | end if |
| 565 | end function get_selected_symbol_location |
| 566 | |
| 567 | end module symbols_panel_module |