fortrangoingonforty/facsimile / ad49903

Browse files

Add LSP integration to editor

- Integrate LSP manager into editor state with init/cleanup
- Start LSP servers automatically when opening supported files
- Send textDocument/didOpen notifications when files are opened
- Process LSP messages in main event loop
- Add buffer_to_string function for converting buffer to string
- Add helper functions for language detection and file events:
- get_language_for_file: Detect language from file extension
- start_lsp_for_file: Start appropriate LSP server for file
- notify_file_opened/changed/closed: Send LSP notifications
- Support for C/C++, Python, Rust, Go, TypeScript, Fortran
- Add tests/lsp directory with test programs and documentation
- Add Makefile targets for LSP testing (test-lsp, test-lsp-editor)
- Move LSP modules earlier in build order (before editor_state_module)

Co-Authored-By: mfwolffe <mfwolffe@mtu.edu>
Authored by espadonne
SHA
ad49903102ca670b025971dfd50853ebaefcab8e
Parents
fa6b66e
Tree
adbfd35

9 changed files

StatusFile+-
M Makefile 14 7
M app/main.f90 14 0
M src/buffer/text_buffer_module.f90 30 0
M src/editor_state_module.f90 19 0
M src/lsp/lsp_server_manager_module.f90 120 0
A tests/lsp/README.md 64 0
A tests/lsp/sample.c 13 0
A tests/lsp/test_json.f90 34 0
A tests/lsp/test_lsp_init.f90 59 0
Makefilemodified
@@ -75,6 +75,10 @@ SOURCES = src/version_module.f90 \
75
           src/terminal/terminal_io_module.f90 \
75
           src/terminal/terminal_io_module.f90 \
76
           src/terminal/input_handler_module.f90 \
76
           src/terminal/input_handler_module.f90 \
77
           src/utils/bracket_matching_module.f90 \
77
           src/utils/bracket_matching_module.f90 \
78
+          src/lsp/json_module.f90 \
79
+          src/lsp/lsp_protocol_module.f90 \
80
+          src/lsp/lsp_server_manager_module.f90 \
81
+          src/lsp/lsp_client_module.f90 \
78
           src/editor_state_module.f90 \
82
           src/editor_state_module.f90 \
79
           src/undo/undo_stack_module.f90 \
83
           src/undo/undo_stack_module.f90 \
80
           src/workspace/file_tree_module.f90 \
84
           src/workspace/file_tree_module.f90 \
@@ -86,10 +90,6 @@ SOURCES = src/version_module.f90 \
86
           src/workspace/workspace_module.f90 \
90
           src/workspace/workspace_module.f90 \
87
           src/workspace/backup_module.f90 \
91
           src/workspace/backup_module.f90 \
88
           src/syntax/syntax_highlighter_module.f90 \
92
           src/syntax/syntax_highlighter_module.f90 \
89
-          src/lsp/json_module.f90 \
90
-          src/lsp/lsp_protocol_module.f90 \
91
-          src/lsp/lsp_server_manager_module.f90 \
92
-          src/lsp/lsp_client_module.f90 \
93
           src/terminal/renderer_module.f90 \
93
           src/terminal/renderer_module.f90 \
94
           src/ui/help_display_module.f90 \
94
           src/ui/help_display_module.f90 \
95
           src/ui/text_prompt_module.f90 \
95
           src/ui/text_prompt_module.f90 \
@@ -221,10 +221,17 @@ lsp-modules: src/lsp/json_module.o src/lsp/lsp_protocol_module.o src/lsp/lsp_ser
221
 
221
 
222
 test-lsp: lsp-modules
222
 test-lsp: lsp-modules
223
 	@echo "Testing LSP JSON parser..."
223
 	@echo "Testing LSP JSON parser..."
