fortrangoingonforty/facsimile / d3256e4

Browse files

lsp callback handlers ready for tests

Authored by espadonne
SHA
d3256e4a75ece88eb6045460247ebad7e20ebbcc
Parents
038f1e3
Tree
c05bea0

16 changed files

StatusFile+-
M docs/KEYBINDINGS.md 12 11
M docs/LSP_GUIDE.md 24 24
M src/commands/command_handler_module.f90 206 15
M src/lsp/lsp_server_manager_module.f90 19 16
M src/syntax/syntax_highlighter_module.f90 9 9
M src/terminal/input_handler_module.f90 130 32
M src/ui/help_display_module.f90 5 1
A test_keys.sh 25 0
A test_raw_keys.sh 32 0
A tests/scratch_files/README.md 268 0
A tests/scratch_files/calculator.py 68 0
A tests/scratch_files/main.c 79 0
A tests/scratch_files/main.py 71 0
A tests/scratch_files/math_utils.f90 89 0
A tests/scratch_files/test_program.f90 43 0
A tests/scratch_files/utils.c 76 0
docs/KEYBINDINGS.mdmodified
@@ -8,13 +8,14 @@ Complete keyboard shortcut reference for `fac` editor.
88
 
99
 | Keybinding | Command | Description |
1010
 |------------|---------|-------------|
11
-| `F12` | Go to Definition | Jump to where a symbol is defined |
12
-| `Shift+F12` | Find References | Find all usages of a symbol |
11
+| `F12` or `Ctrl+\` | Go to Definition | Jump to where a symbol is defined |
12
+| `Shift+F12` or `Ctrl+Shift+R` | Find References | Find all usages of a symbol |
1313
 | `F2` | Rename Symbol | Rename symbol across entire project |
1414
 | `Ctrl+.` | Code Actions | Quick fixes and refactorings |
15
-| `Ctrl+Shift+D` | Diagnostics Panel | Show all errors and warnings |
16
-| `Ctrl+Shift+O` | Document Symbols | Navigate symbols in current file |
17
-| `Ctrl+Shift+T` | Workspace Symbols | Search symbols across all files |
15
+| `F8` or `Ctrl+Shift+D` | Diagnostics Panel | Show all errors and warnings |
16
+| `F4` or `Ctrl+Shift+O` | Document Symbols | Navigate symbols in current file |
17
+| `F6` or `Ctrl+Shift+T` | Workspace Symbols | Search symbols across all files |
18
+| `Ctrl+P` | Command Palette | Search and execute any command |
1819
 | `Shift+Alt+F` | Format Document | Auto-format current file |
1920
 | `Alt+,` | Jump Back | Return to previous location (jump stack) |
2021
 
@@ -77,7 +78,7 @@ Complete keyboard shortcut reference for `fac` editor.
7778
 | `Ctrl+T` | New Tab | Create new empty tab |
7879
 | `Ctrl+W` | Close Tab | Close current tab |
7980
 | `Ctrl+Tab` | Next Tab | Switch to next tab |
80
-| `Ctrl+Shift+Tab` | Previous Tab | Switch to previous tab |
81
+| `F6ab` | Previous Tab | Switch to previous tab |
8182
 | `Alt+1` to `Alt+9` | Jump to Tab | Switch to specific tab number |
8283
 | `Ctrl+\` | Split Vertical | Split current pane vertically |
8384
 | `Ctrl+Shift+\` | Split Horizontal | Split current pane horizontally |
@@ -127,7 +128,7 @@ Complete keyboard shortcut reference for `fac` editor.
127128
 
128129
 | Keybinding | Command | Description |
129130
 |------------|---------|-------------|
130
-| `Ctrl+Shift+P` | Command Palette | Search and execute any command |
131
+| `Ctrl+P` | Command Palette | Search and execute any command |
131132
 | `Ctrl+?` or `F1` | Help | Show help screen |
132133
 | `Ctrl+Shift+F` | File Tree | Toggle file explorer (Fortress mode) |
133134
 | `Esc` | Cancel/Close | Close panels, cancel operations |
@@ -218,14 +219,14 @@ Active when `Ctrl+Shift+F` is pressed:
218219
 ### Keybinding Conflicts
219220
 - Some keybindings may conflict with terminal emulator shortcuts
220221
 - If a key doesn't work, check your terminal's keyboard settings
221
-- Common conflicts: `Ctrl+Shift+T` (new terminal tab), `Ctrl+W` (close terminal tab)
222
+- Common conflicts: `F6` (new terminal tab), `Ctrl+W` (close terminal tab)
222223
 
223224
 ### Customization
224225
 - Keybindings are currently hardcoded
225226
 - Future versions will support custom keybindings
226227
 
227228
 ### Discovering Commands
228
-- Use `Ctrl+Shift+P` (Command Palette) to see all available commands
229
+- Use `Ctrl+P` (Command Palette) to see all available commands
229230
 - Commands show their keybindings in the palette
230231
 
231232
 ### Vim Users
@@ -242,13 +243,13 @@ Some vim-style keybindings work:
242243
 ## 🚀 Most Useful Combos
243244
 
244245
 **Exploring code:**
245
-1. `Ctrl+Shift+T` - Find any symbol in project
246
+1. `F6` - Find any symbol in project
246247
 2. `F12` - Jump to definition
247248
 3. `Shift+F12` - See all usages
248249
 4. `Alt+,` - Jump back
249250
 
250251
 **Fixing errors:**
251
-1. `Ctrl+Shift+D` - See all errors
252
+1. `F8` - See all errors
252253
 2. Navigate to error
253254
 3. `Ctrl+.` - Apply quick fix
254255
 4. `Ctrl+S` - Save
docs/LSP_GUIDE.mdmodified
@@ -96,7 +96,7 @@ That's it! If you have `pylsp` installed, `fac` will automatically:
9696
 ### Step 3: Try It Out
9797
 
9898
 1. **See errors**: Look for `E` (error) or `W` (warning) in the left gutter
99
-2. **View all diagnostics**: Press `Ctrl+Shift+D` to open the diagnostics panel
99
+2. **View all diagnostics**: Press `F8` to open the diagnostics panel
100100
 3. **Jump to definition**: Put cursor on a function name and press `F12`
101101
 4. **Find references**: Press `Shift+F12` to see everywhere a symbol is used
102102
 5. **Rename**: Press `F2` to rename a variable across all files
@@ -114,7 +114,7 @@ That's it! If you have `pylsp` installed, `fac` will automatically:
114114
 - Errors appear with `E` in the gutter (red)
115115
 - Warnings appear with `W` in the gutter (yellow)
116116
 - Put your cursor on a line with an error to see the message in the status bar
117
-- Press `Ctrl+Shift+D` to open the **Diagnostics Panel** showing all issues
117
+- Press `F8` to open the **Diagnostics Panel** showing all issues
118118
 
119119
 **Example:**
120120
 ```python
@@ -125,7 +125,7 @@ def greet(name):
125125
 ```
126126
 
127127
 **Keybinding:**
128
-- `Ctrl+Shift+D` - Open/close diagnostics panel
128
+- `F8` or `Ctrl+Shift+D` - Open/close diagnostics panel
129129
 - `j`/`k` or `↑`/`↓` - Navigate issues in panel
130130
 - `Enter` - Jump to selected issue
131131
 - `Esc` - Close panel
@@ -155,7 +155,7 @@ def calculate_total(items): # ← You land here
155155
 ```
156156
 
157157
 **Keybindings:**
158
-- `F12` - Go to definition
158
+- `F12` or `Ctrl+\` - Go to definition
159159
 - `Alt+,` - Jump back (navigate backward in jump history)
160160
 
161161
 **Cross-file navigation:** If the definition is in another file, `fac` automatically opens it in a new tab!
@@ -186,7 +186,7 @@ References to 'calculate_total':
186186
 ```
187187
 
188188
 **Keybindings:**
189
-- `Shift+F12` - Find all references
189
+- `Shift+F12` or `Ctrl+Shift+R` - Find all references
190190
 - `j`/`k` or `↑`/`↓` - Navigate references
191191
 - `Enter` - Jump to selected reference
192192
 - `Esc` - Close panel
@@ -275,12 +275,12 @@ total = calculate(10, 20)
275275
 
276276
 ---
277277
 
278
-### 6. Document Symbols Outline (Ctrl+Shift+O)
278
+### 6. Document Symbols Outline (F4)
279279
 
280280
 **What it does:** See an outline of all functions, classes, and variables in the current file.
281281
 
282282
 **How to use:**
283
-1. Press `Ctrl+Shift+O` to open the symbols panel
283
+1. Press `F4` to open the symbols panel
284284
 2. Type to filter symbols (fuzzy search)
