fortrangoingonforty/facsimile / b845f07

Browse files

fix workspace symbols not loading or jumping

Authored by espadonne
SHA
b845f076110010944299c63ba277629ce5d5b27f
Parents
8c77d81
Tree
75660d3

2 changed files

StatusFile+-
M src/commands/command_handler_module.f90 54 57
M src/ui/workspace_symbols_panel_module.f90 281 83
src/commands/command_handler_module.f90modified
@@ -1535,93 +1535,90 @@ contains
15351535
             end block
15361536
 
15371537
         case('f6', 'alt-p')
1538
-            ! Workspace symbols (F6 or Alt+P for project)
1539
-            ! Workspace symbols (fuzzy search across project)
1538
+            ! Workspace symbols (F6 or Alt+P for project) - offcanvas panel with fzf-like filtering
15401539
             if (size(editor%tabs) > 0 .and. editor%active_tab_index > 0) then
15411540
                 block
15421541
                     use workspace_symbols_panel_module, only: show_workspace_symbols_panel, &
15431542
                                                                render_workspace_symbols_panel, &
15441543
                                                                workspace_symbols_panel_handle_key, &
15451544
                                                                hide_workspace_symbols_panel, &
1545
+                                                               is_workspace_symbols_panel_visible, &
15461546
                                                                get_selected_symbol, &
1547
+                                                               get_search_query, &
15471548
                                                                workspace_symbol_t
15481549
                     use input_handler_module, only: get_key_input
15491550
                     use lsp_server_manager_module, only: request_workspace_symbols, &
1550
-                        CAP_WORKSPACE_SYMBOLS_USE => CAP_WORKSPACE_SYMBOLS
1551
+                        CAP_WORKSPACE_SYMBOLS_USE => CAP_WORKSPACE_SYMBOLS, &
1552
+                        process_server_messages
15511553
                     integer :: request_id, status, ws_server
15521554
                     character(len=32) :: key_input
1555
+                    character(len=:), allocatable :: prev_query, curr_query
15531556
                     logical :: handled
15541557
                     type(workspace_symbol_t) :: selected_symbol
15551558
 
1556
-                    ! Show panel
1557
-                    call show_workspace_symbols_panel(editor%workspace_symbols_panel)
1559
+                    ! Toggle behavior - if already visible, hide and return
1560
+                    if (is_workspace_symbols_panel_visible(editor%workspace_symbols_panel)) then
1561
+                        call hide_workspace_symbols_panel(editor%workspace_symbols_panel)
1562
+                        call render_screen(buffer, editor)
1563
+                        return
1564
+                    end if
1565
+
1566
+                    ! Show panel with screen dimensions
1567
+                    call show_workspace_symbols_panel(editor%workspace_symbols_panel, &
1568
+                        editor%screen_cols, editor%screen_rows)
15581569
 
15591570
                     ! Get server with workspace symbols capability
15601571
                     ws_server = get_lsp_server_for_cap(editor, CAP_WORKSPACE_SYMBOLS_USE)
15611572
 
1562
-                    ! Send initial empty query to get all symbols
1563
-                    if (ws_server > 0) then
1564
-                        request_id = request_workspace_symbols(editor%lsp_manager, &
1565
-                            ws_server, '', handle_workspace_symbols_response_wrapper)
1566
-                    end if
1573
+                    ! Save editor state for LSP callback
1574
+                    saved_editor_for_callback => editor
15671575
 
1568
-                    call render_workspace_symbols_panel(editor%workspace_symbols_panel, editor%screen_rows)
1576
+                    ! Don't send initial empty query - pyright requires at least 1 char
1577
+                    ! Query will be sent when user starts typing
1578
+
1579
+                    prev_query = ''
15691580
 
15701581
                     ! Interactive loop
1571
-                    do
1582
+                    do while (is_workspace_symbols_panel_visible(editor%workspace_symbols_panel))
1583
+                        ! Process any pending LSP responses
1584
+                        call process_server_messages(editor%lsp_manager)
1585
+
1586
+                        ! Render the panel
1587
+                        call render_workspace_symbols_panel(editor%workspace_symbols_panel, editor%screen_rows)
1588
+
15721589
                         call get_key_input(key_input, status)
