fortrangoingonforty/facsimile / 42f9bdb

Browse files

Implement multiline syntax highlighting and wire up symbols panels

Multiline comments (/* */) and strings (""", `, etc.) were not
highlighted across multiple lines. This made reading C, JavaScript,
Python, and other languages difficult.

Also replaced stub panel renderers with real implementations so
document symbols and workspace symbols are now fully functional.
Authored by espadonne
SHA
42f9bdb61ce8251cd911e6b3b9920068c33f2598
Parents
3d6f981
Tree
295ac40

3 changed files

StatusFile+-
M src/commands/command_handler_module.f90 16 16
M src/syntax/syntax_highlighter_module.f90 135 5
M src/terminal/renderer_module.f90 10 69
src/commands/command_handler_module.f90modified
@@ -5798,9 +5798,9 @@ contains
5798
     ! The response parsing logic is ready but needs proper callback integration
5798
     ! The response parsing logic is ready but needs proper callback integration
5799
 
5799
 
5800
     ! Wrapper callback that matches the LSP callback signature
5800
     ! Wrapper callback that matches the LSP callback signature
5801
-    subroutine handle_references_response_wrapper(request_id, response)
5801
+    subroutine handle_references_response_wrapper(unused_request_id, response)
5802
         use lsp_protocol_module, only: lsp_message_t
5802
         use lsp_protocol_module, only: lsp_message_t
5803
-        integer, intent(in) :: request_id
5803
+        integer, intent(in) :: unused_request_id
5804
         type(lsp_message_t), intent(in) :: response
5804
         type(lsp_message_t), intent(in) :: response
5805
 
5805
 
5806
         ! Call the actual handler with saved editor state
5806
         ! Call the actual handler with saved editor state
@@ -5907,9 +5907,9 @@ contains
5907
     end subroutine handle_references_response_impl
5907
     end subroutine handle_references_response_impl
5908
 
5908
 
5909
     ! Wrapper callback that matches the LSP callback signature for code actions
5909
     ! Wrapper callback that matches the LSP callback signature for code actions
5910
-    subroutine handle_code_actions_response_wrapper(request_id, response)
5910
+    subroutine handle_code_actions_response_wrapper(unused_request_id, response)
5911
         use lsp_protocol_module, only: lsp_message_t
5911
         use lsp_protocol_module, only: lsp_message_t
5912
-        integer, intent(in) :: request_id
5912
+        integer, intent(in) :: unused_request_id
5913
         type(lsp_message_t), intent(in) :: response
5913
         type(lsp_message_t), intent(in) :: response
5914
 
5914
 
5915
         ! Call the actual handler with saved editor state
5915
         ! Call the actual handler with saved editor state
@@ -5994,9 +5994,9 @@ contains
5994
     end subroutine handle_code_actions_response_impl
5994
     end subroutine handle_code_actions_response_impl
5995
 
5995
 
5996
     ! Wrapper callback that matches the LSP callback signature for symbols
5996
     ! Wrapper callback that matches the LSP callback signature for symbols
5997
-    subroutine handle_symbols_response_wrapper(request_id, response)
5997
+    subroutine handle_symbols_response_wrapper(unused_request_id, response)
5998
         use lsp_protocol_module, only: lsp_message_t
5998
         use lsp_protocol_module, only: lsp_message_t
5999
-        integer, intent(in) :: request_id
5999
+        integer, intent(in) :: unused_request_id
6000
         type(lsp_message_t), intent(in) :: response
6000
         type(lsp_message_t), intent(in) :: response
6001
 
6001
 
6002
         ! Call the actual handler with saved editor state
6002
         ! Call the actual handler with saved editor state
@@ -6155,9 +6155,9 @@ contains
6155
     end subroutine handle_symbols_response_impl
6155
     end subroutine handle_symbols_response_impl
6156
 
6156
 
6157
     ! Wrapper callback that matches the LSP callback signature for signature help
6157
     ! Wrapper callback that matches the LSP callback signature for signature help
6158
-    subroutine handle_signature_response_wrapper(request_id, response)
6158
+    subroutine handle_signature_response_wrapper(unused_request_id, response)
6159
         use lsp_protocol_module, only: lsp_message_t
6159
         use lsp_protocol_module, only: lsp_message_t
6160
-        integer, intent(in) :: request_id
6160
+        integer, intent(in) :: unused_request_id
6161
         type(lsp_message_t), intent(in) :: response
6161
         type(lsp_message_t), intent(in) :: response
6162
 
6162
 
6163
         ! Call the actual handler with saved editor state
6163
         ! Call the actual handler with saved editor state
@@ -6167,10 +6167,10 @@ contains
6167
     end subroutine handle_signature_response_wrapper
6167
     end subroutine handle_signature_response_wrapper
6168
 
6168
 
6169
     ! Wrapper callback that matches the LSP callback signature for rename
6169
     ! Wrapper callback that matches the LSP callback signature for rename
6170
-    subroutine handle_rename_response_wrapper(request_id, response)
6170
+    subroutine handle_rename_response_wrapper(unused_request_id, response)
6171
         use lsp_protocol_module, only: lsp_message_t
6171
         use lsp_protocol_module, only: lsp_message_t
6172
         use json_module, only: json_value_t, json_stringify
6172
         use json_module, only: json_value_t, json_stringify
6173
-        integer, intent(in) :: request_id
6173
+        integer, intent(in) :: unused_request_id
6174
         type(lsp_message_t), intent(in) :: response
6174
         type(lsp_message_t), intent(in) :: response
6175
 
6175
 
6176
         character(len=:), allocatable :: result_str
6176
         character(len=:), allocatable :: result_str
@@ -6206,11 +6206,11 @@ contains
6206
     end subroutine handle_rename_response_wrapper
6206
     end subroutine handle_rename_response_wrapper
6207
 
6207
 
6208
     ! Wrapper callback for formatting response
6208
     ! Wrapper callback for formatting response
6209
-    subroutine handle_formatting_response_wrapper(request_id, response)
6209
+    subroutine handle_formatting_response_wrapper(unused_request_id, response)
6210
         use lsp_protocol_module, only: lsp_message_t
6210
         use lsp_protocol_module, only: lsp_message_t
6211
         use json_module, only: json_value_t, json_array_size, json_get_array_element, &
6211
         use json_module, only: json_value_t, json_array_size, json_get_array_element, &
6212
                                json_get_object, json_get_string, json_get_number, json_has_key
6212
                                json_get_object, json_get_string, json_get_number, json_has_key
6213
-        integer, intent(in) :: request_id
6213
+        integer, intent(in) :: unused_request_id
6214
         type(lsp_message_t), intent(in) :: response
6214
         type(lsp_message_t), intent(in) :: response
6215
 
6215
 
6216
         type(json_value_t) :: edits_array, edit_obj, range_obj, start_obj, end_obj
6216
         type(json_value_t) :: edits_array, edit_obj, range_obj, start_obj, end_obj
@@ -6672,11 +6672,11 @@ contains
6672
     end subroutine execute_palette_command
6672
     end subroutine execute_palette_command
6673
 
6673
 
6674
     ! Handle workspace symbols LSP response
6674
     ! Handle workspace symbols LSP response
6675
-    subroutine handle_workspace_symbols_response_wrapper(request_id, response)
6675
+    subroutine handle_workspace_symbols_response_wrapper(unused_request_id, response)
6676
         use lsp_protocol_module, only: lsp_message_t
6676
         use lsp_protocol_module, only: lsp_message_t
6677
         use json_module
6677
         use json_module
6678
         use workspace_symbols_panel_module, only: workspace_symbol_t, set_workspace_symbols
6678
         use workspace_symbols_panel_module, only: workspace_symbol_t, set_workspace_symbols
6679
-        integer, intent(in) :: request_id
6679
+        integer, intent(in) :: unused_request_id
6680
         type(lsp_message_t), intent(in) :: response
6680
         type(lsp_message_t), intent(in) :: response
6681
         type(json_value_t) :: symbol_obj, location_obj, range_obj, start_obj
6681
         type(json_value_t) :: symbol_obj, location_obj, range_obj, start_obj
6682
         integer :: num_symbols, i
6682
         integer :: num_symbols, i
@@ -6943,9 +6943,9 @@ contains
6943
     ! ==================================================
6943
     ! ==================================================
6944
 
6944
 
6945
     ! Wrapper callback for go to definition
6945
     ! Wrapper callback for go to definition
6946
-    subroutine handle_definition_response_wrapper(request_id, response)
6946
+    subroutine handle_definition_response_wrapper(unused_request_id, response)
6947
         use lsp_protocol_module, only: lsp_message_t
6947
         use lsp_protocol_module, only: lsp_message_t
6948
-        integer, intent(in) :: request_id
6948
+        integer, intent(in) :: unused_request_id
6949
         type(lsp_message_t), intent(in) :: response
6949
         type(lsp_message_t), intent(in) :: response
6950
 
6950
 
6951
         ! Call actual handler with saved editor state
6951
         ! Call actual handler with saved editor state
src/syntax/syntax_highlighter_module.f90modified
@@ -166,8 +166,12 @@ contains
166
         do while (i <= line_len)
166
         do while (i <= line_len)
167
             ch = line(i:i)
167
             ch = line(i:i)
168
 
168
 
169
+            ! Check for multiline comment start
170
+            if (check_multiline_comment_start(highlighter, line, i)) then
171
+                call process_multiline_comment_start(highlighter, line, tokens, token_count, i)
172
+
169
             ! Check for single-line comment
173
             ! Check for single-line comment
170
-            if (check_comment_start(highlighter, line, i)) then
174
+            else if (check_comment_start(highlighter, line, i)) then
171
                 token_count = token_count + 1
175
                 token_count = token_count + 1
172
                 tokens(token_count)%type = TOKEN_COMMENT
176
                 tokens(token_count)%type = TOKEN_COMMENT
173
                 tokens(token_count)%start_col = i
177
                 tokens(token_count)%start_col = i
@@ -396,6 +400,22 @@ contains
396
         end if
400
         end if
397
     end function check_comment_start
401
     end function check_comment_start
398
 
402
 
403
+    function check_multiline_comment_start(highlighter, line, pos) result(res)
404
+        type(syntax_highlighter_t), intent(in) :: highlighter
405
+        character(len=*), intent(in) :: line
406
+        integer, intent(in) :: pos
407
+        logical :: res
408
+        integer :: comment_len
409
+
410
+        res = .false.
411
+        if (highlighter%current_lang%comment_start /= "") then
412
+            comment_len = len_trim(highlighter%current_lang%comment_start)
413
+            if (pos + comment_len - 1 <= len(line)) then
414
+                res = line(pos:pos+comment_len-1) == trim(highlighter%current_lang%comment_start)
415
+            end if
416
+        end if
417
+    end function check_multiline_comment_start
418
+
399
     function check_string_start(highlighter, line, pos) result(res)
419
     function check_string_start(highlighter, line, pos) result(res)
400
         type(syntax_highlighter_t), intent(in) :: highlighter
420
         type(syntax_highlighter_t), intent(in) :: highlighter
401
         character(len=*), intent(in) :: line
421
         character(len=*), intent(in) :: line
@@ -424,7 +444,7 @@ contains
424
         integer, intent(inout) :: token_count, pos
444
         integer, intent(inout) :: token_count, pos
425
         integer :: i, start_pos, delim_len, line_len
445
         integer :: i, start_pos, delim_len, line_len
426
         character(len=:), allocatable :: delimiter
446
         character(len=:), allocatable :: delimiter
427
-        logical :: found_end
447
+        logical :: found_end, is_multiline
428
 
448
 
429
         line_len = len(line)
449
         line_len = len(line)
430
         start_pos = pos
450
         start_pos = pos
@@ -443,6 +463,11 @@ contains
443
             end if
463
             end if
444
         end do
464
         end do
445
 
465
 
466
+        ! Check if this is a multiline-capable delimiter
467
+        ! Python: """ or ''' (length 3)
468
+        ! JavaScript/TypeScript: ` (template literals)
469
+        is_multiline = (len(delimiter) >= 3) .or. (delimiter == '`')
470
+
446
         ! Move past opening delimiter
471
         ! Move past opening delimiter
447
         pos = pos + len(delimiter)
472
         pos = pos + len(delimiter)
448
 
473
 
@@ -473,6 +498,11 @@ contains
473
 
498
 
474
         ! Handle unclosed string
499
         ! Handle unclosed string
475
         if (.not. found_end) then
500
         if (.not. found_end) then
501
+            if (is_multiline) then
502
+                ! Enter multiline string mode
503
+                highlighter%in_multiline_string = .true.
504
+                highlighter%string_delimiter = delimiter
505
+            end if
476
             pos = line_len + 1
506
             pos = line_len + 1
477
         end if
507
         end if
478
     end subroutine process_string
508
     end subroutine process_string
@@ -949,13 +979,83 @@ contains
949
         highlighter%enabled = .true.
979
         highlighter%enabled = .true.
950
     end subroutine load_markdown_syntax
980
     end subroutine load_markdown_syntax
951
 
981
 
952
-    ! Stub implementations for multiline handling
982
+    ! Handle multiline comment start (when we encounter /* on a line)
983
+    subroutine process_multiline_comment_start(highlighter, line, tokens, token_count, pos)
984
+        type(syntax_highlighter_t), intent(inout) :: highlighter
985
+        character(len=*), intent(in) :: line
986
+        type(token_t), intent(inout) :: tokens(:)
987
+        integer, intent(inout) :: token_count, pos
988
+        integer :: start_pos, end_pos, comment_start_len, comment_end_len, line_len
989
+
990
+        line_len = len(line)
991
+        start_pos = pos
992
+        comment_start_len = len_trim(highlighter%current_lang%comment_start)
993
+        comment_end_len = len_trim(highlighter%current_lang%comment_end)
994
+
995
+        ! Skip past the opening delimiter
996
+        pos = pos + comment_start_len
997
+
998
+        ! Look for closing delimiter on the same line
999
+        end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end))
1000
+
1001
+        if (end_pos > 0) then
1002
+            ! Found closing delimiter on same line - this is a complete comment
1003
+            end_pos = pos + end_pos - 1 + comment_end_len - 1
1004
+
1005
+            token_count = token_count + 1
1006
+            tokens(token_count)%type = TOKEN_COMMENT
1007
+            tokens(token_count)%start_col = start_pos
1008
+            tokens(token_count)%end_col = end_pos
1009
+
1010
+            pos = end_pos + 1
1011
+        else
1012
+            ! Closing delimiter not found - rest of line is comment, enter multiline mode
1013
+            token_count = token_count + 1
1014
+            tokens(token_count)%type = TOKEN_COMMENT
1015
+            tokens(token_count)%start_col = start_pos
1016
+            tokens(token_count)%end_col = line_len
1017
+
1018
+            highlighter%in_multiline_comment = .true.
1019
+            pos = line_len + 1
1020
+        end if
1021
+    end subroutine process_multiline_comment_start
1022
+
1023
+    ! Handle multiline comment continuation (when we start a line in comment mode)
953
     subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos)
1024
     subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos)
954
         type(syntax_highlighter_t), intent(inout) :: highlighter
1025
         type(syntax_highlighter_t), intent(inout) :: highlighter
955
         character(len=*), intent(in) :: line
1026
         character(len=*), intent(in) :: line
956
         type(token_t), intent(inout) :: tokens(:)
1027
         type(token_t), intent(inout) :: tokens(:)
957
         integer, intent(inout) :: token_count, pos
1028
         integer, intent(inout) :: token_count, pos
958
-        ! TODO: Implement multiline comment handling
1029
+        integer :: end_pos, comment_end_len, line_len
1030
+
1031
+        line_len = len(line)
1032
+        comment_end_len = len_trim(highlighter%current_lang%comment_end)
1033
+
1034
+        ! Look for closing delimiter
1035
+        end_pos = index(line(pos:), trim(highlighter%current_lang%comment_end))
1036
+
1037
+        if (end_pos > 0) then
1038
+            ! Found closing delimiter on this line
1039
+            end_pos = pos + end_pos - 1 + comment_end_len - 1
1040
+
1041
+            token_count = token_count + 1
1042
+            tokens(token_count)%type = TOKEN_COMMENT
1043
+            tokens(token_count)%start_col = pos
1044
+            tokens(token_count)%end_col = end_pos
1045
+
1046
+            ! Exit multiline comment mode
1047
+            highlighter%in_multiline_comment = .false.
1048
+            pos = end_pos + 1
1049
+        else
1050
+            ! Closing delimiter not found - entire rest of line is comment
1051
+            token_count = token_count + 1
1052
+            tokens(token_count)%type = TOKEN_COMMENT
1053
+            tokens(token_count)%start_col = pos
1054
+            tokens(token_count)%end_col = line_len
1055
+
1056
+            ! Stay in multiline comment mode
1057
+            pos = line_len + 1
1058
+        end if
959
     end subroutine process_multiline_comment
1059
     end subroutine process_multiline_comment
960
 
1060
 
961
     subroutine process_multiline_string(highlighter, line, tokens, token_count, pos)
1061
     subroutine process_multiline_string(highlighter, line, tokens, token_count, pos)
@@ -963,7 +1063,37 @@ contains
963
         character(len=*), intent(in) :: line
1063
         character(len=*), intent(in) :: line
964
         type(token_t), intent(inout) :: tokens(:)
1064
         type(token_t), intent(inout) :: tokens(:)
965
         integer, intent(inout) :: token_count, pos
1065
         integer, intent(inout) :: token_count, pos
966
-        ! TODO: Implement multiline string handling
1066
+        integer :: end_pos, delim_len, line_len
1067
+
1068
+        line_len = len(line)
1069
+        delim_len = len(highlighter%string_delimiter)
1070
+
1071
+        ! Look for closing delimiter
1072
+        end_pos = index(line(pos:), highlighter%string_delimiter)
1073
+
1074
+        if (end_pos > 0) then
1075
+            ! Found closing delimiter on this line
1076
+            end_pos = pos + end_pos - 1 + delim_len - 1
1077
+
1078
+            token_count = token_count + 1
1079
+            tokens(token_count)%type = TOKEN_STRING
1080
+            tokens(token_count)%start_col = pos
1081
+            tokens(token_count)%end_col = end_pos
1082
+
1083
+            ! Exit multiline string mode
1084
+            highlighter%in_multiline_string = .false.
1085
+            highlighter%string_delimiter = ""
1086
+            pos = end_pos + 1
1087
+        else
1088
+            ! Closing delimiter not found - entire rest of line is string
1089
+            token_count = token_count + 1
1090
+            tokens(token_count)%type = TOKEN_STRING
1091
+            tokens(token_count)%start_col = pos
1092
+            tokens(token_count)%end_col = line_len
1093
+
1094
+            ! Stay in multiline string mode
1095
+            pos = line_len + 1
1096
+        end if
967
     end subroutine process_multiline_string
1097
     end subroutine process_multiline_string
968
 
1098
 
969
 end module syntax_highlighter_module
1099
 end module syntax_highlighter_module
src/terminal/renderer_module.f90modified
@@ -1971,17 +1971,16 @@ contains
1971
         if (n_panes == 0) return
1971
         if (n_panes == 0) return
1972
 
1972
 
1973
         if (n_panes > 1) then
1973
         if (n_panes > 1) then
1974
-            call render_cursor_for_panes_in_lsp_view(editor, start_col, width)
1974
+            call render_cursor_for_panes_in_lsp_view(editor)
1975
         else
1975
         else
1976
             call render_cursor_in_pane(editor, start_col, width)
1976
             call render_cursor_in_pane(editor, start_col, width)
1977
         end if
1977
         end if
1978
     end subroutine render_cursor_for_lsp_panel
1978
     end subroutine render_cursor_for_lsp_panel
1979
 
1979
 
1980
     ! Helper to render cursor for multiple panes when LSP panel is visible
1980
     ! Helper to render cursor for multiple panes when LSP panel is visible
1981
-    subroutine render_cursor_for_panes_in_lsp_view(editor, start_col, width)
1981
+    subroutine render_cursor_for_panes_in_lsp_view(editor)
1982
         use editor_state_module, only: pane_t
1982
         use editor_state_module, only: pane_t
1983
         type(editor_state_t), intent(inout) :: editor
1983
         type(editor_state_t), intent(inout) :: editor
1984
-        integer, intent(in) :: start_col, width
1985
         integer :: tab_idx, active_pane
1984
         integer :: tab_idx, active_pane
1986
         type(pane_t) :: pane
1985
         type(pane_t) :: pane
1987
         integer :: screen_row, screen_col
1986
         integer :: screen_row, screen_col
@@ -2132,82 +2131,24 @@ contains
2132
 
2131
 
2133
     ! Render symbols panel in offcanvas mode (right side, full height)
2132
     ! Render symbols panel in offcanvas mode (right side, full height)
2134
     subroutine render_lsp_symbols_panel(panel, start_col, width, start_row, end_row)
2133
     subroutine render_lsp_symbols_panel(panel, start_col, width, start_row, end_row)
2135
-        use symbols_panel_module, only: symbols_panel_t
2134
+        use symbols_panel_module, only: symbols_panel_t, render_symbols_panel
2136
         type(symbols_panel_t), intent(in) :: panel
2135
         type(symbols_panel_t), intent(in) :: panel
2137
         integer, intent(in) :: start_col, width, start_row, end_row
2136
         integer, intent(in) :: start_col, width, start_row, end_row
2138
-        integer :: row
2139
-        character(len=1), parameter :: ESC = achar(27)
2140
-
2141
-        ! Clear panel area
2142
-        do row = start_row, end_row
2143
-            call terminal_move_cursor(row, start_col)
2144
-            call terminal_write(repeat(' ', width))
2145
-        end do
2146
-
2147
-        row = start_row
2148
 
2137
 
2149
-        ! Header
2138
+        ! Delegate to the real symbols panel renderer
2150
-        call terminal_move_cursor(row, start_col)
2139
+        ! The panel manages its own positioning via panel_start_col and panel_width
2151
-        call terminal_write(ESC // '[48;5;237m' // ESC // '[1m Document Symbols ')
2140
+        call render_symbols_panel(panel, end_row)
2152
-        if (19 < width) then
2153
-            call terminal_write(repeat(' ', width - 19))
2154
-        end if
2155
-        call terminal_write(ESC // '[0m')
2156
-        row = row + 1
2157
-
2158
-        ! Separator
2159
-        call terminal_move_cursor(row, start_col)
2160
-        call terminal_write(ESC // '[48;5;237m' // repeat("─", width) // ESC // '[0m')
2161
-        row = row + 1
2162
-
2163
-        ! Legend
2164
-        call terminal_move_cursor(row, start_col)
2165
-        call terminal_write(ESC // '[90mj/k:nav  enter:jump  esc:close' // ESC // '[0m')
2166
-        row = row + 1
2167
-
2168
-        ! TODO: Actually render symbols (will delegate to symbols_panel_module logic)
2169
-        call terminal_move_cursor(row, start_col)
2170
-        call terminal_write(ESC // '[90m(symbols panel implementation pending)' // ESC // '[0m')
2171
     end subroutine render_lsp_symbols_panel
2141
     end subroutine render_lsp_symbols_panel
2172
 
2142
 
2173
     ! Render workspace symbols panel in offcanvas mode (right side, full height)
2143
     ! Render workspace symbols panel in offcanvas mode (right side, full height)
2174
     subroutine render_lsp_workspace_symbols_panel(panel, start_col, width, start_row, end_row)
2144
     subroutine render_lsp_workspace_symbols_panel(panel, start_col, width, start_row, end_row)
2175
-        use workspace_symbols_panel_module, only: workspace_symbols_panel_t
2145
+        use workspace_symbols_panel_module, only: workspace_symbols_panel_t, render_workspace_symbols_panel
2176
         type(workspace_symbols_panel_t), intent(in) :: panel
2146
         type(workspace_symbols_panel_t), intent(in) :: panel
2177
         integer, intent(in) :: start_col, width, start_row, end_row
2147
         integer, intent(in) :: start_col, width, start_row, end_row
2178
-        integer :: row
2179
-        character(len=1), parameter :: ESC = achar(27)
2180
-
2181
-        ! Clear panel area
2182
-        do row = start_row, end_row
2183
-            call terminal_move_cursor(row, start_col)
2184
-            call terminal_write(repeat(' ', width))
2185
-        end do
2186
-
2187
-        row = start_row
2188
-
2189
-        ! Header
2190
-        call terminal_move_cursor(row, start_col)
2191
-        call terminal_write(ESC // '[48;5;237m' // ESC // '[1m Workspace Symbols ')
2192
-        if (21 < width) then
2193
-            call terminal_write(repeat(' ', width - 21))
2194
-        end if
2195
-        call terminal_write(ESC // '[0m')
2196
-        row = row + 1
2197
-
2198
-        ! Separator
2199
-        call terminal_move_cursor(row, start_col)
2200
-        call terminal_write(ESC // '[48;5;237m' // repeat("─", width) // ESC // '[0m')
2201
-        row = row + 1
2202
 
2148
 
2203
-        ! Legend
2149
+        ! Delegate to the real workspace symbols panel renderer
2204
-        call terminal_move_cursor(row, start_col)
2150
+        ! The panel manages its own positioning via panel_start_col and panel_width
2205
-        call terminal_write(ESC // '[90mj/k:nav  enter:jump  esc:close' // ESC // '[0m')
2151
+        call render_workspace_symbols_panel(panel, end_row)
2206
-        row = row + 1
2207
-
2208
-        ! TODO: Actually render workspace symbols (will delegate to workspace_symbols_panel_module logic)
2209
-        call terminal_move_cursor(row, start_col)
2210
-        call terminal_write(ESC // '[90m(workspace symbols panel implementation pending)' // ESC // '[0m')
2211
     end subroutine render_lsp_workspace_symbols_panel
2152
     end subroutine render_lsp_workspace_symbols_panel
2212
 
2153
 
2213
     ! Helper function to extract basename from path
2154
     ! Helper function to extract basename from path