fortrangoingonforty/facsimile / 206363c

Browse files

prep for lsp, need testing of basic files

Authored by espadonne
SHA
206363cacd3858ce398cf5078908d37f56fe6c31
Parents
be74517
Tree
53bc0a8

5 changed files

StatusFile+-
M Makefile 11 3
M app/main.f90 6 2
A src/lsp/lsp_client_module.f90 434 0
A src/syntax/syntax_highlighter_module.f90 966 0
M src/terminal/renderer_module.f90 47 4
Makefilemodified
@@ -11,9 +11,16 @@ CC = gcc
1111
 ifeq ($(UNAME_S),Darwin)
1212
     # macOS
1313
     ifeq ($(UNAME_M),arm64)
14
-        # Apple Silicon - use flang-new for better arm64 support
14
+        # Apple Silicon - use gfortran-15 for syntax highlighting support
1515
         BREW_PREFIX = /opt/homebrew
16
-        ifneq ($(wildcard $(BREW_PREFIX)/bin/flang-new),)
16
+        ifneq ($(wildcard $(BREW_PREFIX)/bin/gfortran-15),)
17
+            FC = $(BREW_PREFIX)/bin/gfortran-15
18
+            FFLAGS = -O2 -Wall -ffree-line-length-none
19
+            FFLAGS_DEV = -O0 -g -Wall -Wextra -pedantic -Wunused-variable -Wuninitialized \
20
+                         -Wimplicit-interface -fcheck=all -fbacktrace -ffree-line-length-none
21
+            FFLAGS_DEBUG = -O0 -g -fcheck=all -fbacktrace -ffree-line-length-none
22
+        else ifneq ($(wildcard $(BREW_PREFIX)/bin/flang-new),)
23
+            # Fallback to flang-new if gfortran-15 not available
1724
             FC = $(BREW_PREFIX)/bin/flang-new
1825
             # flang-new flags
1926
             FFLAGS = -O2
@@ -23,7 +30,7 @@ ifeq ($(UNAME_S),Darwin)
2330
             # Debug flags with debug symbols
2431
             FFLAGS_DEBUG = -O0 -g
2532
         else
26
-            # Fallback to gfortran if flang-new not available
33
+            # Fallback to any available gfortran
2734
             ifneq ($(wildcard $(BREW_PREFIX)/bin/gfortran-*),)
2835
                 FC = $(shell ls $(BREW_PREFIX)/bin/gfortran-* | head -n1)
2936
             endif
@@ -78,6 +85,7 @@ SOURCES = src/version_module.f90 \
7885
           src/workspace/recents_module.f90 \
7986
           src/workspace/workspace_module.f90 \
8087
           src/workspace/backup_module.f90 \
88
+          src/syntax/syntax_highlighter_module.f90 \
8189
           src/terminal/renderer_module.f90 \
8290
           src/ui/help_display_module.f90 \
8391
           src/ui/text_prompt_module.f90 \
app/main.f90modified
@@ -247,8 +247,12 @@ program facsimile
247247
     editor%screen_rows = rows
248248
     editor%screen_cols = cols
249249
 
250
-    ! Initialize renderer
251
-    call init_renderer(rows, cols)
250
+    ! Initialize renderer (pass filename for syntax highlighting detection)
251
+    if (len_trim(filename) > 0) then
252
+        call init_renderer(rows, cols, trim(filename))
253
+    else
254
+        call init_renderer(rows, cols)
255
+    end if
252256
 
253257
     ! Initialize command handler (for yank stack)
254258
     call init_command_handler()