15731590
                         if (status /= 0) cycle
15741591
 
1575
-                        ! Handle special keys
1576
-                        if (key_input == 'enter') then
1592
+                        ! Handle enter specially - navigate to symbol
1593
+                        if (trim(key_input) == 'enter') then
15771594
                             selected_symbol = get_selected_symbol(editor%workspace_symbols_panel)
15781595
                             if (allocated(selected_symbol%file_uri) .and. len_trim(selected_symbol%file_uri) > 0) then
1579
-                                ! Navigate to the symbol location
1596
+                                call navigate_to_workspace_symbol(editor, buffer, selected_symbol, should_quit)
1597
+                            else if (allocated(selected_symbol%file_path) .and. len_trim(selected_symbol%file_path) > 0) then
1598
+                                ! Use file_path if file_uri not set
1599
+                                selected_symbol%file_uri = 'file://' // trim(selected_symbol%file_path)
15801600
                                 call navigate_to_workspace_symbol(editor, buffer, selected_symbol, should_quit)
15811601
                             end if
15821602
                             call hide_workspace_symbols_panel(editor%workspace_symbols_panel)
15831603
                             call render_screen(buffer, editor)
15841604
                             exit
1585
-                        else if (key_input == 'esc') then
1586
-                            call hide_workspace_symbols_panel(editor%workspace_symbols_panel)
1587
-                            call render_screen(buffer, editor)
1588
-                            exit
1589
-                        else if (key_input == 'backspace') then
1590
-                            if (editor%workspace_symbols_panel%search_pos > 0) then
1591
-                                editor%workspace_symbols_panel%search_query(editor%workspace_symbols_panel%search_pos:editor%workspace_symbols_panel%search_pos) = ' '
1592
-                                editor%workspace_symbols_panel%search_pos = editor%workspace_symbols_panel%search_pos - 1
1593
-                                ! Send new query to LSP
1594
-                                if (ws_server > 0) then
1595
-                                    request_id = request_workspace_symbols(editor%lsp_manager, &
1596
-                                        ws_server, &
1597
-                                        trim(editor%workspace_symbols_panel%search_query(1:editor%workspace_symbols_panel%search_pos)), &
1598
-                                        handle_workspace_symbols_response_wrapper)
1599
-                                end if
1600
-                            end if
1601
-                        else
1602
-                            ! Try navigation keys
1603
-                            call workspace_symbols_panel_handle_key(editor%workspace_symbols_panel, key_input, handled)
1604
-                            if (.not. handled) then
1605
-                                ! Regular character - add to search
1606
-                                if (len_trim(key_input) == 1) then
1607
-                                    if (iachar(key_input(1:1)) >= 32 .and. iachar(key_input(1:1)) < 127 .and. &
1608
-                                        editor%workspace_symbols_panel%search_pos < 255) then
1609
-                                        editor%workspace_symbols_panel%search_pos = editor%workspace_symbols_panel%search_pos + 1
1610
-                                        editor%workspace_symbols_panel%search_query(editor%workspace_symbols_panel%search_pos:editor%workspace_symbols_panel%search_pos) = key_input(1:1)
1611
-                                        ! Send new query to LSP
1612
-                                        if (ws_server > 0) then
1613
-                                            request_id = request_workspace_symbols(editor%lsp_manager, &
1614
-                                                ws_server, &
1615
-                                                trim(editor%workspace_symbols_panel%search_query(1:editor%workspace_symbols_panel%search_pos)), &
1616
-                                                handle_workspace_symbols_response_wrapper)
1617
-                                        end if
1618
-                                    end if
1619
-                                end if
1620
-                            end if
16211605
                         end if
16221606
 
1623
-                        call render_workspace_symbols_panel(editor%workspace_symbols_panel, editor%screen_rows)
1607
+                        ! Let the panel handle all other keys
1608
+                        handled = workspace_symbols_panel_handle_key(editor%workspace_symbols_panel, trim(key_input))
1609
+
1610
+                        ! Check if query changed - send new LSP request (only if query has content)
1611
+                        if (ws_server > 0) then
1612
+                            curr_query = get_search_query(editor%workspace_symbols_panel)
1613
+                            if (curr_query /= prev_query .and. len_trim(curr_query) > 0) then
1614
+                                request_id = request_workspace_symbols(editor%lsp_manager, &
1615
+                                    ws_server, curr_query, handle_workspace_symbols_response_wrapper)
1616
+                                prev_query = curr_query
1617
+                            end if
1618
+                        end if
16241619
                     end do
