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
57985798
     ! The response parsing logic is ready but needs proper callback integration
57995799
 
58005800
     ! 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)
58025802
         use lsp_protocol_module, only: lsp_message_t
5803
-        integer, intent(in) :: request_id
5803
+        integer, intent(in) :: unused_request_id
58045804
         type(lsp_message_t), intent(in) :: response
58055805
 
58065806
         ! Call the actual handler with saved editor state
@@ -5907,9 +5907,9 @@ contains
59075907
     end subroutine handle_references_response_impl
59085908
 
59095909
     ! 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)
59115911
         use lsp_protocol_module, only: lsp_message_t
5912
-        integer, intent(in) :: request_id
5912
+        integer, intent(in) :: unused_request_id
59135913
         type(lsp_message_t), intent(in) :: response
59145914
 
59155915
         ! Call the actual handler with saved editor state
@@ -5994,9 +5994,9 @@ contains
59945994
     end subroutine handle_code_actions_response_impl
59955995
 
59965996
     ! 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)
59985998
         use lsp_protocol_module, only: lsp_message_t
5999
-        integer, intent(in) :: request_id
5999
+        integer, intent(in) :: unused_request_id
60006000
         type(lsp_message_t), intent(in) :: response
60016001
 
60026002
         ! Call the actual handler with saved editor state
@@ -6155,9 +6155,9 @@ contains
61556155
     end subroutine handle_symbols_response_impl
61566156
 
61576157
     ! 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)
61596159
         use lsp_protocol_module, only: lsp_message_t
6160
-        integer, intent(in) :: request_id
6160
+        integer, intent(in) :: unused_request_id
61616161
         type(lsp_message_t), intent(in) :: response
61626162
 
61636163
         ! Call the actual handler with saved editor state
@@ -6167,10 +6167,10 @@ contains
61676167
     end subroutine handle_signature_response_wrapper
61686168
 
61696169
     ! 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)
61716171
         use lsp_protocol_module, only: lsp_message_t
61726172
         use json_module, only: json_value_t, json_stringify
6173
-        integer, intent(in) :: request_id
6173
+        integer, intent(in) :: unused_request_id
61746174
         type(lsp_message_t), intent(in) :: response
61756175
 
61766176
         character(len=:), allocatable :: result_str
@@ -6206,11 +6206,11 @@ contains
62066206
     end subroutine handle_rename_response_wrapper
62076207
 
62086208
     ! Wrapper callback for formatting response
6209
-    subroutine handle_formatting_response_wrapper(request_id, response)
6209
+    subroutine handle_formatting_response_wrapper(unused_request_id, response)
62106210
         use lsp_protocol_module, only: lsp_message_t
62116211
         use json_module, only: json_value_t, json_array_size, json_get_array_element, &
62126212
                                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
62146214
         type(lsp_message_t), intent(in) :: response
62156215
 
62166216
         type(json_value_t) :: edits_array, edit_obj, range_obj, start_obj, end_obj
@@ -6672,11 +6672,11 @@ contains
66726672
     end subroutine execute_palette_command
66736673
 
66746674
     ! 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)
66766676
         use lsp_protocol_module, only: lsp_message_t
66776677
         use json_module
66786678
         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
66806680
         type(lsp_message_t), intent(in) :: response
66816681
         type(json_value_t) :: symbol_obj, location_obj, range_obj, start_obj
66826682
         integer :: num_symbols, i
@@ -6943,9 +6943,9 @@ contains
69436943
     ! ==================================================
69446944
 
69456945
     ! 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)
69476947
         use lsp_protocol_module, only: lsp_message_t
6948
-        integer, intent(in) :: request_id
6948
+        integer, intent(in) :: unused_request_id
69496949
         type(lsp_message_t), intent(in) :: response
69506950
 
69516951
         ! Call actual handler with saved editor state
src/syntax/syntax_highlighter_module.f90modified
@@ -166,8 +166,12 @@ contains
166166
         do while (i <= line_len)
167167
             ch = line(i:i)
168168
 
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
+
169173
             ! Check for single-line comment
170
-            if (check_comment_start(highlighter, line, i)) then
174
+            else if (check_comment_start(highlighter, line, i)) then
171175
                 token_count = token_count + 1