src/lsp/lsp_client_module.f90added
@@ -0,0 +1,434 @@
1
+module lsp_client_module
2
+    use iso_fortran_env, only: int32, int64, output_unit, error_unit
3
+    use iso_c_binding
4
+    implicit none
5
+    private
6
+
7
+    public :: lsp_client_t
8
+    public :: lsp_init, lsp_shutdown
9
+    public :: lsp_get_completions, lsp_get_hover
10
+    public :: lsp_go_to_definition, lsp_find_references
11
+    public :: lsp_get_diagnostics
12
+
13
+    ! LSP message types
14
+    type :: lsp_position_t
15
+        integer :: line
16
+        integer :: character
17
+    end type lsp_position_t
18
+
19
+    type :: lsp_range_t
20
+        type(lsp_position_t) :: start
21
+        type(lsp_position_t) :: end
22
+    end type lsp_range_t
23
+
24
+    type :: lsp_diagnostic_t
25
+        type(lsp_range_t) :: range
26
+        integer :: severity  ! 1=Error, 2=Warning, 3=Info, 4=Hint
27
+        character(len=:), allocatable :: message
28
+        character(len=:), allocatable :: source
29
+    end type lsp_diagnostic_t
30
+
31
+    type :: lsp_completion_t
32
+        character(len=:), allocatable :: label
33
+        integer :: kind  ! 1=Text, 2=Method, 3=Function, etc.
34
+        character(len=:), allocatable :: detail
35
+        character(len=:), allocatable :: documentation
36
+    end type lsp_completion_t
37
+
38
+    type :: lsp_location_t
39
+        character(len=:), allocatable :: uri
40
+        type(lsp_range_t) :: range
41
+    end type lsp_location_t
42
+
43
+    ! Main LSP client type
44
+    type :: lsp_client_t
45
+        integer :: server_pid = -1
46
+        integer :: stdin_fd = -1
47
+        integer :: stdout_fd = -1
48
+        integer :: stderr_fd = -1
49
+        logical :: initialized = .false.
50
+        character(len=:), allocatable :: root_path
51
+        character(len=:), allocatable :: language_id
52
+        integer :: next_request_id = 1
53
+
54
+        ! Capabilities
55
+        logical :: supports_completion = .false.
56
+        logical :: supports_hover = .false.
57
+        logical :: supports_goto = .false.
58
+        logical :: supports_references = .false.
59
+        logical :: supports_diagnostics = .false.
60
+        logical :: supports_semantic_tokens = .false.
61
+    end type lsp_client_t
62
+
63
+    ! JSON-RPC interface (simplified)
64
+    interface
65
+        ! These would interface with C code for process management and JSON parsing
66
+        function c_start_lsp_server(command, argc, argv) bind(c, name="start_lsp_server")
67
+            use iso_c_binding
68
+            integer(c_int) :: c_start_lsp_server
69
+            character(c_char), intent(in) :: command(*)
70
+            integer(c_int), value :: argc
71
+            type(c_ptr), intent(in) :: argv
72
+        end function c_start_lsp_server
73
+
74
+        function c_send_json_rpc(fd, json_str, len) bind(c, name="send_json_rpc")
75
+            use iso_c_binding
76
+            integer(c_int) :: c_send_json_rpc
77
+            integer(c_int), value :: fd
78
+            character(c_char), intent(in) :: json_str(*)
79
+            integer(c_int), value :: len
80
+        end function c_send_json_rpc
81
+
82
+        function c_read_json_rpc(fd, buffer, max_len) bind(c, name="read_json_rpc")
83
+            use iso_c_binding
84
+            integer(c_int) :: c_read_json_rpc
85
+            integer(c_int), value :: fd
86
+            character(c_char), intent(out) :: buffer(*)
87
+            integer(c_int), value :: max_len
88
+        end function c_read_json_rpc
89
+    end interface
90
+
91
+contains
92
+
93
+    subroutine lsp_init(client, language, root_path)
94
+        type(lsp_client_t), intent(out) :: client
95
+        character(len=*), intent(in) :: language
96
+        character(len=*), intent(in) :: root_path
97
+        character(len=:), allocatable :: server_command
98
+        logical :: server_started
99
+
100
+        client%language_id = language
101
+        client%root_path = root_path
102
+
103
+        ! Determine LSP server command based on language
104
+        select case(language)
105
+        case('python')
106
+            server_command = 'pylsp'  ! Python LSP Server
107
+        case('rust')
108
+            server_command = 'rust-analyzer'
109
+        case('go')
110
+            server_command = 'gopls'
111
+        case('c', 'cpp')
112
+            server_command = 'clangd'
113
+        case('fortran')
114
+            server_command = 'fortls'  ! Fortran Language Server
115
+        case('typescript', 'javascript')
116
+            server_command = 'typescript-language-server --stdio'
117
+        case default
118
+            ! No LSP server for this language
119
+            client%initialized = .false.
120
+            return
121
+        end select
122
+
123
+        ! Start the LSP server process
124
+        call start_lsp_server(client, server_command)
125
+
126
+        if (client%server_pid > 0) then
127
+            ! Send initialization request
128
+            call send_initialize_request(client)
129
+
130
+            ! Wait for and process initialization response
131
+            call receive_initialize_response(client)
132
+
133
+            if (client%initialized) then
134
+                ! Send initialized notification
135
+                call send_initialized_notification(client)
136
+            end if
137
+        end if
138
+    end subroutine lsp_init
139
+
140
+    subroutine lsp_shutdown(client)
141
+        type(lsp_client_t), intent(inout) :: client
142
+
143
+        if (client%initialized) then
144
+            ! Send shutdown request
145
+            call send_shutdown_request(client)
146
+
147
+            ! Send exit notification
148
+            call send_exit_notification(client)
149
+
150
+            ! Close pipes and kill process if needed
151
+            call cleanup_lsp_process(client)
152
+        end if
153
+
154
+        client%initialized = .false.
155
+    end subroutine lsp_shutdown
156
+
157
+    function lsp_get_completions(client, file_path, line, col) result(completions)
158
+        type(lsp_client_t), intent(inout) :: client
159
+        character(len=*), intent(in) :: file_path
160
+        integer, intent(in) :: line, col
161
+        type(lsp_completion_t), allocatable :: completions(:)
162
+        character(len=:), allocatable :: request_json, response_json
163
+
164
+        if (.not. client%supports_completion) then
165
+            allocate(completions(0))
166
+            return
167
+        end if
168
+
169
+        ! Build completion request
170
+        request_json = build_completion_request(client, file_path, line, col)
171
+
172
+        ! Send request
173
+        call send_request(client, request_json)
174
+
175
+        ! Receive and parse response
176
+        response_json = receive_response(client)
177
+        completions = parse_completion_response(response_json)
178
+
179
+    end function lsp_get_completions
180
+
181
+    function lsp_get_hover(client, file_path, line, col) result(hover_text)
182
+        type(lsp_client_t), intent(inout) :: client
183
+        character(len=*), intent(in) :: file_path
184
+        integer, intent(in) :: line, col
185
+        character(len=:), allocatable :: hover_text
186
+        character(len=:), allocatable :: request_json, response_json
187
+
188
+        if (.not. client%supports_hover) then
189
+            hover_text = ""
190
+            return
191
+        end if
192
+
193
+        ! Build hover request
194
+        request_json = build_hover_request(client, file_path, line, col)
195
+
196
+        ! Send request
197
+        call send_request(client, request_json)
198
+
199
+        ! Receive and parse response
200
+        response_json = receive_response(client)
201
+        hover_text = parse_hover_response(response_json)
202
+
203
+    end function lsp_get_hover
204
+
205
+    function lsp_go_to_definition(client, file_path, line, col) result(location)
206
+        type(lsp_client_t), intent(inout) :: client
207
+        character(len=*), intent(in) :: file_path
208
+        integer, intent(in) :: line, col
209
+        type(lsp_location_t) :: location
210
+        character(len=:), allocatable :: request_json, response_json
211
+
212
+        if (.not. client%supports_goto) then
213
+            location%uri = ""
214
+            return
215
+        end if
216
+
217
+        ! Build goto definition request
218
+        request_json = build_goto_request(client, file_path, line, col)
219
+
220
+        ! Send request
221
+        call send_request(client, request_json)
222
+
223
+        ! Receive and parse response
224
+        response_json = receive_response(client)
225
+        location = parse_location_response(response_json)
226
+
227
+    end function lsp_go_to_definition
228
+
229
+    function lsp_find_references(client, file_path, line, col) result(locations)
230
+        type(lsp_client_t), intent(inout) :: client
231
+        character(len=*), intent(in) :: file_path
232
+        integer, intent(in) :: line, col
233
+        type(lsp_location_t), allocatable :: locations(:)
234
+        character(len=:), allocatable :: request_json, response_json
235
+
236
+        if (.not. client%supports_references) then
237
+            allocate(locations(0))
238
+            return
239
+        end if
240
+
241
+        ! Build find references request
242
+        request_json = build_references_request(client, file_path, line, col)
243
+
244
+        ! Send request
245
+        call send_request(client, request_json)
246
+
247
+        ! Receive and parse response
248
+        response_json = receive_response(client)
249
+        locations = parse_locations_response(response_json)
250
+
251
+    end function lsp_find_references
252
+
253
+    function lsp_get_diagnostics(client, file_path) result(diagnostics)
254
+        type(lsp_client_t), intent(inout) :: client
255
+        character(len=*), intent(in) :: file_path
256
+        type(lsp_diagnostic_t), allocatable :: diagnostics(:)
257
+
258
+        if (.not. client%supports_diagnostics) then
259
+            allocate(diagnostics(0))
260
+            return
261
+        end if
262
+
263
+        ! Diagnostics are typically pushed by the server
264
+        ! This would check a cache of received diagnostics
265
+        diagnostics = get_cached_diagnostics(client, file_path)
266
+
267
+    end function lsp_get_diagnostics
268
+
269
+    ! Internal helper procedures (stubs for now)
270
+
271
+    subroutine start_lsp_server(client, command)
272
+        type(lsp_client_t), intent(inout) :: client
273
+        character(len=*), intent(in) :: command
274
+        ! TODO: Implement process spawning with pipes
275
+        ! This would use fork/exec on Unix or CreateProcess on Windows
276
+    end subroutine start_lsp_server
277
+
278
+    subroutine send_initialize_request(client)
279
+        type(lsp_client_t), intent(inout) :: client
280
+        character(len=:), allocatable :: json_request
281
+
282
+        ! Build initialize request JSON
283
+        json_request = '{"jsonrpc":"2.0","id":' // int_to_string(client%next_request_id) // &
284
+                      ',"method":"initialize","params":{' // &
285
+                      '"processId":' // int_to_string(getpid()) // ',' // &
286
+                      '"rootPath":"' // client%root_path // '",' // &
287
+                      '"capabilities":{}' // &
288
+                      '}}'
289
+
290
+        call send_request(client, json_request)
291
+        client%next_request_id = client%next_request_id + 1
292
+    end subroutine send_initialize_request
293
+
294
+    subroutine receive_initialize_response(client)
295
+        type(lsp_client_t), intent(inout) :: client
296
+        character(len=:), allocatable :: response
297
+
298
+        response = receive_response(client)
299
+
300
+        ! Parse capabilities from response
301
+        ! This would parse the JSON to determine server capabilities
302
+        client%supports_completion = .true.  ! Placeholder
303
+        client%supports_hover = .true.
304
+        client%supports_goto = .true.
305
+        client%supports_references = .true.
306
+        client%supports_diagnostics = .true.
307
+
308
+        client%initialized = .true.
309
+    end subroutine receive_initialize_response
310
+
311
+    subroutine send_initialized_notification(client)
312
+        type(lsp_client_t), intent(inout) :: client
313
+        character(len=:), allocatable :: notification
314
+
315
+        notification = '{"jsonrpc":"2.0","method":"initialized","params":{}}'
316
+        call send_request(client, notification)
317
+    end subroutine send_initialized_notification
318
+
319
+    subroutine send_shutdown_request(client)
320
+        type(lsp_client_t), intent(inout) :: client
321
+        character(len=:), allocatable :: request
322
+
323
+        request = '{"jsonrpc":"2.0","id":' // int_to_string(client%next_request_id) // &
324
+                 ',"method":"shutdown","params":null}'
325
+        call send_request(client, request)
326
+    end subroutine send_shutdown_request
327
+
328
+    subroutine send_exit_notification(client)
329
+        type(lsp_client_t), intent(inout) :: client
330
+        character(len=:), allocatable :: notification
331
+
332
+        notification = '{"jsonrpc":"2.0","method":"exit","params":null}'
333
+        call send_request(client, notification)
334
+    end subroutine send_exit_notification
335
+
336
+    subroutine cleanup_lsp_process(client)
337
+        type(lsp_client_t), intent(inout) :: client
338
+        ! TODO: Close pipes and kill process
339
+    end subroutine cleanup_lsp_process
340
+
341
+    subroutine send_request(client, json_str)
342
+        type(lsp_client_t), intent(in) :: client
343
+        character(len=*), intent(in) :: json_str
344
+        ! TODO: Implement sending JSON-RPC over pipe
345
+    end subroutine send_request
346
+
347
+    function receive_response(client) result(response)
348
+        type(lsp_client_t), intent(in) :: client
349
+        character(len=:), allocatable :: response
350
+        ! TODO: Implement receiving JSON-RPC over pipe
351
+        response = "{}"
352
+    end function receive_response
353
+
354
+    ! Stub implementations for request builders
355
+    function build_completion_request(client, file_path, line, col) result(json)
356
+        type(lsp_client_t), intent(inout) :: client
357
+        character(len=*), intent(in) :: file_path
358
+        integer, intent(in) :: line, col
359
+        character(len=:), allocatable :: json
360
+        json = "{}"  ! TODO: Implement
361
+    end function build_completion_request
362
+
363
+    function build_hover_request(client, file_path, line, col) result(json)
364
+        type(lsp_client_t), intent(inout) :: client
365
+        character(len=*), intent(in) :: file_path
366
+        integer, intent(in) :: line, col
367
+        character(len=:), allocatable :: json
368
+        json = "{}"  ! TODO: Implement
369
+    end function build_hover_request
370
+
371
+    function build_goto_request(client, file_path, line, col) result(json)
372
+        type(lsp_client_t), intent(inout) :: client
373
+        character(len=*), intent(in) :: file_path
374
+        integer, intent(in) :: line, col
375
+        character(len=:), allocatable :: json
376
+        json = "{}"  ! TODO: Implement
377
+    end function build_goto_request
378
+
379
+    function build_references_request(client, file_path, line, col) result(json)
380
+        type(lsp_client_t), intent(inout) :: client
381
+        character(len=*), intent(in) :: file_path
382
+        integer, intent(in) :: line, col
383
+        character(len=:), allocatable :: json
384
+        json = "{}"  ! TODO: Implement
385
+    end function build_references_request
386
+
387
+    ! Stub implementations for response parsers
388
+    function parse_completion_response(json) result(completions)
389
+        character(len=*), intent(in) :: json
390
+        type(lsp_completion_t), allocatable :: completions(:)
391
+        allocate(completions(0))  ! TODO: Implement JSON parsing
392
+    end function parse_completion_response
393
+
394
+    function parse_hover_response(json) result(hover)
395
+        character(len=*), intent(in) :: json
396
+        character(len=:), allocatable :: hover
397
+        hover = ""  ! TODO: Implement JSON parsing
398
+    end function parse_hover_response
399
+
400
+    function parse_location_response(json) result(location)
401
+        character(len=*), intent(in) :: json
402
+        type(lsp_location_t) :: location
403
+        location%uri = ""  ! TODO: Implement JSON parsing
404
+    end function parse_location_response
405
+
406
+    function parse_locations_response(json) result(locations)
407
+        character(len=*), intent(in) :: json
408
+        type(lsp_location_t), allocatable :: locations(:)
409
+        allocate(locations(0))  ! TODO: Implement JSON parsing
410
+    end function parse_locations_response
411
+
412
+    function get_cached_diagnostics(client, file_path) result(diagnostics)
413
+        type(lsp_client_t), intent(in) :: client
414
+        character(len=*), intent(in) :: file_path
415
+        type(lsp_diagnostic_t), allocatable :: diagnostics(:)
416
+        allocate(diagnostics(0))  ! TODO: Implement diagnostic cache
417
+    end function get_cached_diagnostics
418
+
419
+    ! Utility functions
420
+    function int_to_string(n) result(str)
421
+        integer, intent(in) :: n
422
+        character(len=:), allocatable :: str
423
+        character(len=32) :: buffer
424
+        write(buffer, '(i0)') n
425
+        str = trim(buffer)
426
+    end function int_to_string
427
+
428
+    function getpid() result(pid)
429
+        integer :: pid
430
+        ! This would call the C getpid() function
431
+        pid = 0  ! Placeholder
432
+    end function getpid
433
+
434
+end module lsp_client_module
src/syntax/syntax_highlighter_module.f90added
@@ -0,0 +1,966 @@
1
+module syntax_highlighter_module
2
+    use iso_fortran_env, only: int32
3
+    implicit none
4
+    private
5
+
6
+    public :: syntax_highlighter_t
7
+    public :: token_t
8
+    public :: init_highlighter, cleanup_highlighter
9
+    public :: tokenize_line, get_token_color
10
+    public :: detect_language
11
+    public :: TOKEN_PLAIN, TOKEN_KEYWORD, TOKEN_STRING, TOKEN_NUMBER
12
+    public :: TOKEN_COMMENT, TOKEN_OPERATOR, TOKEN_TYPE, TOKEN_FUNCTION
13
+    public :: TOKEN_PREPROCESSOR
14
+
15
+    ! Token types as integer parameters
16
+    integer, parameter :: TOKEN_PLAIN = 0
17
+    integer, parameter :: TOKEN_KEYWORD = 1
18
+    integer, parameter :: TOKEN_STRING = 2
19
+    integer, parameter :: TOKEN_NUMBER = 3
20
+    integer, parameter :: TOKEN_COMMENT = 4
21
+    integer, parameter :: TOKEN_OPERATOR = 5
22
+    integer, parameter :: TOKEN_TYPE = 6
23
+    integer, parameter :: TOKEN_FUNCTION = 7
24
+    integer, parameter :: TOKEN_PREPROCESSOR = 8
25
+
26
+    ! Token structure
27
+    type :: token_t
28
+        integer :: type = TOKEN_PLAIN
29
+        integer :: start_col
30
+        integer :: end_col
31
+    end type token_t
32
+
33
+    ! Language definition
34
+    type :: language_def_t
35
+        character(len=32) :: name = ""
36
+        character(len=16), allocatable :: extensions(:)
37
+        character(len=64), allocatable :: keywords(:)
38
+        character(len=64), allocatable :: types(:)
39
+        character(len=8) :: comment_single = ""
40
+        character(len=8) :: comment_start = ""
41
+        character(len=8) :: comment_end = ""
42
+        character(len=4), allocatable :: string_delimiters(:)
43
+        character(len=4), allocatable :: operators(:)
44
+        logical :: case_sensitive = .true.
45
+    end type language_def_t
46
+
47
+    ! Main highlighter type
48
+    type :: syntax_highlighter_t
49
+        type(language_def_t) :: current_lang
50
+        logical :: enabled = .false.
51
+        logical :: in_multiline_comment = .false.
52
+        logical :: in_multiline_string = .false.
53
+        character(len=4) :: string_delimiter = ""
54
+    end type syntax_highlighter_t
55
+
56
+    ! Color mapping (ANSI escape codes)
57
+    character(len=16), parameter :: COLOR_KEYWORD = char(27) // '[1;34m'     ! Bold Blue
58
+    character(len=16), parameter :: COLOR_STRING = char(27) // '[32m'        ! Green
59
+    character(len=16), parameter :: COLOR_NUMBER = char(27) // '[35m'        ! Magenta
60
+    character(len=16), parameter :: COLOR_COMMENT = char(27) // '[90m'       ! Gray
61
+    character(len=16), parameter :: COLOR_OPERATOR = char(27) // '[33m'      ! Yellow
62
+    character(len=16), parameter :: COLOR_TYPE = char(27) // '[36m'          ! Cyan
63
+    character(len=16), parameter :: COLOR_FUNCTION = char(27) // '[1;36m'    ! Bold Cyan
64
+    character(len=16), parameter :: COLOR_PREPROC = char(27) // '[95m'       ! Light Magenta
65
+    character(len=16), parameter :: COLOR_RESET = char(27) // '[0m'
66
+
67
+contains
68
+
69
+    subroutine init_highlighter(highlighter, filename)
70
+        type(syntax_highlighter_t), intent(out) :: highlighter
71
+        character(len=*), intent(in), optional :: filename
72
+
73
+        highlighter%enabled = .false.
74
+        highlighter%in_multiline_comment = .false.
75
+        highlighter%in_multiline_string = .false.
76
+
77
+        if (present(filename)) then
78
+            call detect_language(highlighter, filename)
79
+        end if
80
+    end subroutine init_highlighter
81
+
82
+    subroutine cleanup_highlighter(highlighter)
83
+        type(syntax_highlighter_t), intent(inout) :: highlighter
84
+
85
+        if (allocated(highlighter%current_lang%extensions)) &
86
+            deallocate(highlighter%current_lang%extensions)
87
+        if (allocated(highlighter%current_lang%keywords)) &
88
+            deallocate(highlighter%current_lang%keywords)
89
+        if (allocated(highlighter%current_lang%types)) &
90
+            deallocate(highlighter%current_lang%types)
91
+        if (allocated(highlighter%current_lang%string_delimiters)) &
92
+            deallocate(highlighter%current_lang%string_delimiters)
93
+        if (allocated(highlighter%current_lang%operators)) &
94
+            deallocate(highlighter%current_lang%operators)
95
+    end subroutine cleanup_highlighter
96
+
97
+    subroutine detect_language(highlighter, filename)
98
+        type(syntax_highlighter_t), intent(inout) :: highlighter
99
+        character(len=*), intent(in) :: filename
100
+        character(len=:), allocatable :: extension
101
+        integer :: dot_pos
102
+
103
+        ! Find file extension
104
+        dot_pos = index(filename, '.', back=.true.)
105
+        if (dot_pos > 0) then
106
+            extension = filename(dot_pos:)
107
+
108
+            select case(extension)
109
+            case('.f90', '.f95', '.f03', '.f08', '.f18')
110
+                call load_fortran_syntax(highlighter)
111
+            case('.py', '.pyw')
112
+                call load_python_syntax(highlighter)
113
+            case('.c', '.h')
114
+                call load_c_syntax(highlighter)
115
+            case('.cpp', '.cc', '.cxx', '.hpp', '.hxx')
116
+                call load_cpp_syntax(highlighter)
117
+            case('.rs')
118
+                call load_rust_syntax(highlighter)
119
+            case('.go')
120
+                call load_go_syntax(highlighter)
121
+            case('.js', '.jsx', '.mjs')
122
+                call load_javascript_syntax(highlighter)
123
+            case('.ts', '.tsx')
124
+                call load_typescript_syntax(highlighter)
125
+            case('.sh', '.bash')
126
+                call load_bash_syntax(highlighter)
127
+            case('.md', '.markdown')
128
+                call load_markdown_syntax(highlighter)
129
+            case default
130
+                highlighter%enabled = .false.
131
+            end select
132
+        end if
133
+    end subroutine detect_language
134
+
135
+    subroutine tokenize_line(highlighter, line, tokens)
136
+        type(syntax_highlighter_t), intent(inout) :: highlighter
137
+        character(len=*), intent(in) :: line
138
+        type(token_t), allocatable, intent(out) :: tokens(:)
139
+        integer :: i, j, line_len, token_count
140
+        logical :: in_string, in_comment
141
+        character(len=1) :: ch
142
+        character(len=:), allocatable :: word
143
+
144
+        if (.not. highlighter%enabled) then
145
+            allocate(tokens(1))
146
+            tokens(1)%type = TOKEN_PLAIN
147
+            tokens(1)%start_col = 1
148
+            tokens(1)%end_col = len(line)
149
+            return
150
+        end if
151
+
152
+        line_len = len(line)
153
+        allocate(tokens(line_len))  ! Worst case: each char is a token
154
+        token_count = 0
155
+        i = 1
156
+
157
+        ! Handle multiline comment continuation
158
+        if (highlighter%in_multiline_comment) then
159
+            call process_multiline_comment(highlighter, line, tokens, token_count, i)
160
+        end if
161
+
162
+        ! Handle multiline string continuation
163
+        if (highlighter%in_multiline_string) then
164
+            call process_multiline_string(highlighter, line, tokens, token_count, i)
165
+        end if
166
+
167
+        ! Process rest of line
168
+        do while (i <= line_len)
169
+            ch = line(i:i)
170
+
171
+            ! Check for single-line comment
172
+            if (check_comment_start(highlighter, line, i)) then
173
+                token_count = token_count + 1
174
+                tokens(token_count)%type = TOKEN_COMMENT
175
+                tokens(token_count)%start_col = i
176
+                tokens(token_count)%end_col = line_len
177
+                exit
178
+            end if
179
+
180
+            ! Check for string
181
+            if (check_string_start(highlighter, line, i)) then
182
+                call process_string(highlighter, line, tokens, token_count, i)
183
+
184
+            ! Check for number
185
+            else if (is_digit(ch) .or. (ch == '.' .and. i < line_len .and. is_digit(line(i+1:i+1)))) then
186
+                call process_number(line, tokens, token_count, i)
187
+
188
+            ! Check for word (keyword, type, identifier)
189
+            else if (is_alpha(ch) .or. ch == '_') then
190
+                call process_word(highlighter, line, tokens, token_count, i)
191
+
192
+            ! Check for operator
193
+            else if (is_operator_char(highlighter, ch)) then
194
+                token_count = token_count + 1
195
+                tokens(token_count)%type = TOKEN_OPERATOR
196
+                tokens(token_count)%start_col = i
197
+                tokens(token_count)%end_col = i
198
+                i = i + 1
199
+
200
+            ! Plain character (space, etc.)
201
+            else
202
+                token_count = token_count + 1
203
+                tokens(token_count)%type = TOKEN_PLAIN
204
+                tokens(token_count)%start_col = i
205
+                tokens(token_count)%end_col = i
206
+                i = i + 1
207
+            end if
208
+        end do
209
+
210
+        ! Resize tokens array
211
+        if (token_count > 0) then
212
+            tokens = tokens(1:token_count)
213
+        else
214
+            allocate(tokens(1))
215
+            tokens(1)%type = TOKEN_PLAIN
216
+            tokens(1)%start_col = 1
217
+            tokens(1)%end_col = max(1, line_len)
218
+        end if
219
+    end subroutine tokenize_line
220
+
221
+    function get_token_color(tok_type) result(color)
222
+        integer, intent(in) :: tok_type
223
+        character(len=:), allocatable :: color
224
+
225
+        select case(tok_type)
226
+        case(TOKEN_KEYWORD)
227
+            color = COLOR_KEYWORD
228
+        case(TOKEN_STRING)
229
+            color = COLOR_STRING
230
+        case(TOKEN_NUMBER)
231
+            color = COLOR_NUMBER
232
+        case(TOKEN_COMMENT)
233
+            color = COLOR_COMMENT
234
+        case(TOKEN_OPERATOR)
235
+            color = COLOR_OPERATOR
236
+        case(TOKEN_TYPE)
237
+            color = COLOR_TYPE
238
+        case(TOKEN_FUNCTION)
239
+            color = COLOR_FUNCTION
240
+        case(TOKEN_PREPROCESSOR)
241
+            color = COLOR_PREPROC
242
+        case(TOKEN_PLAIN)
243
+            color = ""
244
+        case default
245
+            color = ""
246
+        end select
247
+    end function get_token_color
248
+
249
+    ! Language-specific loading routines
250
+    subroutine load_fortran_syntax(highlighter)
251
+        type(syntax_highlighter_t), intent(inout) :: highlighter
252
+
253
+        highlighter%current_lang%name = "fortran"
254
+        highlighter%current_lang%case_sensitive = .false.
255
+
256
+        ! Keywords
257
+        allocate(highlighter%current_lang%keywords(58))
258
+        highlighter%current_lang%keywords = [ &
259
+            "program     ", "end         ", "subroutine  ", "function    ", &
260
+            "module      ", "use         ", "implicit    ", "none        ", &
261
+            "if          ", "then        ", "else        ", "elseif      ", &
262
+            "endif       ", "do          ", "while       ", "enddo       ", &
263
+            "select      ", "case        ", "default     ", "endselect   ", &
264
+            "where       ", "elsewhere   ", "endwhere    ", "forall      ", &
265
+            "call        ", "return      ", "contains    ", "interface   ", &
266
+            "abstract    ", "allocate    ", "deallocate  ", "allocatable ", &
267
+            "intent      ", "in          ", "out         ", "inout       ", &
268
+            "optional    ", "parameter   ", "save        ", "pointer     ", &
269
+            "target      ", "public      ", "private     ", "protected   ", &
270
+            "bind        ", "import      ", "only        ", "operator    ", &
271
+            "assignment  ", "generic     ", "final       ", "extends     ", &
272
+            "class       ", "type        ", "endtype     ", "enum        ", &
273
+            "enumerator  ", "namelist    " &
274
+        ]
275
+
276
+        ! Types
277
+        allocate(highlighter%current_lang%types(10))
278
+        highlighter%current_lang%types = [ &
279
+            "integer     ", "real        ", "complex     ", "logical     ", &
280
+            "character   ", "double      ", "precision   ", "int32       ", &
281
+            "int64       ", "real64      " &
282
+        ]
283
+
284
+        ! Comments
285
+        highlighter%current_lang%comment_single = "!"
286
+
287
+        ! String delimiters
288
+        allocate(highlighter%current_lang%string_delimiters(2))
289
+        highlighter%current_lang%string_delimiters = ['"', "'"]
290
+
291
+        ! Operators
292
+        allocate(highlighter%current_lang%operators(20))
293
+        highlighter%current_lang%operators = [ &
294
+            "+   ", "-   ", "*   ", "/   ", "**  ", "=   ", &
295
+            "==  ", "/=  ", "<   ", ">   ", "<=  ", ">=  ", &
296
+            ".and", ".or.", ".not", ".eq.", ".ne.", ".lt.", &
297
+            ".gt.", ".le." &
298
+        ]
299
+
300
+        highlighter%enabled = .true.
301
+    end subroutine load_fortran_syntax
302
+
303
+    subroutine load_python_syntax(highlighter)
304
+        type(syntax_highlighter_t), intent(inout) :: highlighter
305
+
306
+        highlighter%current_lang%name = "python"
307
+        highlighter%current_lang%case_sensitive = .true.
308
+
309
+        ! Keywords
310
+        allocate(highlighter%current_lang%keywords(35))
311
+        highlighter%current_lang%keywords = [ &
312
+            "def         ", "class       ", "if          ", "elif        ", &
313
+            "else        ", "for         ", "while       ", "break       ", &
314
+            "continue    ", "return      ", "yield       ", "import      ", &
315
+            "from        ", "as          ", "try         ", "except      ", &
316
+            "finally     ", "raise       ", "with        ", "assert      ", &
317
+            "lambda      ", "pass        ", "del         ", "global      ", &
318
+            "nonlocal    ", "in          ", "is          ", "and         ", &
319
+            "or          ", "not         ", "True        ", "False       ", &
320
+            "None        ", "async       ", "await       " &
321
+        ]
322
+
323
+        ! Types
324
+        allocate(highlighter%current_lang%types(8))
325
+        highlighter%current_lang%types = [ &
326
+            "int         ", "float       ", "str         ", "bool        ", &
327
+            "list        ", "dict        ", "tuple       ", "set         " &
328
+        ]
329
+
330
+        highlighter%current_lang%comment_single = "#"
331
+
332
+        ! String delimiters
333
+        allocate(highlighter%current_lang%string_delimiters(4))
334
+        highlighter%current_lang%string_delimiters = ['"   ', "'   ", '""" ', "''' "]
335
+
336
+        ! Operators
337
+        allocate(highlighter%current_lang%operators(15))
338
+        highlighter%current_lang%operators = [ &
339
+            "+   ", "-   ", "*   ", "/   ", "//  ", "%   ", "**  ", &
340
+            "=   ", "==  ", "!=  ", "<   ", ">   ", "<=  ", ">=  ", &
341
+            "&   " &
342
+        ]
343
+
344
+        highlighter%enabled = .true.
345
+    end subroutine load_python_syntax
346
+
347
+    ! Helper functions
348
+    function is_alpha(ch) result(res)
349
+        character(len=1), intent(in) :: ch
350
+        logical :: res
351
+        res = (ch >= 'a' .and. ch <= 'z') .or. (ch >= 'A' .and. ch <= 'Z')
352
+    end function is_alpha
353
+
354
+    function is_digit(ch) result(res)
355
+        character(len=1), intent(in) :: ch
356
+        logical :: res
357
+        res = (ch >= '0' .and. ch <= '9')
358
+    end function is_digit
359
+
360
+    function is_alnum(ch) result(res)
361
+        character(len=1), intent(in) :: ch
362
+        logical :: res
363
+        res = is_alpha(ch) .or. is_digit(ch) .or. ch == '_'
364
+    end function is_alnum
365
+
366
+    function is_operator_char(highlighter, ch) result(res)
367
+        type(syntax_highlighter_t), intent(in) :: highlighter
368
+        character(len=1), intent(in) :: ch
369
+        logical :: res
370
+        integer :: i
371
+
372
+        res = .false.
373
+        if (.not. allocated(highlighter%current_lang%operators)) return
374
+
375
+        do i = 1, size(highlighter%current_lang%operators)
376
+            if (index(trim(highlighter%current_lang%operators(i)), ch) > 0) then
377
+                res = .true.
378
+                exit
379
+            end if
380
+        end do
381
+    end function is_operator_char
382
+
383
+    function check_comment_start(highlighter, line, pos) result(res)
384
+        type(syntax_highlighter_t), intent(in) :: highlighter
385
+        character(len=*), intent(in) :: line
386
+        integer, intent(in) :: pos
387
+        logical :: res
388
+        integer :: comment_len
389
+
390
+        res = .false.
391
+        if (highlighter%current_lang%comment_single /= "") then
392
+            comment_len = len_trim(highlighter%current_lang%comment_single)
393
+            if (pos + comment_len - 1 <= len(line)) then
394
+                res = line(pos:pos+comment_len-1) == trim(highlighter%current_lang%comment_single)
395
+            end if
396
+        end if
397
+    end function check_comment_start
398
+
399
+    function check_string_start(highlighter, line, pos) result(res)
400
+        type(syntax_highlighter_t), intent(in) :: highlighter
401
+        character(len=*), intent(in) :: line
402
+        integer, intent(in) :: pos
403
+        logical :: res
404
+        integer :: i, delim_len
405
+
406
+        res = .false.
407
+        if (.not. allocated(highlighter%current_lang%string_delimiters)) return
408
+
409
+        do i = 1, size(highlighter%current_lang%string_delimiters)
410
+            delim_len = len_trim(highlighter%current_lang%string_delimiters(i))
411
+            if (pos + delim_len - 1 <= len(line)) then
412
+                if (line(pos:pos+delim_len-1) == trim(highlighter%current_lang%string_delimiters(i))) then
413
+                    res = .true.
414
+                    exit
415
+                end if
416
+            end if
417
+        end do
418
+    end function check_string_start
419
+
420
+    subroutine process_string(highlighter, line, tokens, token_count, pos)
421
+        type(syntax_highlighter_t), intent(inout) :: highlighter
422
+        character(len=*), intent(in) :: line
423
+        type(token_t), intent(inout) :: tokens(:)
424
+        integer, intent(inout) :: token_count, pos
425
+        integer :: i, start_pos, delim_len, line_len
426
+        character(len=:), allocatable :: delimiter
427
+        logical :: found_end
428
+
429
+        line_len = len(line)
430
+        start_pos = pos
431
+
432
+        ! Find which delimiter matches
433
+        do i = 1, size(highlighter%current_lang%string_delimiters)
434
+            delim_len = len_trim(highlighter%current_lang%string_delimiters(i))
435
+            if (pos + delim_len - 1 <= line_len) then
436
+                if (line(pos:pos+delim_len-1) == trim(highlighter%current_lang%string_delimiters(i))) then
437
+                    delimiter = trim(highlighter%current_lang%string_delimiters(i))
438
+                    exit
439
+                end if
440
+            end if
441
+        end do
442
+
443
+        ! Move past opening delimiter
444
+        pos = pos + len(delimiter)
445
+
446
+        ! Find closing delimiter
447
+        found_end = .false.
448
+        do while (pos <= line_len)
449
+            if (line(pos:pos) == '\' .and. pos < line_len) then
450
+                ! Skip escaped character
451
+                pos = pos + 2
452
+            else if (pos + len(delimiter) - 1 <= line_len) then
453
+                if (line(pos:pos+len(delimiter)-1) == delimiter) then
454
+                    pos = pos + len(delimiter)
455
+                    found_end = .true.
456
+                    exit
457
+                else
458
+                    pos = pos + 1
459
+                end if
460
+            else
461
+                pos = pos + 1
462
+            end if
463
+        end do
464
+
465
+        ! Add string token
466
+        token_count = token_count + 1
467
+        tokens(token_count)%type = TOKEN_STRING
468
+        tokens(token_count)%start_col = start_pos
469
+        tokens(token_count)%end_col = min(pos - 1, line_len)
470
+
471
+        ! Handle unclosed string
472
+        if (.not. found_end) then
473
+            pos = line_len + 1
474
+        end if
475
+    end subroutine process_string
476
+
477
+    subroutine process_number(line, tokens, token_count, pos)
478
+        character(len=*), intent(in) :: line
479
+        type(token_t), intent(inout) :: tokens(:)
480
+        integer, intent(inout) :: token_count, pos
481
+        integer :: start_pos
482
+        logical :: has_dot, has_e
483
+
484
+        start_pos = pos
485
+        has_dot = .false.
486
+        has_e = .false.
487
+
488
+        do while (pos <= len(line))
489
+            if (is_digit(line(pos:pos))) then
490
+                pos = pos + 1
491
+            else if (line(pos:pos) == '.' .and. .not. has_dot .and. .not. has_e) then
492
+                has_dot = .true.
493
+                pos = pos + 1
494
+            else if ((line(pos:pos) == 'e' .or. line(pos:pos) == 'E') .and. .not. has_e) then
495
+                has_e = .true.
496
+                pos = pos + 1
497
+                if (pos <= len(line) .and. (line(pos:pos) == '+' .or. line(pos:pos) == '-')) then
498
+                    pos = pos + 1
499
+                end if
500
+            else
501
+                exit
502
+            end if
503
+        end do
504
+
505
+        token_count = token_count + 1
506
+        tokens(token_count)%type = TOKEN_NUMBER
507
+        tokens(token_count)%start_col = start_pos
508
+        tokens(token_count)%end_col = pos - 1
509
+    end subroutine process_number
510
+
511
+    subroutine process_word(highlighter, line, tokens, token_count, pos)
512
+        type(syntax_highlighter_t), intent(in) :: highlighter
513
+        character(len=*), intent(in) :: line
514
+        type(token_t), intent(inout) :: tokens(:)
515
+        integer, intent(inout) :: token_count, pos
516
+        integer :: start_pos, end_pos, i
517
+        character(len=:), allocatable :: word
518
+        logical :: is_keyword, is_type
519
+
520
+        start_pos = pos
521
+
522
+        ! Find end of word
523
+        do while (pos <= len(line) .and. is_alnum(line(pos:pos)))
524
+            pos = pos + 1
525
+        end do
526
+        end_pos = pos - 1
527
+
528
+        word = line(start_pos:end_pos)
529
+
530
+        ! Check if it's a keyword
531
+        is_keyword = .false.
532
+        if (allocated(highlighter%current_lang%keywords)) then
533
+            do i = 1, size(highlighter%current_lang%keywords)
534
+                if (compare_word(word, highlighter%current_lang%keywords(i), &
535
+                                highlighter%current_lang%case_sensitive)) then
536
+                    is_keyword = .true.
537
+                    exit
538
+                end if
539
+            end do
540
+        end if
541
+
542
+        ! Check if it's a type
543
+        is_type = .false.
544
+        if (.not. is_keyword .and. allocated(highlighter%current_lang%types)) then
545
+            do i = 1, size(highlighter%current_lang%types)
546
+                if (compare_word(word, highlighter%current_lang%types(i), &
547
+                                highlighter%current_lang%case_sensitive)) then
548
+                    is_type = .true.
549
+                    exit
550
+                end if
551
+            end do
552
+        end if
553
+
554
+        token_count = token_count + 1
555
+        tokens(token_count)%start_col = start_pos
556
+        tokens(token_count)%end_col = end_pos
557
+
558
+        if (is_keyword) then
559
+            tokens(token_count)%type = TOKEN_KEYWORD
560
+        else if (is_type) then
561
+            tokens(token_count)%type = TOKEN_TYPE
562
+        else
563
+            tokens(token_count)%type = TOKEN_PLAIN
564
+        end if
565
+    end subroutine process_word
566
+
567
+    function compare_word(word1, word2, case_sensitive) result(match)
568
+        character(len=*), intent(in) :: word1, word2
569
+        logical, intent(in) :: case_sensitive
570
+        logical :: match
571
+
572
+        if (case_sensitive) then
573
+            match = trim(word1) == trim(word2)
574
+        else
575
+            match = to_lower(trim(word1)) == to_lower(trim(word2))
576
+        end if
577
+    end function compare_word
578
+
579
+    function to_lower(str) result(lower_str)
580
+        character(len=*), intent(in) :: str
581
+        character(len=len(str)) :: lower_str
582
+        integer :: i
583
+
584
+        lower_str = str
585
+        do i = 1, len(str)
586
+            if (str(i:i) >= 'A' .and. str(i:i) <= 'Z') then
587
+                lower_str(i:i) = char(ichar(str(i:i)) + 32)
588
+            end if
589
+        end do
590
+    end function to_lower
591
+
592
+    ! C language support
593
+    subroutine load_c_syntax(highlighter)
594
+        type(syntax_highlighter_t), intent(inout) :: highlighter
595
+
596
+        highlighter%current_lang%name = "c"
597
+        highlighter%current_lang%case_sensitive = .true.
598
+
599
+        ! Keywords
600
+        allocate(highlighter%current_lang%keywords(32))
601
+        highlighter%current_lang%keywords = [ &
602
+            "auto        ", "break       ", "case        ", "char        ", &
603
+            "const       ", "continue    ", "default     ", "do          ", &
604
+            "double      ", "else        ", "enum        ", "extern      ", &
605
+            "float       ", "for         ", "goto        ", "if          ", &
606
+            "inline      ", "int         ", "long        ", "register    ", &
607
+            "return      ", "short       ", "signed      ", "sizeof      ", &
608
+            "static      ", "struct      ", "switch      ", "typedef     ", &
609
+            "union       ", "unsigned    ", "void        ", "while       " &
610
+        ]
611
+
612
+        ! Types
613
+        allocate(highlighter%current_lang%types(8))
614
+        highlighter%current_lang%types = [ &
615
+            "size_t      ", "uint32_t    ", "int32_t     ", "uint64_t    ", &
616
+            "int64_t     ", "bool        ", "FILE        ", "NULL        " &
617
+        ]
618
+
619
+        highlighter%current_lang%comment_single = "//"
620
+        highlighter%current_lang%comment_start = "/*"
621
+        highlighter%current_lang%comment_end = "*/"
622
+
623
+        allocate(highlighter%current_lang%string_delimiters(2))
624
+        highlighter%current_lang%string_delimiters = ['"', "'"]
625
+
626
+        allocate(highlighter%current_lang%operators(20))
627
+        highlighter%current_lang%operators = [ &
628
+            "+   ", "-   ", "*   ", "/   ", "%   ", "=   ", &
629
+            "==  ", "!=  ", "<   ", ">   ", "<=  ", ">=  ", &
630
+            "&&  ", "||  ", "!   ", "&   ", "|   ", "^   ", &
631
+            "<<  ", ">>  " &
632
+        ]
633
+
634
+        highlighter%enabled = .true.
635
+    end subroutine load_c_syntax
636
+
637
+    ! C++ language support
638
+    subroutine load_cpp_syntax(highlighter)
639
+        type(syntax_highlighter_t), intent(inout) :: highlighter
640
+
641
+        highlighter%current_lang%name = "cpp"
642
+        highlighter%current_lang%case_sensitive = .true.
643
+
644
+        ! Keywords (C++ specific + C keywords)
645
+        allocate(highlighter%current_lang%keywords(48))
646
+        highlighter%current_lang%keywords = [ &
647
+            "auto        ", "break       ", "case        ", "char        ", &
648
+            "const       ", "continue    ", "default     ", "do          ", &
649
+            "double      ", "else        ", "enum        ", "extern      ", &
650
+            "float       ", "for         ", "goto        ", "if          ", &
651
+            "inline      ", "int         ", "long        ", "register    ", &
652
+            "return      ", "short       ", "signed      ", "sizeof      ", &
653
+            "static      ", "struct      ", "switch      ", "typedef     ", &
654
+            "union       ", "unsigned    ", "void        ", "while       ", &
655
+            "class       ", "namespace   ", "template    ", "typename    ", &
656
+            "new         ", "delete      ", "this        ", "friend      ", &
657
+            "virtual     ", "override    ", "final       ", "public      ", &
658
+            "private     ", "protected   ", "try         ", "catch       " &
659
+        ]
660
+
661
+        ! Types
662
+        allocate(highlighter%current_lang%types(12))
663
+        highlighter%current_lang%types = [ &
664
+            "std         ", "string      ", "vector      ", "map         ", &
665
+            "set         ", "pair        ", "unique_ptr  ", "shared_ptr  ", &
666
+            "nullptr     ", "true        ", "false       ", "bool        " &
667
+        ]
668
+
669
+        highlighter%current_lang%comment_single = "//"
670
+        highlighter%current_lang%comment_start = "/*"
671
+        highlighter%current_lang%comment_end = "*/"
672
+
673
+        allocate(highlighter%current_lang%string_delimiters(2))
674
+        highlighter%current_lang%string_delimiters = ['"', "'"]
675
+
676
+        allocate(highlighter%current_lang%operators(22))
677
+        highlighter%current_lang%operators = [ &
678
+            "+   ", "-   ", "*   ", "/   ", "%   ", "=   ", &
679
+            "==  ", "!=  ", "<   ", ">   ", "<=  ", ">=  ", &
680
+            "&&  ", "||  ", "!   ", "&   ", "|   ", "^   ", &
681
+            "<<  ", ">>  ", "::  ", "->  " &
682
+        ]
683
+
684
+        highlighter%enabled = .true.
685
+    end subroutine load_cpp_syntax
686
+
687
+    ! Rust language support
688
+    subroutine load_rust_syntax(highlighter)
689
+        type(syntax_highlighter_t), intent(inout) :: highlighter
690
+
691
+        highlighter%current_lang%name = "rust"
692
+        highlighter%current_lang%case_sensitive = .true.
693
+
694
+        ! Keywords
695
+        allocate(highlighter%current_lang%keywords(40))
696
+        highlighter%current_lang%keywords = [ &
697
+            "as          ", "async       ", "await       ", "break       ", &
698
+            "const       ", "continue    ", "crate       ", "dyn         ", &
699
+            "else        ", "enum        ", "extern      ", "false       ", &
700
+            "fn          ", "for         ", "if          ", "impl        ", &
701
+            "in          ", "let         ", "loop        ", "match       ", &
702
+            "mod         ", "move        ", "mut         ", "pub         ", &
703
+            "ref         ", "return      ", "self        ", "Self        ", &
704
+            "static      ", "struct      ", "super       ", "trait       ", &
705
+            "true        ", "type        ", "unsafe      ", "use         ", &
706
+            "where       ", "while       ", "async       ", "await       " &
707
+        ]
708
+
709
+        ! Types
710
+        allocate(highlighter%current_lang%types(16))
711
+        highlighter%current_lang%types = [ &
712
+            "i8          ", "i16         ", "i32         ", "i64         ", &
713
+            "i128        ", "u8          ", "u16         ", "u32         ", &
714
+            "u64         ", "u128        ", "f32         ", "f64         ", &
715
+            "bool        ", "char        ", "str         ", "String      " &
716
+        ]
717
+
718
+        highlighter%current_lang%comment_single = "//"
719
+        highlighter%current_lang%comment_start = "/*"
720
+        highlighter%current_lang%comment_end = "*/"
721
+
722
+        allocate(highlighter%current_lang%string_delimiters(2))
723
+        highlighter%current_lang%string_delimiters = ['"', "'"]
724
+
725
+        allocate(highlighter%current_lang%operators(20))
726
+        highlighter%current_lang%operators = [ &
727
+            "+   ", "-   ", "*   ", "/   ", "%   ", "=   ", &
728
+            "==  ", "!=  ", "<   ", ">   ", "<=  ", ">=  ", &
729
+            "&&  ", "||  ", "!   ", "&   ", "|   ", "^   ", &
730
+            "::  ", "->  " &
731
+        ]
732
+
733
+        highlighter%enabled = .true.
734
+    end subroutine load_rust_syntax
735
+
736
+    ! Go language support
737
+    subroutine load_go_syntax(highlighter)
738
+        type(syntax_highlighter_t), intent(inout) :: highlighter
739
+
740
+        highlighter%current_lang%name = "go"
741
+        highlighter%current_lang%case_sensitive = .true.
742
+
743
+        ! Keywords
744
+        allocate(highlighter%current_lang%keywords(25))
745
+        highlighter%current_lang%keywords = [ &
746
+            "break       ", "case        ", "chan        ", "const       ", &
747
+            "continue    ", "default     ", "defer       ", "else        ", &
748
+            "fallthrough ", "for         ", "func        ", "go          ", &
749
+            "goto        ", "if          ", "import      ", "interface   ", &
750
+            "map         ", "package     ", "range       ", "return      ", &
751
+            "select      ", "struct      ", "switch      ", "type        ", &
752
+            "var         " &
753
+        ]
754
+
755
+        ! Types
756
+        allocate(highlighter%current_lang%types(15))
757
+        highlighter%current_lang%types = [ &
758
+            "bool        ", "byte        ", "complex64   ", "complex128  ", &
759
+            "error       ", "float32     ", "float64     ", "int         ", &
760
+            "int8        ", "int16       ", "int32       ", "int64       ", &
761
+            "string      ", "uint        ", "nil         " &
762
+        ]
763
+
764
+        highlighter%current_lang%comment_single = "//"
765
+        highlighter%current_lang%comment_start = "/*"
766
+        highlighter%current_lang%comment_end = "*/"
767
+
768
+        allocate(highlighter%current_lang%string_delimiters(3))
769
+        highlighter%current_lang%string_delimiters = ['"', "'", '`']
770
+
771
+        allocate(highlighter%current_lang%operators(18))
772
+        highlighter%current_lang%operators = [ &
773
+            "+   ", "-   ", "*   ", "/   ", "%   ", "=   ", &
774
+            "==  ", "!=  ", "<   ", ">   ", "<=  ", ">=  ", &
775
+            "&&  ", "||  ", "!   ", "&   ", "|   ", ":=  " &
776
+        ]
777
+
778
+        highlighter%enabled = .true.
779
+    end subroutine load_go_syntax
780
+
781
+    ! JavaScript language support
782
+    subroutine load_javascript_syntax(highlighter)
783
+        type(syntax_highlighter_t), intent(inout) :: highlighter
784
+
785
+        highlighter%current_lang%name = "javascript"
786
+        highlighter%current_lang%case_sensitive = .true.
787
+
788
+        ! Keywords
789
+        allocate(highlighter%current_lang%keywords(38))
790
+        highlighter%current_lang%keywords = [ &
791
+            "async       ", "await       ", "break       ", "case        ", &
792
+            "catch       ", "class       ", "const       ", "continue    ", &
793
+            "debugger    ", "default     ", "delete      ", "do          ", &
794
+            "else        ", "export      ", "extends     ", "finally     ", &
795
+            "for         ", "function    ", "if          ", "import      ", &
796
+            "in          ", "instanceof  ", "let         ", "new         ", &
797
+            "return      ", "super       ", "switch      ", "this        ", &
798
+            "throw       ", "try         ", "typeof      ", "var         ", &
799
+            "void        ", "while       ", "with        ", "yield       ", &
800
+            "true        ", "false       " &
801
+        ]
802
+
803
+        ! Types
804
+        allocate(highlighter%current_lang%types(10))
805
+        highlighter%current_lang%types = [ &
806
+            "null        ", "undefined   ", "Boolean     ", "Number      ", &
807
+            "String      ", "Symbol      ", "Object      ", "Array       ", &
808
+            "Function    ", "Promise     " &
809
+        ]
810
+
811
+        highlighter%current_lang%comment_single = "//"
812
+        highlighter%current_lang%comment_start = "/*"
813
+        highlighter%current_lang%comment_end = "*/"
814
+
815
+        allocate(highlighter%current_lang%string_delimiters(3))
816
+        highlighter%current_lang%string_delimiters = ['"', "'", '`']
817
+
818
+        allocate(highlighter%current_lang%operators(20))
819
+        highlighter%current_lang%operators = [ &
820
+            "+   ", "-   ", "*   ", "/   ", "%   ", "=   ", &
821
+            "==  ", "=== ", "!=  ", "!== ", "<   ", ">   ", &
822
+            "<=  ", ">=  ", "&&  ", "||  ", "!   ", "?   ", &
823
+            ":   ", "=>  " &
824
+        ]
825
+
826
+        highlighter%enabled = .true.
827
+    end subroutine load_javascript_syntax
828
+
829
+    ! TypeScript language support (extends JavaScript)
830
+    subroutine load_typescript_syntax(highlighter)
831
+        type(syntax_highlighter_t), intent(inout) :: highlighter
832
+
833
+        ! Start with JavaScript syntax
834
+        call load_javascript_syntax(highlighter)
835
+        highlighter%current_lang%name = "typescript"
836
+
837
+        ! Add TypeScript-specific keywords
838
+        deallocate(highlighter%current_lang%keywords)
839
+        allocate(highlighter%current_lang%keywords(45))
840
+        highlighter%current_lang%keywords = [ &
841
+            "async       ", "await       ", "break       ", "case        ", &
842
+            "catch       ", "class       ", "const       ", "continue    ", &
843
+            "debugger    ", "default     ", "delete      ", "do          ", &
844
+            "else        ", "export      ", "extends     ", "finally     ", &
845
+            "for         ", "function    ", "if          ", "import      ", &
846
+            "in          ", "instanceof  ", "let         ", "new         ", &
847
+            "return      ", "super       ", "switch      ", "this        ", &
848
+            "throw       ", "try         ", "typeof      ", "var         ", &
849
+            "void        ", "while       ", "with        ", "yield       ", &
850
+            "true        ", "false       ", "enum        ", "interface   ", &
851
+            "type        ", "namespace   ", "module      ", "declare     ", &
852
+            "abstract    " &
853
+        ]
854
+
855
+        ! Add TypeScript types
856
+        deallocate(highlighter%current_lang%types)
857
+        allocate(highlighter%current_lang%types(15))
858
+        highlighter%current_lang%types = [ &
859
+            "null        ", "undefined   ", "boolean     ", "number      ", &
860
+            "string      ", "symbol      ", "object      ", "any         ", &
861
+            "unknown     ", "never       ", "void        ", "Array       ", &
862
+            "Function    ", "Promise     ", "ReadonlyArra" &
863
+        ]
864
+
865
+        highlighter%enabled = .true.
866
+    end subroutine load_typescript_syntax
867
+
868
+    ! Bash/Shell script support
869
+    subroutine load_bash_syntax(highlighter)
870
+        type(syntax_highlighter_t), intent(inout) :: highlighter
871
+
872
+        highlighter%current_lang%name = "bash"
873
+        highlighter%current_lang%case_sensitive = .true.
874
+
875
+        ! Keywords
876
+        allocate(highlighter%current_lang%keywords(30))
877
+        highlighter%current_lang%keywords = [ &
878
+            "if          ", "then        ", "else        ", "elif        ", &
879
+            "fi          ", "for         ", "while       ", "do          ", &
880
+            "done        ", "case        ", "esac        ", "function    ", &
881
+            "return      ", "break       ", "continue    ", "exit        ", &
882
+            "export      ", "source      ", "alias       ", "unset       ", &
883
+            "shift       ", "local       ", "declare     ", "readonly    ", &
884
+            "echo        ", "printf      ", "read        ", "cd          ", &
885
+            "pwd         ", "ls          " &
886
+        ]
887
+
888
+        ! Built-in variables/types
889
+        allocate(highlighter%current_lang%types(10))
890
+        highlighter%current_lang%types = [ &
891
+            "$0          ", "$1          ", "$@          ", "$*          ", &
892
+            "$#          ", "$?          ", "$$          ", "$!          ", &
893
+            "true        ", "false       " &
894
+        ]
895
+
896
+        highlighter%current_lang%comment_single = "#"
897
+
898
+        allocate(highlighter%current_lang%string_delimiters(3))
899
+        highlighter%current_lang%string_delimiters = ['"', "'", '`']
900
+
901
+        allocate(highlighter%current_lang%operators(15))
902
+        highlighter%current_lang%operators = [ &
903
+            "=   ", "==  ", "!=  ", "<   ", ">   ", &
904
+            "-eq ", "-ne ", "-lt ", "-gt ", "-le ", &
905
+            "-ge ", "&&  ", "||  ", "|   ", "&   " &
906
+        ]
907
+
908
+        highlighter%enabled = .true.
909
+    end subroutine load_bash_syntax
910
+
911
+    ! Markdown support (special handling needed)
912
+    subroutine load_markdown_syntax(highlighter)
913
+        type(syntax_highlighter_t), intent(inout) :: highlighter
914
+
915
+        highlighter%current_lang%name = "markdown"
916
+        highlighter%current_lang%case_sensitive = .true.
917
+
918
+        ! Headers and emphasis markers as "keywords"
919
+        allocate(highlighter%current_lang%keywords(8))
920
+        highlighter%current_lang%keywords = [ &
921
+            "#           ", "##          ", "###         ", "####        ", &
922
+            "#####       ", "######      ", "*           ", "_           " &
923
+        ]
924
+
925
+        ! Code block languages as "types"
926
+        allocate(highlighter%current_lang%types(10))
927
+        highlighter%current_lang%types = [ &
928
+            "```         ", "```python   ", "```bash     ", "```fortran  ", &
929
+            "```c        ", "```cpp      ", "```rust     ", "```go       ", &
930
+            "```javascri ", "```typescri " &
931
+        ]
932
+
933
+        ! No traditional comments in markdown
934
+        highlighter%current_lang%comment_single = ""
935
+
936
+        ! Links and code spans
937
+        allocate(highlighter%current_lang%string_delimiters(2))
938
+        highlighter%current_lang%string_delimiters = ['`', '[']
939
+
940
+        ! List markers and special chars
941
+        allocate(highlighter%current_lang%operators(6))
942
+        highlighter%current_lang%operators = [ &
943
+            "-   ", "+   ", "*   ", ">   ", "|   ", "!   " &
944
+        ]
945
+
946
+        highlighter%enabled = .true.
947
+    end subroutine load_markdown_syntax
948
+
949
+    ! Stub implementations for multiline handling
950
+    subroutine process_multiline_comment(highlighter, line, tokens, token_count, pos)
951
+        type(syntax_highlighter_t), intent(inout) :: highlighter
952
+        character(len=*), intent(in) :: line
953
+        type(token_t), intent(inout) :: tokens(:)
954
+        integer, intent(inout) :: token_count, pos
955
+        ! TODO: Implement multiline comment handling
956
+    end subroutine process_multiline_comment
957
+
958
+    subroutine process_multiline_string(highlighter, line, tokens, token_count, pos)
959
+        type(syntax_highlighter_t), intent(inout) :: highlighter
960
+        character(len=*), intent(in) :: line
961
+        type(token_t), intent(inout) :: tokens(:)
962
+        integer, intent(inout) :: token_count, pos
963
+        ! TODO: Implement multiline string handling
964
+    end subroutine process_multiline_string
965
+
966
+end module syntax_highlighter_module
src/terminal/renderer_module.f90modified
@@ -7,6 +7,7 @@ module renderer_module
77
     use bracket_matching_module
88
     use file_tree_module
99
     use file_tree_renderer_module
10
+    use syntax_highlighter_module
1011
     implicit none
1112
     private
1213
 
@@ -40,10 +41,14 @@ module renderer_module
4041
     ! File tree state (for fuss mode)
4142
     type(tree_state_t) :: tree_state
4243
 
44
+    ! Syntax highlighting state
45
+    type(syntax_highlighter_t) :: syntax_highlighter
46
+
4347
 contains
4448
 
45
-    subroutine init_renderer(rows, cols)
49
+    subroutine init_renderer(rows, cols, filename)
4650
         integer, intent(in) :: rows, cols
51
+        character(len=*), intent(in), optional :: filename
4752
         integer :: i
4853
 
4954
         screen_buffer%rows = rows
@@ -54,10 +59,18 @@ contains
5459
         do i = 1, rows
5560
             screen_buffer%lines(i) = repeat(' ', cols)
5661
         end do
62
+
63
+        ! Initialize syntax highlighter if filename provided
64
+        if (present(filename)) then
65
+            call init_highlighter(syntax_highlighter, filename)
66
+        else
67
+            call init_highlighter(syntax_highlighter)
68
+        end if
5769
     end subroutine init_renderer
5870
 
5971
     subroutine cleanup_renderer()
6072
         if (allocated(screen_buffer%lines)) deallocate(screen_buffer%lines)
73
+        call cleanup_highlighter(syntax_highlighter)
6174
     end subroutine cleanup_renderer
6275
 
6376
     subroutine render_screen(buffer, editor, match_mode_active, match_case_sens)
@@ -203,14 +216,26 @@ contains
203216
         type(editor_state_t), intent(in) :: editor
204217
         integer, intent(in) :: line_num, start_col, width
205218
         character(len=:), allocatable :: line
206
-        integer :: i, col, line_len
219
+        integer :: i, col, line_len, token_idx
207220
         integer :: sel_start_line, sel_start_col, sel_end_line, sel_end_col
208221
         logical :: in_selection, is_bracket_match, is_current_line
209222
         character :: ch
223
+        type(token_t), allocatable :: tokens(:)
224
+        character(len=:), allocatable :: token_color
210225
 
211226
         line = buffer_get_line(buffer, line_num)
212227
         line_len = len(line)
213228
 
229
+        ! Get syntax tokens for this line
230
+        if (syntax_highlighter%enabled) then
231
+            call tokenize_line(syntax_highlighter, line, tokens)
232
+        else
233
+            allocate(tokens(1))
234
+            tokens(1)%type = TOKEN_PLAIN
235
+            tokens(1)%start_col = 1
236
+            tokens(1)%end_col = max(1, line_len)
237
+        end if
238
+
214239
         ! Check if this is the current line
215240
         is_current_line = (line_num == editor%cursors(editor%active_cursor)%line) .and. highlight_current_line
216241
 
@@ -272,6 +297,17 @@ contains
272297
                 is_bracket_match = .true.
273298
             end if
274299
 
300
+            ! Find which token this column belongs to
301
+            token_color = ""
302
+            if (syntax_highlighter%enabled) then
303
+                do token_idx = 1, size(tokens)
304
+                    if (col >= tokens(token_idx)%start_col .and. col <= tokens(token_idx)%end_col) then
305
+                        token_color = get_token_color(tokens(token_idx)%type)
306
+                        exit
307
+                    end if
308
+                end do
309
+            end if
310
+
275311
             ! Render character with or without highlighting
276312
             if (col <= line_len) then
277313
                 ch = line(col:col)
@@ -286,8 +322,15 @@ contains
286322
                 ! Highlight matching brackets with cyan background
287323
                 call terminal_write(char(27) // '[46m' // ch // char(27) // '[0m')
288324
             else if (is_current_line) then
289
-                ! Subtle background for current line (dark gray)
290
-                call terminal_write(char(27) // '[48;5;236m' // ch // char(27) // '[0m')
325
+                ! Subtle background for current line with syntax color
326
+                if (len(token_color) > 0) then
327
+                    call terminal_write(token_color // char(27) // '[48;5;236m' // ch // char(27) // '[0m')
328
+                else
329
+                    call terminal_write(char(27) // '[48;5;236m' // ch // char(27) // '[0m')
330
+                end if
331
+            else if (len(token_color) > 0) then
332
+                ! Apply syntax highlighting
333
+                call terminal_write(token_color // ch // char(27) // '[0m')
291334
             else
292335
                 call terminal_write(ch)
293336
             end if