1620
+
1621
+                    call render_screen(buffer, editor)
16251622
                 end block
16261623
             end if
16271624
 
@@ -6603,7 +6600,7 @@ contains
66036600
                 line_num = json_get_number(start_obj, 'line', 0.0d0)
66046601
                 char_num = json_get_number(start_obj, 'character', 0.0d0)
66056602
                 symbols(i+1)%line = int(line_num)
6606
-                symbols(i+1)%character = int(char_num)
6603
+                symbols(i+1)%column = int(char_num)
66076604
             end if
66086605
         end do
66096606
 
@@ -6679,8 +6676,8 @@ contains
66796676
                     call switch_to_tab(editor, i)
66806677
                     ! Jump to the symbol's position
66816678
                     editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
6682
-                    editor%cursors(editor%active_cursor)%column = symbol%character + 1
6683
-                    editor%cursors(editor%active_cursor)%desired_column = symbol%character + 1
6679
+                    editor%cursors(editor%active_cursor)%column = symbol%column + 1
6680
+                    editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
66846681
                     editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
66856682
                     return
66866683
                 end if
@@ -6713,8 +6710,8 @@ contains
67136710
 
67146711
                 ! Navigate to the symbol's position
67156712
                 editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
6716
-                editor%cursors(editor%active_cursor)%column = symbol%character + 1
6717
-                editor%cursors(editor%active_cursor)%desired_column = symbol%character + 1
6713
+                editor%cursors(editor%active_cursor)%column = symbol%column + 1
6714
+                editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
67186715
                 editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
67196716
 
67206717
                 ! Sync editor state back to pane
src/ui/workspace_symbols_panel_module.f90modified
@@ -9,23 +9,28 @@ module workspace_symbols_panel_module
99
     public :: show_workspace_symbols_panel, hide_workspace_symbols_panel
1010
     public :: is_workspace_symbols_panel_visible, workspace_symbols_panel_handle_key
1111
     public :: set_workspace_symbols, get_selected_symbol
12
-    public :: render_workspace_symbols_panel
12
+    public :: render_workspace_symbols_panel, get_search_query
13
+    public :: add_search_char, delete_search_char, clear_search
1314
 
1415
     integer, parameter :: MAX_SYMBOLS = 1000
15
-    integer, parameter :: MAX_VISIBLE = 15
1616
 
1717
     type :: workspace_symbol_t
1818
         character(len=:), allocatable :: name
1919
         character(len=:), allocatable :: kind_name  ! "Function", "Class", etc.
2020
         character(len=:), allocatable :: container_name
2121
         character(len=:), allocatable :: file_uri
22
+        character(len=:), allocatable :: file_path  ! Extracted from URI
2223
         integer :: line = 0
23
-        integer :: character = 0
24
+        integer :: column = 0
25
+        integer :: kind = 0
2426
         integer :: score = 0  ! For fuzzy matching
2527
     end type workspace_symbol_t
2628
 
2729
     type :: workspace_symbols_panel_t
2830
         logical :: visible = .false.
31
+        integer :: panel_width = 60
32
+        integer :: panel_start_col = 1
33
+        integer :: max_visible = 20
2934
         type(workspace_symbol_t), allocatable :: all_symbols(:)
3035
         integer :: num_symbols = 0
3136
         type(workspace_symbol_t), allocatable :: filtered_symbols(:)
@@ -34,6 +39,7 @@ module workspace_symbols_panel_module
3439
         integer :: scroll_offset = 0
3540
         character(len=256) :: search_query = ''
3641
         integer :: search_pos = 0
42
+        logical :: needs_lsp_query = .false.  ! Flag to trigger LSP request
3743
     end type workspace_symbols_panel_t
3844
 
3945
 contains
@@ -48,6 +54,7 @@ contains
4854
         panel%scroll_offset = 0
4955
         panel%search_query = ''
5056
         panel%search_pos = 0
