Fortran · 20765 bytes Raw Blame History
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