224
-	@$(FC) $(FFLAGS_DEBUG) /tmp/test_json.f90 src/lsp/json_module.o -o /tmp/test_json 2>/dev/null && /tmp/test_json || true
224
+	@$(FC) $(FFLAGS_DEBUG) tests/lsp/test_json.f90 src/lsp/json_module.o -o tests/lsp/test_json 2>/dev/null && tests/lsp/test_json || true
225
 	@echo ""
225
 	@echo ""
226
 	@echo "Testing LSP initialization..."
226
 	@echo "Testing LSP initialization..."
227
-	@$(FC) $(FFLAGS_DEBUG) /tmp/test_lsp_init.f90 src/lsp/json_module.o src/lsp/lsp_protocol_module.o src/lsp/lsp_process_wrapper.o src/lsp/lsp_client_module.o src/lsp/lsp_server_manager_module.o -o /tmp/test_lsp_init 2>/dev/null && /tmp/test_lsp_init || true
227
+	@$(FC) $(FFLAGS_DEBUG) tests/lsp/test_lsp_init.f90 src/lsp/json_module.o src/lsp/lsp_protocol_module.o src/lsp/lsp_process_wrapper.o src/lsp/lsp_client_module.o src/lsp/lsp_server_manager_module.o -o tests/lsp/test_lsp_init 2>/dev/null && tests/lsp/test_lsp_init || true
228
+
229
+test-lsp-editor: all
230
+	@echo "Testing LSP in editor with sample C file..."
231
+	@echo "Opening tests/lsp/sample.c - check for LSP server initialization"
232
+	@timeout 2 ./fac tests/lsp/sample.c < /dev/null 2>&1 | grep -q "LSP server initialized" && \
233
+		echo "✓ LSP server initialized for C file" || \
234
+		echo "✗ LSP server did not initialize (check if clangd is installed)"
228
 
235
 
229
 clean-lsp:
236
 clean-lsp:
230
 	rm -f src/lsp/*.o src/lsp/*.mod /tmp/test_json /tmp/test_lsp_init /tmp/test_lsp_raw
237
 	rm -f src/lsp/*.o src/lsp/*.mod /tmp/test_json /tmp/test_lsp_init /tmp/test_lsp_raw
@@ -234,4 +241,4 @@ lsp-dev: clean-lsp
234
 	@echo "Building LSP modules with debug flags..."
241
 	@echo "Building LSP modules with debug flags..."
235
 	@$(MAKE) lsp-modules FFLAGS="$(FFLAGS_DEBUG)" CFLAGS="$(CFLAGS_DEV)"
242
 	@$(MAKE) lsp-modules FFLAGS="$(FFLAGS_DEBUG)" CFLAGS="$(CFLAGS_DEV)"
236
 
243
 
237
-.PHONY: all clean dev debug info bump-patch bump-minor bump-major version release lsp-modules test-lsp clean-lsp lsp-dev
244
+.PHONY: all clean dev debug info bump-patch bump-minor bump-major version release lsp-modules test-lsp test-lsp-editor clean-lsp lsp-dev
app/main.f90modified
@@ -13,6 +13,8 @@ program facsimile
13
     use welcome_menu_module, only: show_welcome_menu
13
     use welcome_menu_module, only: show_welcome_menu
14
     use fortress_navigator_module, only: open_fortress_navigator
14
     use fortress_navigator_module, only: open_fortress_navigator
15
     use binary_prompt_module, only: binary_file_prompt
15
     use binary_prompt_module, only: binary_file_prompt
16
+    use lsp_server_manager_module, only: notify_file_opened, notify_file_changed, &
17
+                                         notify_file_closed, process_server_messages
16
     implicit none
18
     implicit none
17
 
19
 
18
     type(editor_state_t) :: editor
20
     type(editor_state_t) :: editor
@@ -284,6 +286,15 @@ program facsimile
284
             if (allocated(editor%filename)) deallocate(editor%filename)
286
             if (allocated(editor%filename)) deallocate(editor%filename)
285
             allocate(character(len=len_trim(filename)) :: editor%filename)
287
             allocate(character(len=len_trim(filename)) :: editor%filename)
286
             editor%filename = trim(filename)
288
             editor%filename = trim(filename)
289
+
290
+            ! Send LSP didOpen notification if LSP server is active
291
+            if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
292
+                if (editor%tabs(editor%active_tab_index)%lsp_server_index > 0) then
293
+                    call notify_file_opened(editor%lsp_manager, &
294
+                        editor%tabs(editor%active_tab_index)%lsp_server_index, &
295
+                        trim(filename), buffer_to_string(buffer))
296
+                end if
297
+            end if
287
         else if (status == -2) then
298
         else if (status == -2) then
288
             ! Binary file detected - prompt user
299
             ! Binary file detected - prompt user
289
             if (binary_file_prompt(trim(filename))) then
300
             if (binary_file_prompt(trim(filename))) then
@@ -337,6 +348,9 @@ program facsimile
337
 
348
 
338
     ! Main event loop
349
     ! Main event loop
339
     do while (running)
350
     do while (running)
351
+        ! Process any LSP messages
352
+        call process_server_messages(editor%lsp_manager)
353
+
340
         ! Get input
354
         ! Get input
341
         call get_key_input(key_input, status)
355
         call get_key_input(key_input, status)
342
 
356
 
src/buffer/text_buffer_module.f90modified
@@ -10,6 +10,7 @@ module text_buffer_module
10
     public :: buffer_load_file, buffer_save_file, buffer_load_file_as_hex
10
     public :: buffer_load_file, buffer_save_file, buffer_load_file_as_hex
11
     public :: buffer_move_gap
11
     public :: buffer_move_gap
12
     public :: buffer_char_at, buffer_byte_to_char_col, buffer_char_to_byte_col
12
     public :: buffer_char_at, buffer_byte_to_char_col, buffer_char_to_byte_col
13
+    public :: buffer_to_string
13
 
14
 
14
     integer, parameter :: INITIAL_SIZE = 8192
15
     integer, parameter :: INITIAL_SIZE = 8192
15
     integer, parameter :: GROW_FACTOR = 2
16
     integer, parameter :: GROW_FACTOR = 2
@@ -545,4 +546,33 @@ contains
545
         end do
546
         end do
546
     end subroutine format_hex_line
547
     end subroutine format_hex_line
547
 
548
 
549
+    ! Convert buffer contents to a string
550
+    function buffer_to_string(buffer) result(str)
551
+        type(buffer_t), intent(in) :: buffer
552
+        character(len=:), allocatable :: str
553
+        integer :: content_len
554
+
555
+        if (buffer%gap_start == 1) then
556
+            ! No content before gap
557
+            content_len = buffer%size - buffer%gap_end + 1
558
+            if (content_len > 0) then
559
+                allocate(character(len=content_len) :: str)
560
+                str = buffer%data(buffer%gap_end:buffer%size)
561
+            else
562
+                allocate(character(len=0) :: str)
563
+                str = ""
564
+            end if
565
+        else if (buffer%gap_end > buffer%size) then
566
+            ! No gap (full buffer)
567
+            content_len = buffer%gap_start - 1
568
+            allocate(character(len=content_len) :: str)
569
+            str = buffer%data(1:content_len)
570
+        else
571
+            ! Content on both sides of gap
572
+            content_len = (buffer%gap_start - 1) + (buffer%size - buffer%gap_end + 1)
573
+            allocate(character(len=content_len) :: str)
574
+            str = buffer%data(1:buffer%gap_start-1) // buffer%data(buffer%gap_end:buffer%size)
575
+        end if
576
+    end function buffer_to_string
577
+
548
 end module text_buffer_module
578
 end module text_buffer_module
src/editor_state_module.f90modified
@@ -1,6 +1,10 @@
1
 module editor_state_module
1
 module editor_state_module
2
     use iso_fortran_env, only: int32, int64
2
     use iso_fortran_env, only: int32, int64
3
     use text_buffer_module, only: buffer_t, copy_buffer, init_buffer
3
     use text_buffer_module, only: buffer_t, copy_buffer, init_buffer
4
+    use lsp_server_manager_module, only: lsp_manager_t, init_lsp_manager, cleanup_lsp_manager, &
5
+                                         get_or_start_server, process_server_messages, &
6
+                                         start_lsp_for_file, notify_file_opened, &
7
+                                         notify_file_changed, notify_file_closed
4
     implicit none
8
     implicit none
5
     private
9
     private
6
 
10
 
@@ -66,6 +70,9 @@ module editor_state_module
66
 
70
 
67
         logical :: modified = .false.
71
         logical :: modified = .false.
68
         logical :: is_orphan = .false.  ! True if file is outside workspace (uses absolute path)
72
         logical :: is_orphan = .false.  ! True if file is outside workspace (uses absolute path)
73
+
74
+        ! LSP support
75
+        integer :: lsp_server_index = 0  ! Index of LSP server handling this file
69
     end type tab_t
76
     end type tab_t
70
 
77
 
71
     ! Main editor state
78
     ! Main editor state
@@ -87,6 +94,9 @@ module editor_state_module
87
         type(tab_t), allocatable :: tabs(:)
94
         type(tab_t), allocatable :: tabs(:)
88
         integer(int32) :: active_tab_index = 1
95
         integer(int32) :: active_tab_index = 1
89
         integer(int32) :: max_tabs = 10
96
         integer(int32) :: max_tabs = 10
97
+
98
+        ! LSP support
99
+        type(lsp_manager_t) :: lsp_manager
90
     end type editor_state_t
100
     end type editor_state_t
91
 
101
 
92
 contains
102
 contains
@@ -112,6 +122,9 @@ contains
112
         ! Initialize tabs array (empty initially)
122
         ! Initialize tabs array (empty initially)
113
         allocate(editor%tabs(0))
123
         allocate(editor%tabs(0))
114
         editor%active_tab_index = 0
124
         editor%active_tab_index = 0
125
+
126
+        ! Initialize LSP manager
127
+        call init_lsp_manager(editor%lsp_manager)
115
     end subroutine init_editor
128
     end subroutine init_editor
116
 
129
 
117
     subroutine cleanup_editor(editor)
130
     subroutine cleanup_editor(editor)
@@ -129,6 +142,9 @@ contains
129
             end do
142
             end do
130
             deallocate(editor%tabs)
143
             deallocate(editor%tabs)
131
         end if
144
         end if
145
+
146
+        ! Cleanup LSP manager
147
+        call cleanup_lsp_manager(editor%lsp_manager)
132
     end subroutine cleanup_editor
148
     end subroutine cleanup_editor
133
 
149
 
134
     ! Helper to cleanup a single tab
150
     ! Helper to cleanup a single tab
@@ -212,6 +228,9 @@ contains
212
         temp_tabs(new_index)%active_pane_index = 1
228
         temp_tabs(new_index)%active_pane_index = 1
213
         temp_tabs(new_index)%modified = .false.
229
         temp_tabs(new_index)%modified = .false.
214
 
230
 
231
+        ! Start LSP server for this file if applicable
232
+        temp_tabs(new_index)%lsp_server_index = start_lsp_for_file(editor%lsp_manager, filename)
233
+
215
         ! Replace tabs array
234
         ! Replace tabs array
216
         call move_alloc(temp_tabs, editor%tabs)
235
         call move_alloc(temp_tabs, editor%tabs)
217
         editor%active_tab_index = new_index
236
         editor%active_tab_index = new_index
src/lsp/lsp_server_manager_module.f90modified
@@ -14,6 +14,8 @@ module lsp_server_manager_module
14
     public :: send_request, send_notification
14
     public :: send_request, send_notification
15
     public :: process_server_messages
15
     public :: process_server_messages
16
     public :: register_callback
16
     public :: register_callback
17
+    public :: get_language_for_file, start_lsp_for_file
18
+    public :: notify_file_opened, notify_file_changed, notify_file_closed
17
 
19
 
18
     ! Language server configuration
20
     ! Language server configuration
19
     type :: server_config_t
21
     type :: server_config_t
@@ -622,4 +624,122 @@ contains
622
         manager%num_callbacks = manager%num_callbacks - 1
624
         manager%num_callbacks = manager%num_callbacks - 1
623
     end subroutine remove_callback
625
     end subroutine remove_callback
624
 
626
 
627
+    ! Helper to get language from filename
628
+    function get_language_for_file(filename) result(language)
629
+        character(len=*), intent(in) :: filename
630
+        character(len=:), allocatable :: language
631
+        integer :: dot_pos
632
+
633
+        ! Find last dot in filename
634
+        dot_pos = index(filename, '.', back=.true.)
635
+        if (dot_pos == 0) then
636
+            language = ""
637
+            return
638
+        end if
639
+
640
+        ! Match extension to language
641
+        select case(filename(dot_pos:))
642
+        case('.py')
643
+            language = "python"
644
+        case('.rs')
645
+            language = "rust"
646
+        case('.c', '.h')
647
+            language = "c"
648
+        case('.cpp', '.cc', '.cxx', '.hpp', '.hxx', '.C', '.H')
649
+            language = "cpp"
650
+        case('.go')
651
+            language = "go"
652
+        case('.ts', '.tsx')
653
+            language = "typescript"
654
+        case('.js', '.jsx')
655
+            language = "javascript"
656
+        case('.f90', '.f95', '.f03', '.f08', '.F90', '.F95', '.F03', '.F08')
657
+            language = "fortran"
658
+        case('.java')
659
+            language = "java"
660
+        case('.rb')
661
+            language = "ruby"
662
+        case('.lua')
663
+            language = "lua"
664
+        case default
665
+            language = ""
666
+        end select
667
+    end function get_language_for_file
668
+
669
+    ! Start LSP server for a file if needed
670
+    function start_lsp_for_file(manager, filename) result(server_index)
671
+        type(lsp_manager_t), intent(inout) :: manager
672
+        character(len=*), intent(in) :: filename
673
+        integer :: server_index
674
+        character(len=:), allocatable :: language
675
+        character(len=256) :: workspace_path
676
+        integer :: slash_pos
677
+
678
+        server_index = 0
679
+
680
+        ! Get language from file extension
681
+        language = get_language_for_file(filename)
682
+        if (language == "") return
683
+
684
+        ! Extract workspace path from filename (directory containing file)
685
+        slash_pos = index(filename, '/', back=.true.)
686
+        if (slash_pos > 0) then
687
+            workspace_path = filename(1:slash_pos-1)
688
+        else
689
+            workspace_path = "."
690
+        end if
691
+
692
+        ! Get or start server for this language
693
+        server_index = get_or_start_server(manager, language, trim(workspace_path))
694
+    end function start_lsp_for_file
695
+
696
+    ! Send textDocument/didOpen notification
697
+    subroutine notify_file_opened(manager, server_index, filename, content)
698
+        use lsp_protocol_module, only: create_did_open_notification
699
+        type(lsp_manager_t), intent(inout) :: manager
700
+        integer, intent(in) :: server_index
701
+        character(len=*), intent(in) :: filename
702
+        character(len=*), intent(in) :: content
703
+        type(lsp_message_t) :: msg
704
+        character(len=:), allocatable :: language
705
+
706
+        if (server_index < 1 .or. server_index > manager%num_servers) return
707
+        if (.not. manager%servers(server_index)%initialized) return
708
+
709
+        language = get_language_for_file(filename)
710
+        msg = create_did_open_notification(filename, language, 1, content)
711
+        call send_notification(manager%servers(server_index), msg)
712
+    end subroutine notify_file_opened
713
+
714
+    ! Send textDocument/didChange notification
715
+    subroutine notify_file_changed(manager, server_index, filename, content)
716
+        use lsp_protocol_module, only: create_did_change_notification
717
+        type(lsp_manager_t), intent(inout) :: manager
718
+        integer, intent(in) :: server_index
719
+        character(len=*), intent(in) :: filename
720
+        character(len=*), intent(in) :: content
721
+        type(lsp_message_t) :: msg
722
+
723
+        if (server_index < 1 .or. server_index > manager%num_servers) return
724
+        if (.not. manager%servers(server_index)%initialized) return
725
+
726
+        msg = create_did_change_notification(filename, 2, content)
727
+        call send_notification(manager%servers(server_index), msg)
728
+    end subroutine notify_file_changed
729
+
730
+    ! Send textDocument/didClose notification
731
+    subroutine notify_file_closed(manager, server_index, filename)
732
+        use lsp_protocol_module, only: create_did_close_notification
733
+        type(lsp_manager_t), intent(inout) :: manager
734
+        integer, intent(in) :: server_index
735
+        character(len=*), intent(in) :: filename
736
+        type(lsp_message_t) :: msg
737
+
738
+        if (server_index < 1 .or. server_index > manager%num_servers) return
739
+        if (.not. manager%servers(server_index)%initialized) return
740
+
741
+        msg = create_did_close_notification(filename)
742
+        call send_notification(manager%servers(server_index), msg)
743
+    end subroutine notify_file_closed
744
+
625
 end module lsp_server_manager_module
745
 end module lsp_server_manager_module
tests/lsp/README.mdadded
@@ -0,0 +1,64 @@
1
+# LSP Tests
2
+
3
+This directory contains tests for the Language Server Protocol (LSP) integration in the fac editor.
4
+
5
+## Test Files
6
+
7
+- `test_json.f90` - Tests the JSON parser implementation
8
+- `test_lsp_init.f90` - Tests LSP server initialization and communication
9
+- `sample.c` - Sample C file for testing LSP with clangd
10
+- `sample.py` - Sample Python file for testing LSP with pylsp
11
+- `sample.rs` - Sample Rust file for testing LSP with rust-analyzer
12
+
13
+## Running Tests
14
+
15
+### Test JSON Parser
16
+```bash
17
+make test-lsp
18
+```
19
+
20
+### Test LSP in Editor
21
+```bash
22
+make test-lsp-editor
23
+```
24
+
25
+### Manual Testing
26
+Open a supported file type in the editor:
27
+```bash
28
+./fac tests/lsp/sample.c   # Tests C/C++ with clangd
29
+./fac tests/lsp/sample.py  # Tests Python with pylsp
30
+./fac tests/lsp/sample.rs  # Tests Rust with rust-analyzer
31
+```
32
+
33
+## Supported Languages
34
+
35
+The editor currently supports LSP for:
36
+- C/C++ (clangd)
37
+- Python (pylsp)
38
+- Rust (rust-analyzer)
39
+- Go (gopls)
40
+- TypeScript/JavaScript (typescript-language-server)
41
+- Fortran (fortls)
42
+
43
+## Requirements
44
+
45
+For LSP to work, you need the corresponding language servers installed:
46
+- C/C++: `brew install llvm` (provides clangd)
47
+- Python: `pip install python-lsp-server`
48
+- Rust: `rustup component add rust-analyzer`
49
+- Go: `go install golang.org/x/tools/gopls@latest`
50
+- TypeScript: `npm install -g typescript typescript-language-server`
51
+- Fortran: `pip install fortls`
52
+
53
+## Current Features
54
+- ✓ Automatic server startup when opening supported files
55
+- ✓ textDocument/didOpen notifications
56
+- ✓ Server initialization handshake
57
+
58
+## Planned Features
59
+- [ ] Code completion (Ctrl+Space)
60
+- [ ] Hover information (Ctrl+H)
61
+- [ ] Go to definition (Ctrl+D)
62
+- [ ] Find references (Ctrl+R)
63
+- [ ] Diagnostics display
64
+- [ ] Code actions and fixes
tests/lsp/sample.cadded
@@ -0,0 +1,13 @@
1
+#include <stdio.h>
2
+
3
+// Test file for LSP integration
4
+int main() {
5
+    printf("Hello from LSP test!\n");
6
+
7
+    int x = 10;
8
+    int y = 20;
9
+    int result = x + y;
10
+
11
+    printf("Result: %d\n", result);
12
+    return 0;
13
+}
tests/lsp/test_json.f90added
@@ -0,0 +1,34 @@
1
+program test_json
2
+    use json_module
3
+    use iso_fortran_env, only: real64
4
+    implicit none
5
+
6
+    type(json_value_t) :: json_obj
7
+    character(len=100) :: json_str
8
+    real(real64) :: id_value
9
+
10
+    ! Test with a simple JSON response like what we get from clangd
11
+    json_str = '{"id":1,"jsonrpc":"2.0","result":{"capabilities":{}}}'
12
+
13
+    print *, "Testing JSON: ", trim(json_str)
14
+
15
+    ! Parse it
16
+    json_obj = json_parse(json_str)
17
+
18
+    ! Check if we can find the id
19
+    if (json_has_key(json_obj, "id")) then
20
+        print *, "Found id key!"
21
+        id_value = json_get_number(json_obj, "id", -1.0_real64)
22
+        print *, "ID value: ", id_value
23
+    else
24
+        print *, "No id key found"
25
+    end if
26
+
27
+    ! Check for jsonrpc
28
+    if (json_has_key(json_obj, "jsonrpc")) then
29
+        print *, "Found jsonrpc key!"
30
+    else
31
+        print *, "No jsonrpc key found"
32
+    end if
33
+
34
+end program test_json
tests/lsp/test_lsp_init.f90added
@@ -0,0 +1,59 @@
1
+program test_lsp_init
2
+    use lsp_server_manager_module
3
+    use iso_fortran_env, only: output_unit, error_unit
4
+    implicit none
5
+
6
+    type(lsp_manager_t) :: manager
7
+    integer :: server_index
8
+    character(len=256) :: test_file
9
+    integer :: i
10
+
11
+    print *, "Testing LSP initialization with fixed JSON parser..."
12
+
13
+    ! Create test file
14
+    test_file = "/tmp/test.c"
15
+    open(10, file=test_file, status='replace')
16
+    write(10, '(a)') "#include <stdio.h>"
17
+    write(10, '(a)') "int main() { return 0; }"
18
+    close(10)
19
+
20
+    ! Initialize manager
21
+    call init_lsp_manager(manager)
22
+
23
+    ! Get or start server for C language
24
+    server_index = get_or_start_server(manager, "c", "/tmp")
25
+    if (server_index > 0) then
26
+        print *, "✓ Server started successfully"
27
+        print '(a,i0)', "   Server index: ", server_index
28
+    else
29
+        print *, "✗ Failed to start server"
30
+        stop 1
31
+    end if
32
+
33
+    ! Wait a bit for initialization
34
+    print *, "Waiting for initialization..."
35
+    call sleep(2)
36
+
37
+    ! Process any pending messages
38
+    call process_server_messages(manager)
39
+
40
+    ! Check if server is initialized
41
+    do i = 1, manager%num_servers
42
+        if (manager%servers(i)%initialized) then
43
+            print *, "✓ Server initialized successfully!"
44
+        else
45
+            print *, "✗ Server not yet initialized"
46
+        end if
47
+
48
+        ! Show server info
49
+        print '(a,a)', "   Command: ", trim(manager%servers(i)%command)
50
+        print '(a,i0)', "   Process ID: ", manager%servers(i)%process_id
51
+        print '(a,l1)', "   Initialized: ", manager%servers(i)%initialized
52
+        print '(a,l1)', "   Supports completion: ", manager%servers(i)%supports_completion
53
+    end do
54
+
55
+    ! Cleanup
56
+    call cleanup_lsp_manager(manager)
57
+    print *, "Test complete."
58
+
59
+end program test_lsp_init