57
+        panel%needs_lsp_query = .false.
5158
 
5259
         allocate(panel%all_symbols(MAX_SYMBOLS))
5360
         allocate(panel%filtered_symbols(MAX_SYMBOLS))
@@ -63,6 +70,7 @@ contains
6370
                 if (allocated(panel%all_symbols(i)%kind_name)) deallocate(panel%all_symbols(i)%kind_name)
6471
                 if (allocated(panel%all_symbols(i)%container_name)) deallocate(panel%all_symbols(i)%container_name)
6572
                 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)
6674
             end do
6775
             deallocate(panel%all_symbols)
6876
         end if
@@ -70,17 +78,22 @@ contains
7078
         if (allocated(panel%filtered_symbols)) deallocate(panel%filtered_symbols)
7179
     end subroutine cleanup_workspace_symbols_panel
7280
 
73
-    subroutine show_workspace_symbols_panel(panel)
81
+    subroutine show_workspace_symbols_panel(panel, screen_width, screen_height)
7482
         type(workspace_symbols_panel_t), intent(inout) :: panel
83
+        integer, intent(in) :: screen_width, screen_height
7584
 
7685
         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
7789
         panel%search_query = ''
7890
         panel%search_pos = 0
7991
         panel%selected_index = 1
8092
         panel%scroll_offset = 0
93
+        panel%needs_lsp_query = .true.  ! Request initial symbols
8194
 
8295
         ! Initially show all symbols
83
-        call filter_symbols(panel, '')
96
+        call filter_symbols(panel)
8497
     end subroutine show_workspace_symbols_panel
8598
 
8699
     subroutine hide_workspace_symbols_panel(panel)
@@ -94,6 +107,47 @@ contains
94107
         visible = panel%visible
95108
     end function is_workspace_symbols_panel_visible
96109
 
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
+
97151
     subroutine set_workspace_symbols(panel, symbols, count)
98152
         type(workspace_symbols_panel_t), intent(inout) :: panel
99153
         type(workspace_symbol_t), intent(in) :: symbols(:)
@@ -104,26 +158,68 @@ contains
104158
         panel%num_symbols = copy_count
105159
 
106160
         do i = 1, copy_count
107
-            panel%all_symbols(i) = symbols(i)
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
108196
         end do
109197
 
110198
         ! Refilter with current query
111
-        call filter_symbols(panel, trim(panel%search_query(1:panel%search_pos)))
199
+        call filter_symbols(panel)
112200
     end subroutine set_workspace_symbols
113201
 
114
-    subroutine filter_symbols(panel, query)
202
+    subroutine filter_symbols(panel)
115203
         type(workspace_symbols_panel_t), intent(inout) :: panel
116
-        character(len=*), intent(in) :: query
204
+        character(len=:), allocatable :: query
117205
         integer :: i, score
118206
 
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
+
119213
         panel%num_filtered = 0
120214
 
121215
         do i = 1, panel%num_symbols
122
-            score = fuzzy_match_score(panel%all_symbols(i)%name, query)
123
-            if (score > 0) then
124
-                panel%num_filtered = panel%num_filtered + 1
125
-                panel%filtered_symbols(panel%num_filtered) = panel%all_symbols(i)
126
-                panel%filtered_symbols(panel%num_filtered)%score = score
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
127223
             end if
128224
         end do
129225
 
@@ -225,110 +321,212 @@ contains
225321
 
226322
         if (panel%num_filtered > 0 .and. panel%selected_index <= panel%num_filtered) then
227323
             sym = panel%filtered_symbols(panel%selected_index)
228
-        else
229
-            sym%file_uri = ''
230324
         end if
231325
     end function get_selected_symbol
232326
 
233
-    subroutine workspace_symbols_panel_handle_key(panel, key, handled)
327
+    function workspace_symbols_panel_handle_key(panel, key) result(handled)
234328
         type(workspace_symbols_panel_t), intent(inout) :: panel
235329
         character(len=*), intent(in) :: key
236
-        logical, intent(out) :: handled
330
+        logical :: handled
237331
 
238
-        handled = .true.
332
+        handled = .false.
333
+        if (.not. panel%visible) return
239334
 