172176
                 tokens(token_count)%type = TOKEN_COMMENT
173177
                 tokens(token_count)%start_col = i
@@ -396,6 +400,22 @@ contains
396400
         end if
397401
     end function check_comment_start
398402
 
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
+
399419
     function check_string_start(highlighter, line, pos) result(res)
400420
         type(syntax_highlighter_t), intent(in) :: highlighter
401421
         character(len=*), intent(in) :: line
@@ -424,7 +444,7 @@ contains
424444
         integer, intent(inout) :: token_count, pos
425445
         integer :: i, start_pos, delim_len, line_len
426446
         character(len=:), allocatable :: delimiter
427
-        logical :: found_end
447
+        logical :: found_end, is_multiline
428448
 
429449
         line_len = len(line)
430450
         start_pos = pos
@@ -443,6 +463,11 @@ contains
443463
             end if
444464
         end do
445465
 
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
+
446471
         ! Move past opening delimiter
447472
         pos = pos + len(delimiter)
448473
 
@@ -473,6 +498,11 @@ contains
473498
 
474499
         ! Handle unclosed string
475500
         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
476506
             pos = line_len + 1
477507
         end if
478508
     end subroutine process_string
@@ -949,13 +979,83 @@ contains
949979
         highlighter%enabled = .true.
950980
     end subroutine load_markdown_syntax
951981
 
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)
9531024
     subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos)
9541025
         type(syntax_highlighter_t), intent(inout) :: highlighter
9551026
         character(len=*), intent(in) :: line
9561027
         type(token_t), intent(inout) :: tokens(:)
9571028
         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
9591059
     end subroutine process_multiline_comment
9601060
 
9611061
     subroutine process_multiline_string(highlighter, line, tokens, token_count, pos)
@@ -963,7 +1063,37 @@ contains
9631063
         character(len=*), intent(in) :: line
9641064
         type(token_t), intent(inout) :: tokens(:)
9651065
         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
9671097
     end subroutine process_multiline_string
9681098
 
9691099
 end module syntax_highlighter_module
src/terminal/renderer_module.f90modified
@@ -1971,17 +1971,16 @@ contains
19711971
         if (n_panes == 0) return
19721972
 
19731973
         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)
19751975
         else
19761976
             call render_cursor_in_pane(editor, start_col, width)
19771977
         end if
19781978
     end subroutine render_cursor_for_lsp_panel
19791979
 
19801980
     ! 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)
19821982
         use editor_state_module, only: pane_t
19831983
         type(editor_state_t), intent(inout) :: editor
1984
-        integer, intent(in) :: start_col, width
19851984
         integer :: tab_idx, active_pane
19861985
         type(pane_t) :: pane
19871986
         integer :: screen_row, screen_col
@@ -2132,82 +2131,24 @@ contains
21322131
 
21332132
     ! Render symbols panel in offcanvas mode (right side, full height)
21342133
     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
21362135
         type(symbols_panel_t), intent(in) :: panel
21372136
         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
21482137
 
2149
-        ! Header
2150
-        call terminal_move_cursor(row, start_col)
2151
-        call terminal_write(ESC // '[48;5;237m' // ESC // '[1m Document Symbols ')
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')
2138
+        ! Delegate to the real symbols panel renderer
2139
+        ! The panel manages its own positioning via panel_start_col and panel_width
2140
+        call render_symbols_panel(panel, end_row)
21712141
     end subroutine render_lsp_symbols_panel
21722142
 
21732143
     ! Render workspace symbols panel in offcanvas mode (right side, full height)
21742144
     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
21762146
         type(workspace_symbols_panel_t), intent(in) :: panel
21772147
         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
22022148
 
2203
-        ! Legend
2204
-        call terminal_move_cursor(row, start_col)
2205
-        call terminal_write(ESC // '[90mj/k:nav  enter:jump  esc:close' // ESC // '[0m')
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')
2149
+        ! Delegate to the real workspace symbols panel renderer
2150
+        ! The panel manages its own positioning via panel_start_col and panel_width
2151
+        call render_workspace_symbols_panel(panel, end_row)
22112152
     end subroutine render_lsp_workspace_symbols_panel
22122153
 
22132154
     ! Helper function to extract basename from path