285285
 3. Press `Enter` to jump to a symbol
286286
 
@@ -297,7 +297,7 @@ Document Symbols:
297297
 ```
298298
 
299299
 **Keybindings:**
300
-- `Ctrl+Shift+O` - Open document symbols panel
300
+- `F4` or `Ctrl+Shift+O` - Open document symbols panel
301301
 - Type to search (fuzzy matching)
302302
 - `j`/`k` or `↑`/`↓` - Navigate symbols
303303
 - `Enter` - Jump to selected symbol
@@ -305,12 +305,12 @@ Document Symbols:
305305
 
306306
 ---
307307
 
308
-### 7. Workspace Symbols (Ctrl+Shift+T)
308
+### 7. Workspace Symbols (F6)
309309
 
310310
 **What it does:** Search for any symbol across your **entire project** (all files).
311311
 
312312
 **How to use:**
313
-1. Press `Ctrl+Shift+T` to open workspace symbols
313
+1. Press `F6` to open workspace symbols
314314
 2. Type part of a symbol name (fuzzy search)
315315
 3. Navigate and press `Enter` to jump to it
316316
 
@@ -331,7 +331,7 @@ Workspace Symbols:
331331
 - `calctot` matches `calculate_total` (consecutive)
332332
 
333333
 **Keybindings:**
334
-- `Ctrl+Shift+T` - Open workspace symbols
334
+- `F6` or `Ctrl+Shift+T` - Open workspace symbols
335335
 - Type to search across all files
336336
 - `j`/`k` or `↑`/`↓` - Navigate results
337337
 - `Enter` - Jump to symbol (opens file if needed)
@@ -414,12 +414,12 @@ def greet(name, age):
414414
 
415415
 ---
416416
 
417
-### 10. Command Palette (Ctrl+Shift+P)
417
+### 10. Command Palette (Ctrl+P)
418418
 
419419
 **What it does:** Quick access to all LSP commands without remembering keybindings.
420420
 
421421
 **How to use:**
422
-1. Press `Ctrl+Shift+P`
422
+1. Press `Ctrl+P`
423423
 2. Type to search commands (fuzzy search)
424424
 3. Press `Enter` to execute
425425
 
@@ -439,7 +439,7 @@ Command Palette:
439439
 ```
440440
 
441441
 **Keybindings:**
442
-- `Ctrl+Shift+P` - Open command palette
442
+- `Ctrl+P` - Open command palette
443443
 - Type to search commands
444444
 - `Enter` - Execute selected command
445445
 - `Esc` - Close palette
@@ -705,14 +705,14 @@ After using F12 to jump to a definition:
705705
 ### 2. Combine Search Features
706706
 
707707
 - `Ctrl+F` - Search text in current file
708
-- `Ctrl+Shift+O` - Search symbols in current file
709
-- `Ctrl+Shift+T` - Search symbols across project
708
+- `F4` - Search symbols in current file
709
+- `F6` - Search symbols across project
710710
 - `Shift+F12` - Find all usages of current symbol
711711
 
712712
 ### 3. Fix Errors Faster
713713
 
714714
 When you see an error:
715
-1. Press `Ctrl+Shift+D` to see all errors
715
+1. Press `F8` to see all errors
716716
 2. Navigate to each error
717717
 3. Press `Ctrl+.` to see quick fixes
718718
 4. Apply fixes with `Enter`
@@ -737,15 +737,15 @@ Many language servers support format-on-save:
737737
 
738738
 | Feature | Keybinding | What It Does |
739739
 |---------|-----------|--------------|