240
-        select case(key)
241
-        case('up', 'ctrl-k', 'k')
242
-            if (panel%selected_index > 1) then
243
-                panel%selected_index = panel%selected_index - 1
244
-                if (panel%selected_index < panel%scroll_offset + 1) then
245
-                    panel%scroll_offset = panel%selected_index - 1
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
246343
                 end if
247344
             end if
248345
 
249
-        case('down', 'ctrl-j', 'j')
250
-            if (panel%selected_index < panel%num_filtered) then
251
-                panel%selected_index = panel%selected_index + 1
252
-                if (panel%selected_index > panel%scroll_offset + MAX_VISIBLE) then
253
-                    panel%scroll_offset = panel%selected_index - MAX_VISIBLE
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)
254353
                 end if
255354
             end if
256355
 
257
-        case('esc')
356
+        case('ctrl-u')
357
+            ! Clear search
358
+            call clear_search(panel)
359
+            handled = .true.
360
+
361
+        case('esc', 'escape')
258362
             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.
259372
 
260373
         case default
261
-            handled = .false.
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
262381
         end select
263
-    end subroutine workspace_symbols_panel_handle_key
382
+    end function workspace_symbols_panel_handle_key
264383
 
265
-    subroutine render_workspace_symbols_panel(panel, screen_rows)
384
+    subroutine render_workspace_symbols_panel(panel, screen_height)
266385
         type(workspace_symbols_panel_t), intent(in) :: panel
267
-        integer, intent(in) :: screen_rows
268
-        integer :: i, visible_start, visible_end, row
269
-        character(len=256) :: line, kind_tag, container_info
270
-        type(workspace_symbol_t) :: sym
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)
271390
 
272391
         if (.not. panel%visible) return
273392
 
274
-        ! Clear and draw header
275
-        call terminal_move_cursor(screen_rows - MAX_VISIBLE - 2, 1)
276
-        call terminal_write(repeat(' ', 120))
277
-        call terminal_move_cursor(screen_rows - MAX_VISIBLE - 2, 1)
278
-        call terminal_write('Workspace Symbols (type to search, Esc to cancel)')
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')
279417
 
280
-        ! Draw search query
281
-        call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 1)
282
-        call terminal_write(repeat(' ', 120))
283
-        call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 1)
284
-        call terminal_write('> ' // trim(panel%search_query))
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')
285422
 
286423
         ! Calculate visible range
287
-        visible_start = panel%scroll_offset + 1
288
-        visible_end = min(visible_start + MAX_VISIBLE - 1, panel%num_filtered)
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
289439
 
290
-        ! Draw symbols
291
-        row = screen_rows - MAX_VISIBLE
292
-        do i = visible_start, visible_end
293
-            sym = panel%filtered_symbols(i)
440
+                ! Build display line: icon + name + file:line
441
+                line = " "
294442
 
295
-            call terminal_move_cursor(row, 1)
296
-            call terminal_write(repeat(' ', 120))
297
-            call terminal_move_cursor(row, 1)
443
+                ! Add kind indicator
444
+                if (allocated(panel%filtered_symbols(i)%kind_name)) then
445
+                    line = trim(line) // "[" // trim(panel%filtered_symbols(i)%kind_name(1:min(3, len_trim(panel%filtered_symbols(i)%kind_name)))) // "] "
446
+                else
447
+                    line = trim(line) // "    "
448
+                end if
298449
 
299
-            ! Build line with kind, name, and container
300
-            if (len_trim(sym%kind_name) > 0) then
301
-                write(kind_tag, '(A,A,A)') '[', trim(sym%kind_name), '] '
302
-            else
303
-                kind_tag = ''
304
-            end if
450
+                ! Add symbol name
451
+                if (allocated(panel%filtered_symbols(i)%name)) then
452
+                    line = trim(line) // trim(panel%filtered_symbols(i)%name)
453
+                end if
305454
 
306
-            if (len_trim(sym%container_name) > 0) then
307
-                write(container_info, '(A,A)') ' in ', trim(sym%container_name)
308
-            else
309
-                container_info = ''
310
-            end if
455
+                ! Add file info on selected item
456
+                if (i == panel%selected_index) then
457
+                    if (allocated(panel%filtered_symbols(i)%file_path)) then
458
+                        write(file_info, '(A,A,A,I0)') " (", &
459
+                            trim(get_basename(panel%filtered_symbols(i)%file_path)), &
460
+                            ":", panel%filtered_symbols(i)%line
461
+                        file_info = trim(file_info) // ")"
462
+                        if (len_trim(line) + len_trim(file_info) < panel%panel_width - 1) then
463
+                            line = trim(line) // ESC // '[90m' // trim(file_info) // ESC // '[0m' // ESC // '[48;5;240m'
464
+                        end if
465
+                    end if
466
+                end if
311467
 
312
-            if (i == panel%selected_index) then
313
-                ! Highlight selected item
314
-                write(line, '(A,A,A,A)') '> ', trim(kind_tag), trim(sym%name), trim(container_info)
468
+                ! Write line and pad
469
+                call terminal_write(line(1:min(len_trim(line), panel%panel_width)))
470
+                call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line))))
471
+                call terminal_write(ESC // '[0m')
472
+            end do
473
+
474
+            ! Fill empty rows
475
+            do while (row < screen_height - 1)
476
+                row = row + 1
477
+                call terminal_move_cursor(row, panel%panel_start_col)
478
+                call terminal_write(ESC // '[48;5;235m' // repeat(" ", panel%panel_width) // ESC // '[0m')
479
+            end do
480
+        else
481
+            ! No symbols message
482
+            row = row + 1
483
+            call terminal_move_cursor(row, panel%panel_start_col)
484
+            call terminal_write(ESC // '[48;5;235m' // ESC // '[90m')
485
+            if (panel%search_pos == 0) then
486
+                line = " Type to search..."
487
+            else if (panel%num_symbols == 0) then
488
+                line = " Searching..."
315489
             else
316
-                write(line, '(A,A,A,A)') '  ', trim(kind_tag), trim(sym%name), trim(container_info)
490
+                line = " No matching symbols"
317491
             end if
492
+            call terminal_write(line(1:min(len_trim(line), panel%panel_width)))
493
+            call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line))))
494
+            call terminal_write(ESC // '[0m')
495
+
496
+            do while (row < screen_height - 1)
497
+                row = row + 1
498
+                call terminal_move_cursor(row, panel%panel_start_col)
499
+                call terminal_write(ESC // '[48;5;235m' // repeat(" ", panel%panel_width) // ESC // '[0m')
500
+            end do
501
+        end if
318502
 
319
-            call terminal_write(trim(line))
320
-            row = row + 1
321
-        end do
503
+        ! Draw hint bar at bottom
504
+        call terminal_move_cursor(screen_height, panel%panel_start_col)
505
+        call terminal_write(ESC // '[48;5;237m' // ESC // '[90m')
506
+        line = " Enter:open  Esc:close  Ctrl-U:clear"
507
+        call terminal_write(line(1:min(len_trim(line), panel%panel_width)))
508
+        call terminal_write(repeat(" ", max(0, panel%panel_width - len_trim(line))))
509
+        call terminal_write(ESC // '[0m')
510
+    end subroutine render_workspace_symbols_panel
322511
 
323
-        ! Clear remaining lines
324
-        do while (row <= screen_rows)
325
-            call terminal_move_cursor(row, 1)
326
-            call terminal_write(repeat(' ', 120))
327
-            row = row + 1
512
+    function get_basename(path) result(basename)
513
+        character(len=*), intent(in) :: path
514
+        character(len=256) :: basename
515
+        integer :: i, last_slash
516
+
517
+        last_slash = 0
518
+        do i = len_trim(path), 1, -1
519
+            if (path(i:i) == '/') then
520
+                last_slash = i
521
+                exit
522
+            end if
328523
         end do
329524
 
330
-        ! Position cursor at end of search query
331
-        call terminal_move_cursor(screen_rows - MAX_VISIBLE - 1, 3 + panel%search_pos)
332
-    end subroutine render_workspace_symbols_panel
525
+        if (last_slash > 0 .and. last_slash < len_trim(path)) then
526
+            basename = path(last_slash+1:len_trim(path))
527
+        else
528
+            basename = path
529
+        end if
530
+    end function get_basename
333531
 
334532
 end module workspace_symbols_panel_module