740
-| **Diagnostics Panel** | `Ctrl+Shift+D` | Show all errors/warnings |
741
-| **Go to Definition** | `F12` | Jump to where symbol is defined |
742
-| **Find References** | `Shift+F12` | Find all usages of symbol |
740
+| **Diagnostics Panel** | `F8` or `Ctrl+Shift+D` | Show all errors/warnings |
741
+| **Go to Definition** | `F12` or `Ctrl+\` | Jump to where symbol is defined |
742
+| **Find References** | `Shift+F12` or `Ctrl+Shift+R` | Find all usages of symbol |
743743
 | **Code Actions** | `Ctrl+.` | Quick fixes and refactorings |
744744
 | **Rename Symbol** | `F2` | Rename across entire project |
745
-| **Document Symbols** | `Ctrl+Shift+O` | Outline of current file |
746
-| **Workspace Symbols** | `Ctrl+Shift+T` | Search symbols across project |
745
+| **Document Symbols** | `F4` or `Ctrl+Shift+O` | Outline of current file |
746
+| **Workspace Symbols** | `F6` or `Ctrl+Shift+T` | Search symbols across project |
747747
 | **Format Document** | `Shift+Alt+F` | Auto-format code |
748
-| **Command Palette** | `Ctrl+Shift+P` | Access all commands |
748
+| **Command Palette** | `Ctrl+P` | Access all commands |
749749
 | **Jump Back** | `Alt+,` | Return to previous location |
750750
 
751751
 ---
@@ -779,7 +779,7 @@ Now that you understand LSP in `fac`:
779779
 
780780
 1. **Install language servers** for your languages
781781
 2. **Practice the keybindings** - they'll become second nature
782
-3. **Explore your codebase** with `Ctrl+Shift+T` and `F12`
782
+3. **Explore your codebase** with `F6` and `F12`
783783
 4. **Let LSP catch errors** before you run your code
784784
 5. **Refactor confidently** with `F2` and `Ctrl+.`
785785
 
src/commands/command_handler_module.f90modified
@@ -119,6 +119,14 @@ contains
119119
         line_count = buffer_get_line_count(buffer)
120120
         is_edit_action = .false.
121121
 
122
+        ! DEBUG: Log ALL keys to file only (not screen to avoid overwriting status messages)
123
+        block
124
+            integer :: debug_unit
125
+            open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
126
+            write(debug_unit, '(A)') '[KEY] "' // trim(key_str) // '"'
127
+            close(debug_unit)
128
+        end block
129
+
122130
         ! Ignore empty key strings (from terminal position reports, etc)
123131
         if (len_trim(key_str) == 0 .and. key_str(1:1) /= ' ') then
124132
             return
@@ -132,10 +140,16 @@ contains
132140
             match_case_sensitive = .true.  ! Reset to default
133141
         end if
134142
 
135
-        ! Route input when in fuss mode (except ctrl-b/ctrl-shift-b/F2/F3 and ctrl-q which work in both modes)
143
+        ! Route input when in fuss mode (except ctrl-b/ctrl-shift-b/F-keys/Alt-keys/ctrl-q which work in both modes)
136144
         if (editor%fuss_mode_active .and. trim(key_str) /= 'ctrl-b' .and. &
137145
             trim(key_str) /= 'ctrl-shift-b' .and. trim(key_str) /= 'f2' .and. &
138
-            trim(key_str) /= 'f3' .and. trim(key_str) /= 'ctrl-q') then
146
+            trim(key_str) /= 'f3' .and. trim(key_str) /= 'f4' .and. &
147
+            trim(key_str) /= 'f6' .and. trim(key_str) /= 'f8' .and. &
148
+            trim(key_str) /= 'f12' .and. trim(key_str) /= 'shift-f12' .and. &
149
+            trim(key_str) /= 'alt-g' .and. trim(key_str) /= 'alt-o' .and. &
150
+            trim(key_str) /= 'alt-p' .and. trim(key_str) /= 'alt-e' .and. &
151
+            trim(key_str) /= 'alt-r' .and. trim(key_str) /= 'ctrl-\\' .and. &
152
+            trim(key_str) /= 'ctrl-q') then
139153
             call handle_fuss_input(key_str, editor, buffer)
140154
             return
141155
         end if
@@ -1193,8 +1207,20 @@ contains
11931207
                 end if
11941208
             end if
11951209
 
1196
-        case('f12')
1197
-            ! Go to definition
1210
+        case('f12', 'ctrl-\\', 'alt-g')
1211
+            ! Go to definition (F12, Ctrl+\, or Alt+G)
1212
+            block
1213
+                integer :: debug_unit
1214
+                open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
1215
+                write(debug_unit, '(A)') '>>> INSIDE F12/ALT-G HANDLER <<<'
1216
+                write(debug_unit, '(A,I0)') 'active_tab_index = ', editor%active_tab_index
1217
+                write(debug_unit, '(A,I0)') 'size(tabs) = ', size(editor%tabs)
1218
+                if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
1219
+                    write(debug_unit, '(A,I0)') 'lsp_server_index = ', editor%tabs(editor%active_tab_index)%lsp_server_index
1220
+                end if
1221
+                close(debug_unit)
1222
+            end block
1223
+            call terminal_move_cursor(editor%screen_rows, 1)
11981224
             if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
11991225
                 if (editor%tabs(editor%active_tab_index)%lsp_server_index > 0) then
12001226
                     ! Save current location to jump stack
@@ -1211,22 +1237,40 @@ contains
12111237
                         lsp_line = editor%cursors(editor%active_cursor)%line - 1
12121238
                         lsp_char = editor%cursors(editor%active_cursor)%column - 1
12131239
 
1240
+                        ! Save editor state for callback
1241
+                        if (.not. allocated(saved_editor_for_callback)) then
1242
+                            allocate(saved_editor_for_callback)
1243
+                        end if
1244
+                        saved_editor_for_callback = editor
1245
+
12141246
                         request_id = request_definition(editor%lsp_manager, &
12151247
                             editor%tabs(editor%active_tab_index)%lsp_server_index, &
12161248
                             editor%tabs(editor%active_tab_index)%filename, &
1217
-                            lsp_line, lsp_char)
1249
+                            lsp_line, lsp_char, handle_definition_response_wrapper)
1250
+
1251
+                        block
1252
+                            integer :: debug_unit
1253
+                            open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
1254
+                            write(debug_unit, '(A,I0)') 'request_definition returned request_id = ', request_id
1255
+                            close(debug_unit)
1256
+                        end block
12181257
 
12191258
                         if (request_id > 0) then
12201259
                             ! Response will be handled by callback
1221
-                            call terminal_move_cursor(editor%screen_rows, 1)
12221260
                             call terminal_write('Searching for definition...                ')
1261
+                        else
1262
+                            call terminal_write('[F12] LSP request failed                    ')
12231263
                         end if
12241264
                     end block
1265
+                else
1266
+                    call terminal_write('[F12] No LSP server for this file           ')
12251267
                 end if
1268
+            else
1269
+                call terminal_write('[F12] No active tab                         ')
12261270
             end if
12271271
 
1228
-        case('shift-f12')
1229
-            ! Find all references
1272
+        case('shift-f12', 'alt-r')
1273
+            ! Find all references (Shift+F12 or Alt+R)
12301274
             if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
12311275
                 if (editor%tabs(editor%active_tab_index)%lsp_server_index > 0) then
12321276
                     ! Request references at current cursor position
@@ -1339,8 +1383,14 @@ contains
13391383
                 end if
13401384
             end if
13411385
 
1342
-        case('ctrl-shift-o')
1343
-            ! Document symbols outline
1386
+        case('f4', 'alt-o')
1387
+            ! Document symbols outline (F4 or Alt+O)
1388
+            block
1389
+                integer :: debug_unit
1390
+                open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
1391
+                write(debug_unit, '(A)') '>>> INSIDE F4/ALT-O HANDLER <<<'
1392
+                close(debug_unit)
1393
+            end block
13441394
             if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
13451395
                 if (editor%tabs(editor%active_tab_index)%lsp_server_index > 0) then
13461396
                     ! Request document symbols
@@ -1369,8 +1419,9 @@ contains
13691419
                 end if
13701420
             end if
13711421
 
1372
-        case('ctrl-shift-p')
1373
-            ! Command palette
1422
+        case('ctrl-p')
1423
+            ! Command palette (Ctrl+P - VSCode standard)
1424
+            ! Note: ctrl-shift-p doesn't work - terminals can't distinguish ctrl-p from ctrl-shift-p
13741425
             block
13751426
                 use command_palette_module, only: show_command_palette_interactive
13761427
                 character(len=:), allocatable :: cmd_id
@@ -1386,7 +1437,8 @@ contains
13861437
                 call render_screen(buffer, editor)
13871438
             end block
13881439
 
1389
-        case('ctrl-shift-t')
1440
+        case('f6', 'alt-p')
1441
+            ! Workspace symbols (F6 or Alt+P for project)
13901442
             ! Workspace symbols (fuzzy search across project)
13911443
             if (size(editor%tabs) > 0 .and. editor%active_tab_index > 0) then
13921444
                 block
@@ -1525,9 +1577,22 @@ contains
15251577
             call sync_editor_to_pane(editor)
15261578
             call update_viewport(editor)
15271579
 
1528
-        case('ctrl-shift-d')
1529
-            ! Toggle diagnostics panel
1580
+        case('f8', 'alt-e')
1581
+            ! Toggle diagnostics panel (F8 or Alt+E for errors)
1582
+            block
1583
+                integer :: debug_unit
1584
+                open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
1585
+                write(debug_unit, '(A)') '>>> INSIDE F8/ALT-E HANDLER <<<'
1586
+                write(debug_unit, '(A)') 'Calling toggle_diagnostics_panel...'
1587
+                close(debug_unit)
1588
+            end block
15301589
             call toggle_diagnostics_panel(editor)
1590
+            block
1591
+                integer :: debug_unit
1592
+                open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
1593
+                write(debug_unit, '(A)') 'toggle_diagnostics_panel returned'
1594
+                close(debug_unit)
1595
+            end block
15311596
 
15321597
         case('alt-c')
15331598
             ! Toggle case sensitivity for match mode (ctrl-d)
@@ -1604,6 +1669,12 @@ contains
16041669
             end if
16051670
 
16061671
         case default
1672
+            ! DEBUG: Show unhandled function keys
1673
+            if (index(key_str, 'f') == 1 .or. index(key_str, 'shift-f') == 1) then
1674
+                call terminal_move_cursor(editor%screen_rows, 1)
1675
+                call terminal_write('[DEBUG] Unhandled key: ' // trim(key_str) // '                ')
1676
+            end if
1677
+
16071678
             ! Check for mouse events
16081679
             if (index(key_str, 'mouse-') == 1) then
16091680
                 call handle_mouse_event_action(key_str, editor, buffer)
@@ -6395,4 +6466,124 @@ contains
63956466
         end block
63966467
     end subroutine navigate_to_workspace_symbol
63976468
 
6469
+    ! ==================================================
6470
+    ! LSP Definition Response Handler
6471
+    ! ==================================================
6472
+
6473
+    ! Wrapper callback for go to definition
6474
+    subroutine handle_definition_response_wrapper(request_id, response)
6475
+        use lsp_protocol_module, only: lsp_message_t
6476
+        integer, intent(in) :: request_id
6477
+        type(lsp_message_t), intent(in) :: response
6478
+
6479
+        ! Call actual handler with saved editor state
6480
+        if (allocated(saved_editor_for_callback)) then
6481
+            call handle_definition_response_impl(saved_editor_for_callback, response)
6482
+        end if
6483
+    end subroutine handle_definition_response_wrapper
6484
+
6485
+    ! Handle LSP textDocument/definition response
6486
+    subroutine handle_definition_response_impl(editor, response)
6487
+        use lsp_protocol_module, only: lsp_message_t
6488
+        use json_module, only: json_value_t, json_get_object, json_get_string, &
6489
+                               json_get_number, json_array_size, json_get_array_element, &
6490
+                               json_has_key
6491
+        type(editor_state_t), intent(inout) :: editor
6492
+        type(lsp_message_t), intent(in) :: response
6493
+        type(json_value_t) :: location_obj, range_obj, start_obj
6494
+        character(len=:), allocatable :: uri, filepath
6495
+        real(8) :: line_real, col_real
6496
+        integer :: target_line, target_col, i, num_locations
6497
+        logical :: found_file
6498
+
6499
+        ! Log response for debugging
6500
+        block
6501
+            integer :: debug_unit
6502
+            open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
6503
+            write(debug_unit, '(A)') '>>> DEFINITION RESPONSE RECEIVED <<<'
6504
+            close(debug_unit)
6505
+        end block
6506
+
6507
+        ! Try to treat result as array first
6508
+        num_locations = json_array_size(response%result)
6509
+
6510
+        if (num_locations > 0) then
6511
+            ! Array of locations - take first one
6512
+            location_obj = json_get_array_element(response%result, 0)
6513
+        else if (json_has_key(response%result, "uri")) then
6514
+            ! Single location object
6515
+            location_obj = response%result
6516
+        else
6517
+            ! No definition found
6518
+            call terminal_move_cursor(editor%screen_rows, 1)
6519
+            call terminal_write('No definition found                           ')
6520
+            return
6521
+        end if
6522
+
6523
+        ! Extract URI
6524
+        uri = json_get_string(location_obj, 'uri', '')
6525
+        if (len(uri) == 0) then
6526
+            call terminal_move_cursor(editor%screen_rows, 1)
6527
+            call terminal_write('Invalid definition response                   ')
6528
+            return
6529
+        end if
6530
+
6531
+        ! Convert URI to filepath (remove file:// prefix)
6532
+        if (len(uri) > 7 .and. uri(1:7) == 'file://') then
6533
+            filepath = uri(8:)
6534
+        else
6535
+            filepath = uri
6536
+        end if
6537
+
6538
+        ! Get range
6539
+        range_obj = json_get_object(location_obj, 'range')
6540
+        start_obj = json_get_object(range_obj, 'start')
6541
+
6542
+        line_real = json_get_number(start_obj, 'line', 0.0d0)
6543
+        col_real = json_get_number(start_obj, 'character', 0.0d0)
6544
+
6545
+        ! Convert from 0-based LSP to 1-based editor coordinates
6546
+        target_line = int(line_real) + 1
6547
+        target_col = int(col_real) + 1
6548
+
6549
+        ! Log details
6550
+        block
6551
+            integer :: debug_unit
6552
+            open(newunit=debug_unit, file='/tmp/fac_keys.log', position='append', action='write')
6553
+            write(debug_unit, '(A)') 'File: ' // trim(filepath)
6554
+            write(debug_unit, '(A,I0,A,I0)') 'Position: line=', target_line, ', col=', target_col
6555
+            close(debug_unit)
6556
+        end block
6557
+
6558
+        ! Check if the file is already open in a tab
6559
+        found_file = .false.
6560
+        do i = 1, size(editor%tabs)
6561
+            if (allocated(editor%tabs(i)%filename)) then
6562
+                if (trim(editor%tabs(i)%filename) == trim(filepath)) then
6563
+                    ! Switch to this tab
6564
+                    editor%active_tab_index = i
6565
+                    found_file = .true.
6566
+                    exit
6567
+                end if
6568
+            end if
6569
+        end do
6570
+
6571
+        ! If file not found in tabs, report location
6572
+        if (.not. found_file) then
6573
+            call terminal_move_cursor(editor%screen_rows, 1)
6574
+            call terminal_write('Found in: ' // trim(filepath) // '                ')
6575
+            return
6576
+        end if
6577
+
6578
+        ! Jump to the line and column in current tab
6579
+        editor%cursors(editor%active_cursor)%line = target_line
6580
+        editor%cursors(editor%active_cursor)%column = target_col
6581
+
6582
+        ! Center viewport on target
6583
+        editor%viewport_line = max(1, target_line - editor%screen_rows / 2)
6584
+
6585
+        call terminal_move_cursor(editor%screen_rows, 1)
6586
+        call terminal_write('Jumped to definition                          ')
6587
+    end subroutine handle_definition_response_impl
6588
+
63986589
 end module command_handler_module
src/lsp/lsp_server_manager_module.f90modified
@@ -325,21 +325,24 @@ contains
325325
         server%process_id = -1
326326
     end subroutine stop_server
327327
 
328
-    subroutine send_request(server, msg, callback)
329
-        type(lsp_server_t), intent(inout) :: server
328
+    subroutine send_request(manager, server_index, msg, callback)
329
+        type(lsp_manager_t), intent(inout) :: manager
330
+        integer, intent(in) :: server_index
330331
         type(lsp_message_t), intent(in) :: msg
331332
         procedure(response_callback), optional :: callback
332333
         character(len=:), allocatable :: json_msg
333334
 
334
-        if (.not. server%initialized .and. .not. server%initializing) return
335
+        if (server_index < 1 .or. server_index > manager%num_servers) return
336
+        if (.not. manager%servers(server_index)%initialized .and. &
337
+            .not. manager%servers(server_index)%initializing) return
335338
 
336339
         json_msg = format_json_rpc(msg)
337
-        call send_raw_message(server, json_msg)
340
+        call send_raw_message(manager%servers(server_index), json_msg)
338341
 
339342
         ! Track request and register callback
340
-        call track_request(server, msg%id)
343
+        call track_request(manager%servers(server_index), msg%id)
341344
         if (present(callback)) then
342
-            ! TODO: Register callback in manager
345
+            call register_callback(manager, msg%id, callback)
343346
         end if
344347
     end subroutine send_request
345348
 
@@ -825,7 +828,7 @@ contains
825828
         msg = create_completion_request(trim(uri), line, character)
826829
         request_id = msg%id
827830
 
828
-        call send_request(manager%servers(server_index), msg, callback)
831
+        call send_request(manager, server_index, msg, callback)
829832
     end function request_completion
830833
 
831834
     ! Request hover information at cursor position
@@ -850,7 +853,7 @@ contains
850853
         msg = create_hover_request(trim(uri), line, character)
851854
         request_id = msg%id
852855
 
853
-        call send_request(manager%servers(server_index), msg, callback)
856
+        call send_request(manager, server_index, msg, callback)
854857
     end function request_hover
855858
 
856859
     ! Request definition location at cursor position
@@ -875,7 +878,7 @@ contains
875878
         msg = create_definition_request(uri, line, character)
876879
         request_id = msg%id
877880
 
878
-        call send_request(manager%servers(server_index), msg, callback)
881
+        call send_request(manager, server_index, msg, callback)
879882
     end function request_definition
880883
 
881884
     ! Request references at cursor position
@@ -901,7 +904,7 @@ contains
901904
         msg = create_references_request(uri, line, character, .true.)
902905
         request_id = msg%id
903906
 
904
-        call send_request(manager%servers(server_index), msg, callback)
907
+        call send_request(manager, server_index, msg, callback)
905908
     end function request_references
906909
 
907910
     ! Request code actions for a range
@@ -929,7 +932,7 @@ contains
929932
         msg = create_code_action_request(uri, start_line, start_char, end_line, end_char)
930933
         request_id = msg%id
931934
 
932
-        call send_request(manager%servers(server_index), msg, callback)
935
+        call send_request(manager, server_index, msg, callback)
933936
     end function request_code_actions
934937
 
935938
     ! Request document symbols
@@ -954,7 +957,7 @@ contains
954957
         msg = create_document_symbols_request(uri)
955958
         request_id = msg%id
956959
 
957
-        call send_request(manager%servers(server_index), msg, callback)
960
+        call send_request(manager, server_index, msg, callback)
958961
     end function request_document_symbols
959962
 
960963
     ! Request signature help
@@ -980,7 +983,7 @@ contains
980983
         msg = create_signature_help_request(uri, line, character)
981984
         request_id = msg%id
982985
 
983
-        call send_request(manager%servers(server_index), msg, callback)
986
+        call send_request(manager, server_index, msg, callback)
984987
     end function request_signature_help
985988
 
986989
     ! Request formatting
@@ -1007,7 +1010,7 @@ contains
10071010
         msg = create_formatting_request(uri, tab_size, insert_spaces)
10081011
         request_id = msg%id
10091012
 
1010
-        call send_request(manager%servers(server_index), msg, callback)
1013
+        call send_request(manager, server_index, msg, callback)
10111014
     end function request_formatting
10121015
 
10131016
     ! Request rename
@@ -1034,7 +1037,7 @@ contains
10341037
         msg = create_rename_request(uri, line, character, new_name)
10351038
         request_id = msg%id
10361039
 
1037
-        call send_request(manager%servers(server_index), msg, callback)
1040
+        call send_request(manager, server_index, msg, callback)
10381041
     end function request_rename
10391042
 
10401043
     ! Request workspace symbols
@@ -1056,7 +1059,7 @@ contains
10561059
         msg = create_workspace_symbols_request(query)
10571060
         request_id = msg%id
10581061
 
1059
-        call send_request(manager%servers(server_index), msg, callback)
1062
+        call send_request(manager, server_index, msg, callback)
10601063
     end function request_workspace_symbols
10611064
 
10621065
     ! Set the diagnostics notification handler
src/syntax/syntax_highlighter_module.f90modified
@@ -54,15 +54,15 @@ module syntax_highlighter_module
5454
     end type syntax_highlighter_t
5555
 
5656
     ! 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'
57
+    character(len=*), parameter :: COLOR_KEYWORD = char(27) // '[1;34m'     ! Bold Blue
58
+    character(len=*), parameter :: COLOR_STRING = char(27) // '[32m'        ! Green
59
+    character(len=*), parameter :: COLOR_NUMBER = char(27) // '[35m'        ! Magenta
60
+    character(len=*), parameter :: COLOR_COMMENT = char(27) // '[90m'       ! Gray
61
+    character(len=*), parameter :: COLOR_OPERATOR = char(27) // '[33m'      ! Yellow
62
+    character(len=*), parameter :: COLOR_TYPE = char(27) // '[36m'          ! Cyan
63
+    character(len=*), parameter :: COLOR_FUNCTION = char(27) // '[1;36m'    ! Bold Cyan
64
+    character(len=*), parameter :: COLOR_PREPROC = char(27) // '[95m'       ! Light Magenta
65
+    character(len=*), parameter :: COLOR_RESET = char(27) // '[0m'
6666
 
6767
 contains
6868
 
src/terminal/input_handler_module.f90modified
@@ -194,27 +194,34 @@ contains
194194
                         end if
195195
                     else if (ch3 == '1' .or. ch3 == '2' .or. ch3 == '3' .or. ch3 == '4' .or. &
196196
                              ch3 == '5' .or. ch3 == '7' .or. ch3 == '8' .or. ch3 == '9') then
197
-                        ! Function keys F1-F9: ESC [ 1 X ~
197
+                        ! Function keys F1-F8: ESC [ 1 X ~ or ESC [ 1 X ; modifier ~
198198
                         char_code = terminal_read_char()
199
-                        if (char_code >= 0 .and. achar(char_code) == '~') then
200
-                            select case(ch3)
201
-                            case('1')
202
-                                key_str = 'f1'
203
-                            case('2')
204
-                                key_str = 'f2'
205
-                            case('3')
206
-                                key_str = 'f3'
207
-                            case('4')
208
-                                key_str = 'f4'
209
-                            case('5')
210
-                                key_str = 'f5'
211
-                            case('7')
212
-                                key_str = 'f6'
213
-                            case('8')
214
-                                key_str = 'f7'
215
-                            case('9')
216
-                                key_str = 'f8'
217
-                            end select
199
+                        if (char_code >= 0) then
200
+                            ch = achar(char_code)
201
+                            if (ch == '~') then
202
+                                ! Unmodified F1-F8
203
+                                select case(ch3)
204
+                                case('1')
205
+                                    key_str = 'f1'
206
+                                case('2')
207
+                                    key_str = 'f2'
208
+                                case('3')
209
+                                    key_str = 'f3'
210
+                                case('4')
211
+                                    key_str = 'f4'
212
+                                case('5')
213
+                                    key_str = 'f5'
214
+                                case('7')
215
+                                    key_str = 'f6'
216
+                                case('8')
217
+                                    key_str = 'f7'
218
+                                case('9')
219
+                                    key_str = 'f8'
220
+                                end select
221
+                            else if (ch == ';') then
222
+                                ! Modified F1-F8: ESC [ 1 X ; modifier ~
223
+                                call handle_modified_function_key(key_str, '1', ch3)
224
+                            end if
218225
                         end if
219226
                     else if (ch3 == ';') then
220227
                         ! Modified arrow key or home/end: ESC [ 1 ; 2 A format
@@ -227,19 +234,26 @@ contains
227234
                 if (char_code >= 0) then
228235
                     ch3 = achar(char_code)
229236
                     if (ch3 == '0' .or. ch3 == '1' .or. ch3 == '3' .or. ch3 == '4') then
230
-                        ! Function keys F9-F12: ESC [ 2 X ~
237
+                        ! Function keys F9-F12: ESC [ 2 X ~ or ESC [ 2 X ; modifier ~
231238
                         char_code = terminal_read_char()
232
-                        if (char_code >= 0 .and. achar(char_code) == '~') then
233
-                            select case(ch3)
234
-                            case('0')
235
-                                key_str = 'f9'
236
-                            case('1')
237
-                                key_str = 'f10'
238
-                            case('3')
239
-                                key_str = 'f11'
240
-                            case('4')
241
-                                key_str = 'f12'
242
-                            end select
239
+                        if (char_code >= 0) then
240
+                            ch = achar(char_code)
241
+                            if (ch == '~') then
242
+                                ! Unmodified F9-F12
243
+                                select case(ch3)
244
+                                case('0')
245
+                                    key_str = 'f9'
246
+                                case('1')
247
+                                    key_str = 'f10'
248
+                                case('3')
249
+                                    key_str = 'f11'
250
+                                case('4')
251
+                                    key_str = 'f12'
252
+                                end select
253
+                            else if (ch == ';') then
254
+                                ! Modified F9-F12: ESC [ 2 X ; modifier ~
255
+                                call handle_modified_function_key(key_str, '2', ch3)
256
+                            end if
243257
                         end if
244258
                     else if (ch3 == ';') then
245259
                         ! ESC [ 2 ; A format (shift+arrow)
@@ -662,6 +676,90 @@ contains
662676
         end if
663677
     end subroutine handle_modified_special_key
664678
 
679
+    subroutine handle_modified_function_key(key_str, series, fkey_code)
680
+        character(len=*), intent(out) :: key_str
681
+        character, intent(in) :: series      ! '1' for ESC[1X~, '2' for ESC[2X~
682
+        character, intent(in) :: fkey_code   ! The X in ESC[1X~ or ESC[2X~
683
+        character :: ch, modifier_ch
684
+        integer :: char_code, modifier
685
+        character(len=10) :: base_key
686
+
687
+        key_str = ''
688
+
689
+        ! Determine base function key from series and code
690
+        if (series == '1') then
691
+            ! ESC[1X~ format: 1=F1, 2=F2, 3=F3, 4=F4, 5=F5, 7=F6, 8=F7, 9=F8
692
+            select case(fkey_code)
693
+            case('1')
694
+                base_key = 'f1'
695
+            case('2')
696
+                base_key = 'f2'
697
+            case('3')
698
+                base_key = 'f3'
699
+            case('4')
700
+                base_key = 'f4'
701
+            case('5')
702
+                base_key = 'f5'
703
+            case('7')
704
+                base_key = 'f6'
705
+            case('8')
706
+                base_key = 'f7'
707
+            case('9')
708
+                base_key = 'f8'
709
+            case default
710
+                return
711
+            end select
712
+        else if (series == '2') then
713
+            ! ESC[2X~ format: 0=F9, 1=F10, 3=F11, 4=F12
714
+            select case(fkey_code)
715
+            case('0')
716
+                base_key = 'f9'
717
+            case('1')
718
+                base_key = 'f10'
719
+            case('3')
720
+                base_key = 'f11'
721
+            case('4')
722
+                base_key = 'f12'
723
+            case default
724
+                return
725
+            end select
726
+        else
727
+            return
728
+        end if
729
+
730
+        ! Read modifier (should be a digit 2-8)
731
+        char_code = terminal_read_char()
732
+        if (char_code < 0) return
733
+        modifier_ch = achar(char_code)
734
+
735
+        ! Read terminating ~
736
+        char_code = terminal_read_char()
737
+        if (char_code < 0 .or. achar(char_code) /= '~') return
738
+
739
+        ! Parse modifier: 2=Shift, 3=Alt, 4=Alt+Shift, 5=Ctrl, 6=Ctrl+Shift, 7=Alt+Ctrl, 8=Alt+Shift
740
+        read(modifier_ch, '(i1)') modifier
741
+
742
+        select case(modifier)
743
+        case(2)  ! Shift
744
+            key_str = 'shift-' // trim(base_key)
745
+        case(3)  ! Alt
746
+            key_str = 'alt-' // trim(base_key)
747
+        case(4)  ! Alt+Shift
748
+            key_str = 'alt-shift-' // trim(base_key)
749
+        case(5)  ! Ctrl
750
+            key_str = 'ctrl-' // trim(base_key)
751
+        case(6)  ! Ctrl+Shift
752
+            key_str = 'ctrl-shift-' // trim(base_key)
753
+        case(7)  ! Alt+Ctrl
754
+            key_str = 'alt-ctrl-' // trim(base_key)
755
+        case(8)  ! Alt+Ctrl+Shift
756
+            key_str = 'alt-ctrl-shift-' // trim(base_key)
757
+        case default
758
+            ! Unknown modifier, return unmodified key
759
+            key_str = trim(base_key)
760
+        end select
761
+    end subroutine handle_modified_function_key
762
+
665763
     subroutine handle_mouse_event(key_str)
666764
         character(len=*), intent(out) :: key_str
667765
         character :: ch
src/ui/help_display_module.f90modified
@@ -248,7 +248,11 @@ contains
248248
         lines(i) = "  F12                 go to definition"; i = i + 1
249249
         lines(i) = "  shift-F12           find all references"; i = i + 1
250250
         lines(i) = "  alt-, (alt-comma)   jump back (navigation history)"; i = i + 1
251
-        lines(i) = "  ctrl-shift-d        toggle diagnostics panel"; i = i + 1
251
+        lines(i) = "  F2                  rename symbol"; i = i + 1
252
+        lines(i) = "  F4                  document symbols (outline)"; i = i + 1
253
+        lines(i) = "  F6                  workspace symbols (search project)"; i = i + 1
254
+        lines(i) = "  F8                  toggle diagnostics panel (errors)"; i = i + 1
255
+        lines(i) = "  ctrl-p              command palette"; i = i + 1
252256
         lines(i) = ""; i = i + 1
253257
 
254258
         n_lines = i - 1
test_keys.shadded
@@ -0,0 +1,25 @@
1
+#!/bin/bash
2
+# Test what escape sequences the terminal sends for function keys
3
+
4
+echo "Press keys to see their escape sequences (Ctrl+C to quit):"
5
+echo "Try: F12, Shift-F12, F8, F4, F6, F2"
6
+echo ""
7
+
8
+# Read raw input and show escape sequences
9
+while true; do
10
+    read -rsn1 char
11
+    if [[ $char == $'\e' ]]; then
12
+        # Start of escape sequence
13
+        seq="$char"
14
+        # Read the rest quickly
15
+        while read -rsn1 -t 0.001 char; do
16
+            seq="$seq$char"
17
+        done
18
+        # Show it in a readable format
19
+        echo -n "Escape sequence: "
20
+        echo -n "$seq" | od -An -tx1 | tr -d ' \n'
21
+        echo " (raw: $(echo -n "$seq" | cat -v))"
22
+    else
23
+        echo "Character: '$char' (ASCII: $(printf '%d' "'$char"))"
24
+    fi
25
+done
test_raw_keys.shadded
@@ -0,0 +1,32 @@
1
+#!/bin/bash
2
+# Test what escape sequences WezTerm actually sends for F-keys
3
+# Press F8, F12, Shift-F12, etc. and see what comes through
4
+
5
+echo "=== Raw Key Sequence Tester ==="
6
+echo "Press F8, F12, or Shift-F12, then Ctrl+C to exit"
7
+echo "This will show the EXACT escape sequences received"
8
+echo ""
9
+
10
+# Use od to show the raw bytes of each key press
11
+while true; do
12
+    echo -n "Press a key: "
13
+    # Read one key sequence with timeout
14
+    IFS= read -rsn1 char
15
+
16
+    # If it's ESC, read the rest of the sequence
17
+    if [ "$char" = $'\x1b' ]; then
18
+        # Read the next character(s) quickly
19
+        rest=""
20
+        while IFS= read -rsn1 -t 0.01 c; do
21
+            rest="$rest$c"
22
+        done
23
+        full="$char$rest"
24
+
25
+        # Show as hex and as escaped string
26
+        echo -n "$full" | od -An -tx1 | tr -d ' \n'
27
+        echo -n " = ESC"
28
+        echo "$rest" | sed 's/./[&]/g'
29
+    else
30
+        echo "$char"
31
+    fi
32
+done
tests/scratch_files/README.mdadded
@@ -0,0 +1,268 @@
1
+# LSP Feature Test Files
2
+
3
+These files are designed to test all LSP features in `fac`. Each file contains intentional errors and cross-references for comprehensive testing.
4
+
5
+## Files Overview
6
+
7
+### Python Files
8
+- **calculator.py** - Math functions with intentional errors
9
+  - Undefined variable errors (`reslt` instead of `result`)
10
+  - Missing import errors (`math` module)
11
+  - Multiple functions for testing navigation
12
+
13
+- **main.py** - Main program importing calculator
14
+  - Cross-file references for testing F12
15
+  - Class structure for Document Symbols
16
+  - Missing import error (`divide` function)
17
+
18
+### Fortran Files
19
+- **math_utils.f90** - Math utilities module
20
+  - Vector operations (add, dot product, magnitude)
21
+  - Matrix multiplication
22
+  - Intentional errors (uninitialized variables)
23
+
24
+- **test_program.f90** - Program using math_utils
25
+  - Cross-file references to test F12
26
+  - Undefined variable error
27
+
28
+### C Files
29
+- **utils.c** - Utility functions
30
+  - Factorial, fibonacci, array operations
31
+  - Intentional errors (undefined variables, type mismatches)
32
+
33
+- **main.c** - Main program using utils
34
+  - Cross-file function calls
35
+  - Structure definition
36
+  - Multiple intentional errors
37
+
38
+## Testing LSP Features
39
+
40
+### 1. Diagnostics (F8)
41
+
42
+**Test:** Open any file and check for error markers in the gutter.
43
+
44
+Expected errors:
45
+- **calculator.py**: `reslt` undefined, `math` not imported
46
+- **main.py**: `divide` not imported
47
+- **math_utils.f90**: `undefined_var` in `broken_routine`
48
+- **test_program.f90**: `undefined_result` not declared
49
+- **utils.c**: `undefined_var`, type mismatch, missing return
50
+- **main.c**: `undefined_function`, type mismatch, `undeclared_var`
51
+
52
+**How to test:**
53
+```bash
54
+fac calculator.py
55
+# Press F8 to open diagnostics panel
56
+# Navigate with j/k or ↑/↓
57
+# Press Enter to jump to error
58
+```
59
+
60
+### 2. Go to Definition (F12)
61
+
62
+**Test:** Cross-file navigation between importing files.
63
+
64
+**In main.py:**
65
+1. Put cursor on `add` on line ~15
66
+2. Press `F12`
67
+3. Should jump to `def add()` in calculator.py
68
+
69
+**In test_program.f90:**
70
+1. Put cursor on `vector_add` on line ~23
71
+2. Press `F12`
72
+3. Should jump to `subroutine vector_add` in math_utils.f90
73
+
74
+**In main.c:**
75
+1. Put cursor on `factorial` on line ~28
76
+2. Press `F12`
77
+3. Should jump to `int factorial()` in utils.c
78
+
79
+**Return:** Press `Alt+,` to jump back!
80
+
81
+### 3. Find References (Shift+F12)
82
+
83
+**Test:** Find all usages of a function.
84
+
85
+**In calculator.py:**
86
+1. Put cursor on `calculate_total` function name
87
+2. Press `Shift+F12`
88
+3. Should show:
89
+   - Definition in calculator.py
90
+   - Usage in main.py (imported and called)
91
+
92
+**In math_utils.f90:**
93
+1. Put cursor on `vector_dot`
94
+2. Press `Shift+F12`
95
+3. Should show usage in test_program.f90
96
+
97
+### 4. Code Actions (Ctrl+.)
98
+
99
+**Test:** Quick fixes for errors.
100
+
101
+**In calculator.py line ~48:**
102
+1. Put cursor on `reslt` (undefined error)
103
+2. Press `Ctrl+.`
104
+3. Should offer to fix the typo or define `reslt`
105
+
106
+**In main.py line ~45:**
107
+1. Put cursor on `divide` (not imported error)
108
+2. Press `Ctrl+.`
109
+3. Should offer to add `divide` to imports
110
+
111
+### 5. Rename Symbol (F2)
112
+
113
+**Test:** Rename across files.
114
+
115
+**In calculator.py:**
116
+1. Put cursor on `add` function name
117
+2. Press `F2`
118
+3. Type new name (e.g., `add_numbers`)
119
+4. Press Enter
120
+5. Check:
121
+   - Function renamed in calculator.py
122
+   - Import statement updated in main.py
123
+   - Function call updated in main.py
124
+
125
+**Undo:** Press `Ctrl+Z` repeatedly to revert
126
+
127
+### 6. Document Symbols (F4)
128
+
129
+**Test:** See file outline.
130
+
131
+**In calculator.py:**
132
+1. Press `F4`
133
+2. Should see:
134
+   - `add` function
135
+   - `subtract` function
136
+   - `multiply` function
137
+   - `divide` function
138
+   - etc.
139
+3. Type "broken" to filter
140
+4. Press Enter to jump to function
141
+
142
+**In main.py:**
143
+1. Press `F4`
144
+2. Should see:
145
+   - `DataProcessor` class
146
+   - `process_data` function
147
+   - `main` function
148
+
149
+### 7. Workspace Symbols (F6)
150
+
151
+**Test:** Search symbols across ALL files.
152
+
153
+1. Press `F6`
154
+2. Type "add"
155
+3. Should see:
156
+   - `add` function in calculator.py
157
+   - `vector_add` in math_utils.f90
158
+   - Maybe `sum_array` in utils.c (fuzzy match)
159
+4. Navigate with j/k
160
+5. Press Enter to jump (opens file if needed)
161
+
162
+**Try different searches:**
163
+- "calc" → finds `calculate_total`, `calculator`, etc.
164
+- "vector" → finds all vector functions
165
+- "fact" → finds `factorial`
166
+
167
+### 8. Signature Help
168
+
169
+**Test:** Parameter hints while typing.
170
+
171
+**In any Python file:**
172
+1. Type: `calculate_total(`
173
+2. Should see tooltip: `calculate_total(items)`
174
+3. As you type, current parameter is highlighted
175
+
176
+**In Fortran:**
177
+1. Type: `call vector_add(`
178
+2. Should see signature with parameters
179
+
180
+### 9. Document Formatting (Shift+Alt+F)
181
+
182
+**Test:** Auto-format code.
183
+
184
+**In calculator.py:**
185
+1. Mess up the formatting (add extra spaces, wrong indentation)
186
+2. Press `Shift+Alt+F`
187
+3. Code should be auto-formatted to PEP 8 style
188
+
189
+**In utils.c:**
190
+1. Mess up braces and indentation
191
+2. Press `Shift+Alt+F`
192
+3. Code should be formatted with clang-format
193
+
194
+### 10. Command Palette (Ctrl+P)
195
+
196
+**Test:** Access commands without keybindings.
197
+
198
+1. Press `Ctrl+P`
199
+2. Type "format"
200
+3. Select "Format Document"
201
+4. Same as Shift+Alt+F
202
+
203
+Or:
204
+1. Press `Ctrl+P`
205
+2. Type "def"
206
+3. Select "Go to Definition"
207
+4. Same as F12
208
+
209
+## Language Server Setup
210
+
211
+To test these files, you need language servers installed:
212
+
213
+**Python:**
214
+```bash
215
+pip install python-lsp-server
216
+```
217
+
218
+**Fortran:**
219
+```bash
220
+pip install fortls
221
+```
222
+
223
+**C/C++:**
224
+```bash
225
+# macOS
226
+brew install llvm
227
+
228
+# Linux
229
+sudo apt install clangd
230
+```
231
+
232
+## Expected Behavior
233
+
234
+### With Language Server Installed:
235
+- ✅ Red/yellow error markers in gutter
236
+- ✅ F12 jumps to definitions (even across files)
237
+- ✅ Shift+F12 shows all references
238
+- ✅ Ctrl+. offers quick fixes
239
+- ✅ F2 renames across files
240
+- ✅ F4 shows file outline
241
+- ✅ F6 searches all symbols
242
+- ✅ Shift+Alt+F formats code
243
+
244
+### Without Language Server:
245
+- ❌ No error markers
246
+- ❌ LSP features won't work
247
+- ✅ Basic editing still works
248
+- ✅ Syntax highlighting may work (if implemented separately)
249
+
250
+## Known Issues to Test
251
+
252
+1. **Syntax highlighting for Fortran** - Check if keywords are colored
253
+2. **F8 vs Ctrl+D** - Check if modifier keys work correctly
254
+3. **Cross-file tabs** - Check if F12 opens new tabs correctly
255
+4. **Jump stack** - Check if Alt+, returns to previous location
256
+
257
+## Quick Test Checklist
258
+
259
+- [ ] Open calculator.py and see diagnostics (red/yellow markers)
260
+- [ ] Press F8 and see error list
261
+- [ ] F12 on `add` in main.py → jumps to calculator.py
262
+- [ ] Alt+, → jumps back
263
+- [ ] Shift+F12 on `calculate_total` → shows references
264
+- [ ] F2 on `multiply` → rename and see it update in both files
265
+- [ ] F4 → see file outline
266
+- [ ] F6, type "vector" → find Fortran functions
267
+- [ ] Ctrl+. on an error → see quick fixes
268
+- [ ] Shift+Alt+F → format code
tests/scratch_files/calculator.pyadded
@@ -0,0 +1,68 @@
1
+"""
2
+Simple calculator module for testing LSP features.
3
+
4
+Test these features:
5
+- Diagnostics: See the intentional errors below
6
+- Go to Definition: F12 on function calls
7
+- Find References: Shift+F12 on function names
8
+- Rename: F2 on any function/variable
9
+- Code Actions: Ctrl+. on errors
10
+- Document Symbols: Ctrl+Shift+O to see outline
11
+- Formatting: Shift+Alt+F to format
12
+"""
13
+
14
+def add(x, y):
15
+    """Add two numbers together."""
16
+    return x + y
17
+
18
+
19
+def subtract(x, y):
20
+    """Subtract y from x."""
21
+    return x - y
22
+
23
+
24
+def multiply(x, y):
25
+    """Multiply two numbers."""
26
+    return x * y
27
+
28
+
29
+def divide(x, y):
30
+    """Divide x by y."""
31
+    if y == 0:
32
+        raise ValueError("Cannot divide by zero")
33
+    return x / y
34
+
35
+
36
+def calculate_total(items):
37
+    """Calculate total from a list of numbers."""
38
+    total = 0
39
+    for item in items:
40
+        total = total + item
41
+    return total
42
+
43
+
44
+# Intentional error for diagnostics testing
45
+def broken_function():
46
+    """This function has an error - undefined variable."""
47
+    result = add(5, 3)
48
+    print(f"Result: {reslt}")  # ERROR: typo - should be 'result'
49
+    return reslt
50
+
51
+
52
+# Another intentional error - missing import
53
+def use_math():
54
+    """Uses math module without importing it."""
55
+    return math.sqrt(16)  # ERROR: 'math' is not defined
56
+
57
+
58
+# Test code
59
+if __name__ == "__main__":
60
+    # These work fine
61
+    print(add(10, 5))
62
+    print(subtract(10, 5))
63
+    print(multiply(10, 5))
64
+    print(divide(10, 5))
65
+
66
+    # This will show diagnostic errors
67
+    broken_function()
68
+    use_math()
tests/scratch_files/main.cadded
@@ -0,0 +1,79 @@
1
+/*
2
+ * Main program using utility functions
3
+ *
4
+ * Test cross-file navigation:
5
+ * - F12 on 'factorial' should jump to utils.c
6
+ * - Shift+F12 on 'fibonacci' should show all usages
7
+ * - Ctrl+Shift+T and search "sum" to find sum_array
8
+ */
9
+
10
+#include <stdio.h>
11
+
12
+// Forward declarations from utils.c
13
+int factorial(int n);
14
+int fibonacci(int n);
15
+int sum_array(int *arr, int size);
16
+int find_max(int *arr, int size);
17
+
18
+// Structure for testing Document Symbols (Ctrl+Shift+O)
19
+typedef struct {
20
+    int id;
21
+    char name[50];
22
+    double value;
23
+} Record;
24
+
25
+// Test: F12 on this function name to jump to definition
26
+void process_records(Record *records, int count) {
27
+    printf("Processing %d records\n", count);
28
+    for (int i = 0; i < count; i++) {
29
+        printf("Record %d: %s = %.2f\n",
30
+               records[i].id,
31
+               records[i].name,
32
+               records[i].value);
33
+    }
34
+}
35
+
36
+int main() {
37
+    printf("Testing LSP features in C\n\n");
38
+
39
+    // Test factorial
40
+    // F12 on 'factorial' should jump to utils.c
41
+    int fact = factorial(5);
42
+    printf("factorial(5) = %d\n", fact);
43
+
44
+    // Test fibonacci
45
+    // Shift+F12 on 'fibonacci' should show all usages
46
+    int fib = fibonacci(7);
47
+    printf("fibonacci(7) = %d\n", fib);
48
+
49
+    // Test array functions
50
+    int numbers[] = {5, 2, 8, 1, 9, 3};
51
+    int size = sizeof(numbers) / sizeof(numbers[0]);
52
+
53
+    // F12 on 'sum_array' should jump to definition
54
+    int total = sum_array(numbers, size);
55
+    printf("sum_array() = %d\n", total);
56
+
57
+    // F12 on 'find_max' should jump to definition
58
+    int max = find_max(numbers, size);
59
+    printf("find_max() = %d\n", max);
60
+
61
+    // Test with records
62
+    Record records[2] = {
63
+        {1, "Alpha", 3.14},
64
+        {2, "Beta", 2.71}
65
+    };
66
+    process_records(records, 2);
67
+
68
+    // Intentional errors for diagnostics
69
+    // ERROR: undefined function
70
+    int result = undefined_function(10);
71
+
72
+    // ERROR: type mismatch
73
+    char *str = fact;
74
+
75
+    // ERROR: undeclared variable
76
+    printf("Value: %d\n", undeclared_var);
77
+
78
+    return 0;
79
+}
tests/scratch_files/main.pyadded
@@ -0,0 +1,71 @@
1
+"""
2
+Main module that uses calculator.
3
+
4
+Test cross-file navigation:
5
+- Put cursor on 'add' below and press F12 - should jump to calculator.py
6
+- Put cursor on 'calculate_total' and press Shift+F12 - find all usages
7
+- Use Ctrl+Shift+T and search for "multiply" - should find it in calculator.py
8
+"""
9
+
10
+from calculator import add, subtract, multiply, calculate_total
11
+
12
+
13
+def process_data(numbers):
14
+    """Process a list of numbers using calculator functions."""
15
+    # Test: F12 on 'add' should jump to calculator.py
16
+    sum_result = add(numbers[0], numbers[1])
17
+
18
+    # Test: F12 on 'multiply' should jump to calculator.py
19
+    product = multiply(sum_result, 2)
20
+
21
+    # Test: Shift+F12 on 'calculate_total' should show usages
22
+    total = calculate_total(numbers)
23
+
24
+    return {
25
+        'sum': sum_result,
26
+        'product': product,
27
+        'total': total
28
+    }
29
+
30
+
31
+class DataProcessor:
32
+    """A class for processing numerical data.
33
+
34
+    Test Document Symbols (Ctrl+Shift+O) to see this class structure.
35
+    """
36
+
37
+    def __init__(self, data):
38
+        self.data = data
39
+        self.processed = False
40
+
41
+    def calculate_sum(self):
42
+        """Calculate sum using calculator module."""
43
+        return calculate_total(self.data)
44
+
45
+    def calculate_average(self):
46
+        """Calculate average of the data."""
47
+        total = self.calculate_sum()
48
+        return divide(total, len(self.data))  # ERROR: 'divide' not imported
49
+
50
+    def process(self):
51
+        """Process the data."""
52
+        self.processed = True
53
+        return self.calculate_average()
54
+
55
+
56
+# Test with intentional error
57
+def main():
58
+    numbers = [1, 2, 3, 4, 5]
59
+
60
+    # This works
61
+    result = process_data(numbers)
62
+    print(f"Results: {result}")
63
+
64
+    # This has errors
65
+    processor = DataProcessor(numbers)
66
+    avg = processor.process()  # Will error due to missing 'divide' import
67
+    print(f"Average: {avg}")
68
+
69
+
70
+if __name__ == "__main__":
71
+    main()
tests/scratch_files/math_utils.f90added
@@ -0,0 +1,89 @@
1
+module math_utils
2
+    ! Simple math utilities for testing LSP features
3
+    !
4
+    ! Test:
5
+    ! - Syntax highlighting (keywords, comments, strings)
6
+    ! - Go to Definition (F12 on subroutine calls)
7
+    ! - Find References (Shift+F12 on subroutine names)
8
+    ! - Document Symbols (Ctrl+Shift+O to see module structure)
9
+    ! - Formatting (Shift+Alt+F)
10
+
11
+    implicit none
12
+    private
13
+
14
+    public :: vector_add, vector_dot, vector_magnitude
15
+    public :: matrix_multiply
16
+
17
+contains
18
+
19
+    subroutine vector_add(a, b, result, n)
20
+        ! Add two vectors element-wise
21
+        integer, intent(in) :: n
22
+        real, intent(in) :: a(n), b(n)
23
+        real, intent(out) :: result(n)
24
+        integer :: i
25
+
26
+        do i = 1, n
27
+            result(i) = a(i) + b(i)
28
+        end do
29
+    end subroutine vector_add
30
+
31
+    function vector_dot(a, b, n) result(dot_product)
32
+        ! Calculate dot product of two vectors
33
+        integer, intent(in) :: n
34
+        real, intent(in) :: a(n), b(n)
35
+        real :: dot_product
36
+        integer :: i
37
+
38
+        dot_product = 0.0
39
+        do i = 1, n
40
+            dot_product = dot_product + a(i) * b(i)
41
+        end do
42
+    end function vector_dot
43
+
44
+    function vector_magnitude(vec, n) result(magnitude)
45
+        ! Calculate magnitude of a vector
46
+        integer, intent(in) :: n
47
+        real, intent(in) :: vec(n)
48
+        real :: magnitude
49
+        integer :: i
50
+        real :: sum_squares
51
+
52
+        sum_squares = 0.0
53
+        do i = 1, n
54
+            sum_squares = sum_squares + vec(i)**2
55
+        end do
56
+        magnitude = sqrt(sum_squares)
57
+    end function vector_magnitude
58
+
59
+    subroutine matrix_multiply(A, B, C, m, n, p)
60
+        ! Multiply two matrices: C = A * B
61
+        ! A is m x n, B is n x p, C is m x p
62
+        integer, intent(in) :: m, n, p
63
+        real, intent(in) :: A(m, n), B(n, p)
64
+        real, intent(out) :: C(m, p)
65
+        integer :: i, j, k
66
+
67
+        ! Intentional error: missing initialization
68
+        do i = 1, m
69
+            do j = 1, p
70
+                ! ERROR: C(i,j) not initialized before use
71
+                do k = 1, n
72
+                    C(i, j) = C(i, j) + A(i, k) * B(k, j)
73
+                end do
74
+            end do
75
+        end do
76
+    end subroutine matrix_multiply
77
+
78
+    ! Intentional error function for diagnostics
79
+    subroutine broken_routine(x, y)
80
+        real, intent(in) :: x
81
+        real, intent(out) :: y
82
+        real :: temp
83
+
84
+        temp = x * 2.0
85
+        ! ERROR: using undefined variable
86
+        y = temp + undefined_var
87
+    end subroutine broken_routine
88
+
89
+end module math_utils
tests/scratch_files/test_program.f90added
@@ -0,0 +1,43 @@
1
+program test_program
2
+    ! Test program using math_utils module
3
+    !
4
+    ! Test cross-file navigation:
5
+    ! - F12 on 'vector_add' should jump to math_utils.f90
6
+    ! - Shift+F12 on 'vector_dot' should show all usages
7
+    ! - Ctrl+Shift+T and search "vector" to find all vector functions
8
+
9
+    use math_utils
10
+    implicit none
11
+
12
+    integer, parameter :: n = 3
13
+    real :: a(n), b(n), result(n)
14
+    real :: dot_prod, mag
15
+    integer :: i
16
+
17
+    ! Initialize vectors
18
+    a = [1.0, 2.0, 3.0]
19
+    b = [4.0, 5.0, 6.0]
20
+
21
+    ! Test vector addition
22
+    ! F12 on vector_add should jump to math_utils.f90
23
+    call vector_add(a, b, result, n)
24
+
25
+    print *, 'Vector A:', a
26
+    print *, 'Vector B:', b
27
+    print *, 'A + B   :', result
28
+
29
+    ! Test dot product
30
+    ! F12 on vector_dot should jump to definition
31
+    dot_prod = vector_dot(a, b, n)
32
+    print *, 'A · B   :', dot_prod
33
+
34
+    ! Test magnitude
35
+    mag = vector_magnitude(a, n)
36
+    print *, '|A|     :', mag
37
+
38
+    ! Intentional error - missing variable declaration
39
+    ! ERROR: 'undefined_result' is not declared
40
+    undefined_result = dot_prod * 2.0
41
+    print *, 'Result  :', undefined_result
42
+
43
+end program test_program
tests/scratch_files/utils.cadded
@@ -0,0 +1,76 @@
1
+/*
2
+ * Simple utility functions for testing LSP features
3
+ *
4
+ * Test:
5
+ * - Diagnostics: Intentional errors marked below
6
+ * - Go to Definition: F12 on function calls
7
+ * - Find References: Shift+F12 on function names
8
+ * - Code Actions: Ctrl+. on errors
9
+ * - Formatting: Shift+Alt+F
10
+ */
11
+
12
+#include <stdio.h>
13
+#include <stdlib.h>
14
+
15
+// Calculate factorial
16
+int factorial(int n) {
17
+    if (n <= 1) {
18
+        return 1;
19
+    }
20
+    return n * factorial(n - 1);
21
+}
22
+
23
+// Calculate fibonacci number
24
+int fibonacci(int n) {
25
+    if (n <= 1) {
26
+        return n;
27
+    }
28
+    return fibonacci(n - 1) + fibonacci(n - 2);
29
+}
30
+
31
+// Sum array elements
32
+int sum_array(int *arr, int size) {
33
+    int total = 0;
34
+    for (int i = 0; i < size; i++) {
35
+        total += arr[i];
36
+    }
37
+    return total;
38
+}
39
+
40
+// Find maximum in array
41
+int find_max(int *arr, int size) {
42
+    if (size <= 0) {
43
+        return 0;
44
+    }
45
+
46
+    int max = arr[0];
47
+    for (int i = 1; i < size; i++) {
48
+        if (arr[i] > max) {
49
+            max = arr[i];
50
+        }
51
+    }
52
+    return max;
53
+}
54
+
55
+// Intentional error function for diagnostics
56
+int broken_function() {
57
+    int x = 10;
58
+    int y = 20;
59
+
60
+    // ERROR: undefined variable
61
+    int result = x + y + undefined_var;
62
+
63
+    // ERROR: incompatible pointer type
64
+    char *str = result;
65
+
66
+    printf("Result: %d\n", result);
67
+    return result;
68
+}
69
+
70
+// Another error - missing return
71
+int missing_return(int x) {
72
+    if (x > 0) {
73
+        return x * 2;
74
+    }
75
+    // ERROR: missing return statement for x <= 0
76
+}