fortrangoingonforty/facsimile / 098cb2b

Browse files

bug fixes with tabs and f6 menu

Authored by espadonne
SHA
098cb2b7b6f04c4b57ab14e284973a763d2a52ca
Parents
b845f07
Tree
878a44f

3 changed files

StatusFile+-
M app/main.f90 47 6
M src/commands/command_handler_module.f90 105 10
M src/lsp/lsp_server_manager_module.f90 31 25
app/main.f90modified
@@ -18,18 +18,19 @@ program facsimile
18
     use binary_prompt_module, only: binary_file_prompt
18
     use binary_prompt_module, only: binary_file_prompt
19
     use lsp_server_manager_module, only: notify_file_opened, notify_file_changed, &
19
     use lsp_server_manager_module, only: notify_file_opened, notify_file_changed, &
20
                                          notify_file_closed, process_server_messages, &
20
                                          notify_file_closed, process_server_messages, &
21
-                                         set_diagnostics_handler
21
+                                         set_diagnostics_handler, set_lsp_workspace_root
22
     use lsp_protocol_module, only: lsp_message_t
22
     use lsp_protocol_module, only: lsp_message_t
23
     implicit none
23
     implicit none
24
 
24
 
25
     type(editor_state_t) :: editor
25
     type(editor_state_t) :: editor
26
     type(buffer_t) :: buffer
26
     type(buffer_t) :: buffer
27
     character(len=32) :: key_input
27
     character(len=32) :: key_input
28
-    character(len=512) :: filename, arg, workspace_dir
28
+    character(len=512) :: filename, arg, workspace_dir, lsp_workspace
29
     logical :: running, should_quit, is_workspace_mode, workspace_success
29
     logical :: running, should_quit, is_workspace_mode, workspace_success
30
     logical :: welcome_cancelled, is_browse, nav_cancelled, is_directory
30
     logical :: welcome_cancelled, is_browse, nav_cancelled, is_directory
31
+    logical :: explicit_lsp_workspace
31
     character(len=:), allocatable :: selected_path
32
     character(len=:), allocatable :: selected_path
32
-    integer :: status, argc, rows, cols
33
+    integer :: status, argc, rows, cols, i
33
 
34
 
34
 
35
 
35
     ! Get command line arguments
36
     ! Get command line arguments
@@ -37,9 +38,37 @@ program facsimile
37
     is_workspace_mode = .false.
38
     is_workspace_mode = .false.
38
     workspace_dir = ""
39
     workspace_dir = ""
39
     filename = ""
40
     filename = ""
41
+    lsp_workspace = ""
42
+    explicit_lsp_workspace = .false.
43
+
44
+    ! First pass: look for -w/--workspace flag
45
+    i = 1
46
+    do while (i <= argc)
47
+        call get_command_argument(i, arg)
48
+        if (trim(arg) == '-w' .or. trim(arg) == '--workspace') then
49
+            if (i < argc) then
50
+                call get_command_argument(i + 1, lsp_workspace)
51
+                explicit_lsp_workspace = .true.
52
+                i = i + 2
53
+            else
54
+                write(error_unit, '(A)') 'Error: -w/--workspace requires a directory argument'
55
+                stop 1
56
+            end if
57
+        else
58
+            i = i + 1
59
+        end if
60
+    end do
40
 
61
 
41
-    if (argc > 0) then
62
+    ! Second pass: handle other arguments
42
-        call get_command_argument(1, arg)
63
+    i = 1
64
+    do while (i <= argc)
65
+        call get_command_argument(i, arg)
66
+
67
+        ! Skip -w and its argument (already processed)
68
+        if (trim(arg) == '-w' .or. trim(arg) == '--workspace') then
69
+            i = i + 2
70
+            cycle
71
+        end if
43
 
72
 
44
         ! Handle version flags
73
         ! Handle version flags
45
         if (trim(arg) == '--version' .or. trim(arg) == '-v') then
74
         if (trim(arg) == '--version' .or. trim(arg) == '-v') then
@@ -53,6 +82,7 @@ program facsimile
53
             stop
82
             stop
54
         end if
83
         end if
55
 
84
 
85
+        ! This must be the file/directory argument
56
         ! Check if argument is a directory (workspace mode)
86
         ! Check if argument is a directory (workspace mode)
57
         ! Use test -d which is POSIX compliant (works on Linux, macOS, BSD)
87
         ! Use test -d which is POSIX compliant (works on Linux, macOS, BSD)
58
         call execute_command_line("test -d '" // trim(arg) // &
88
         call execute_command_line("test -d '" // trim(arg) // &
@@ -71,7 +101,10 @@ program facsimile
71
             end if
101
             end if
72
             filename = arg
102
             filename = arg
73
         end if
103
         end if
74
-    else
104
+        i = i + 1
105
+    end do
106
+
107
+    if (argc == 0) then
75
         ! No arguments - launch Fortress welcome menu (Phase 5)
108
         ! No arguments - launch Fortress welcome menu (Phase 5)
76
         call terminal_init()
109
         call terminal_init()
77
         call show_welcome_menu(selected_path, welcome_cancelled)
110
         call show_welcome_menu(selected_path, welcome_cancelled)
@@ -168,6 +201,11 @@ program facsimile
168
     call init_editor(editor)
201
     call init_editor(editor)
169
     running = .true.
202
     running = .true.
170
 
203
 
204
+    ! Set LSP workspace root if explicit -w flag was provided
205
+    if (explicit_lsp_workspace) then
206
+        call set_lsp_workspace_root(editor%lsp_manager, trim(lsp_workspace))
207
+    end if
208
+
171
     ! Set up diagnostics handler for LSP
209
     ! Set up diagnostics handler for LSP
172
     call set_diagnostics_handler(editor%lsp_manager, handle_diagnostics)
210
     call set_diagnostics_handler(editor%lsp_manager, handle_diagnostics)
173
 
211
 
@@ -541,9 +579,12 @@ contains
541
         write(output_unit, '(A)') ''
579
         write(output_unit, '(A)') ''
542
         write(output_unit, '(A)') 'Usage:'
580
         write(output_unit, '(A)') 'Usage:'
543
         write(output_unit, '(A)') '  fac [filename]       Open a file for editing'
581
         write(output_unit, '(A)') '  fac [filename]       Open a file for editing'
582
+        write(output_unit, '(A)') '  fac [directory]      Open directory in workspace mode'
544
         write(output_unit, '(A)') '  fac                  Start with empty buffer'
583
         write(output_unit, '(A)') '  fac                  Start with empty buffer'
545
         write(output_unit, '(A)') '  fac --version, -v    Show version information'
584
         write(output_unit, '(A)') '  fac --version, -v    Show version information'
546
         write(output_unit, '(A)') '  fac --help, -h       Show this help message'
585
         write(output_unit, '(A)') '  fac --help, -h       Show this help message'
586
+        write(output_unit, '(A)') '  fac -w <dir> [file]  Set LSP workspace root to <dir>'
587
+        write(output_unit, '(A)') '  fac --workspace <dir> Same as -w'
547
         write(output_unit, '(A)') ''
588
         write(output_unit, '(A)') ''
548
         write(output_unit, '(A)') 'Key Bindings:'
589
         write(output_unit, '(A)') 'Key Bindings:'
549
         write(output_unit, '(A)') '  Ctrl-Q               Quit'
590
         write(output_unit, '(A)') '  Ctrl-Q               Quit'
src/commands/command_handler_module.f90modified
@@ -6644,7 +6644,7 @@ contains
6644
     subroutine navigate_to_workspace_symbol(editor, buffer, symbol, should_quit)
6644
     subroutine navigate_to_workspace_symbol(editor, buffer, symbol, should_quit)
6645
         use workspace_symbols_panel_module, only: workspace_symbol_t
6645
         use workspace_symbols_panel_module, only: workspace_symbol_t
6646
         use jump_stack_module, only: push_jump_location
6646
         use jump_stack_module, only: push_jump_location
6647
-        use editor_state_module, only: switch_to_tab, create_tab, sync_pane_to_editor, sync_editor_to_pane
6647
+        use editor_state_module, only: switch_to_tab_with_buffer, create_tab, sync_pane_to_editor, sync_editor_to_pane
6648
         use text_buffer_module, only: buffer_load_file, copy_buffer
6648
         use text_buffer_module, only: buffer_load_file, copy_buffer
6649
         type(editor_state_t), intent(inout) :: editor
6649
         type(editor_state_t), intent(inout) :: editor
6650
         type(buffer_t), intent(inout) :: buffer
6650
         type(buffer_t), intent(inout) :: buffer
@@ -6669,27 +6669,55 @@ contains
6669
                 editor%cursors(editor%active_cursor)%column)
6669
                 editor%cursors(editor%active_cursor)%column)
6670
         end if
6670
         end if
6671
 
6671
 
6672
-        ! Check if file is already open in a tab
6672
+        ! FIRST: Check if symbol is in the currently active tab (just jump, no tab switch)
6673
+        if (editor%active_tab_index > 0 .and. editor%active_tab_index <= size(editor%tabs)) then
6674
+            if (allocated(editor%tabs(editor%active_tab_index)%filename)) then
6675
+                if (paths_match(editor%tabs(editor%active_tab_index)%filename, filepath)) then
6676
+                    ! Same file - just jump to the position
6677
+                    editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
6678
+                    editor%cursors(editor%active_cursor)%column = symbol%column + 1
6679
+                    editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
6680
+                    editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
6681
+                    call sync_editor_to_pane(editor)
6682
+                    return
6683
+                end if
6684
+            end if
6685
+        end if
6686
+
6687
+        ! SECOND: Check if file is open in another (inactive) tab
6673
         do i = 1, size(editor%tabs)
6688
         do i = 1, size(editor%tabs)
6689
+            if (i == editor%active_tab_index) cycle  ! Skip active tab, already checked
6674
             if (allocated(editor%tabs(i)%filename)) then
6690
             if (allocated(editor%tabs(i)%filename)) then
6675
-                if (trim(editor%tabs(i)%filename) == trim(filepath)) then
6691
+                if (paths_match(editor%tabs(i)%filename, filepath)) then
6676
-                    call switch_to_tab(editor, i)
6692
+                    ! Save current buffer and switch to existing tab
6693
+                    call switch_to_tab_with_buffer(editor, i, buffer)
6677
                     ! Jump to the symbol's position
6694
                     ! Jump to the symbol's position
6678
                     editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
6695
                     editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
6679
                     editor%cursors(editor%active_cursor)%column = symbol%column + 1
6696
                     editor%cursors(editor%active_cursor)%column = symbol%column + 1
6680
                     editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
6697
                     editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
6681
                     editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
6698
                     editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
6699
+                    call sync_editor_to_pane(editor)
6682
                     return
6700
                     return
6683
                 end if
6701
                 end if
6684
             end if
6702
             end if
6685
         end do
6703
         end do
6686
 
6704
 
6687
         ! File not open - create a new tab and load the file
6705
         ! File not open - create a new tab and load the file
6688
-        call create_tab(editor, filepath)
6689
-
6690
-        ! Load file content into the new tab's buffer
6691
         block
6706
         block
6692
-            integer :: status, new_tab_idx
6707
+            integer :: status, new_tab_idx, old_tab_idx, old_pane_idx
6708
+
6709
+            ! CRITICAL: Save current buffer to old tab BEFORE create_tab changes active_tab_index
6710
+            old_tab_idx = editor%active_tab_index
6711
+            if (old_tab_idx > 0 .and. old_tab_idx <= size(editor%tabs)) then
6712
+                old_pane_idx = editor%tabs(old_tab_idx)%active_pane_index
6713
+                if (allocated(editor%tabs(old_tab_idx)%panes) .and. &
6714
+                    old_pane_idx > 0 .and. old_pane_idx <= size(editor%tabs(old_tab_idx)%panes)) then
6715
+                    call copy_buffer(editor%tabs(old_tab_idx)%panes(old_pane_idx)%buffer, buffer)
6716
+                end if
6717
+                call copy_buffer(editor%tabs(old_tab_idx)%buffer, buffer)
6718
+            end if
6719
+
6720
+            call create_tab(editor, filepath)
6693
 
6721
 
6694
             new_tab_idx = size(editor%tabs)  ! The tab we just created
6722
             new_tab_idx = size(editor%tabs)  ! The tab we just created
6695
 
6723
 
@@ -6702,8 +6730,14 @@ contains
6702
                     call copy_buffer(editor%tabs(new_tab_idx)%panes(1)%buffer, editor%tabs(new_tab_idx)%buffer)
6730
                     call copy_buffer(editor%tabs(new_tab_idx)%panes(1)%buffer, editor%tabs(new_tab_idx)%buffer)
6703
                 end if
6731
                 end if
6704
 
6732
 
6705
-                ! Switch to the new tab
6733
+                ! Load the new tab's buffer into working buffer (create_tab already switched active_tab_index)
6706
-                call switch_to_tab(editor, new_tab_idx)
6734
+                call copy_buffer(buffer, editor%tabs(new_tab_idx)%buffer)
6735
+
6736
+                ! Update editor%filename to the new tab's filename
6737
+                if (allocated(editor%filename)) deallocate(editor%filename)
6738
+                allocate(character(len=len(editor%tabs(new_tab_idx)%filename)) :: editor%filename)
6739
+                editor%filename = editor%tabs(new_tab_idx)%filename
6740
+                editor%modified = editor%tabs(new_tab_idx)%modified
6707
 
6741
 
6708
                 ! Sync the pane to editor state (this updates editor%cursors, etc.)
6742
                 ! Sync the pane to editor state (this updates editor%cursors, etc.)
6709
                 call sync_pane_to_editor(editor, new_tab_idx, 1)
6743
                 call sync_pane_to_editor(editor, new_tab_idx, 1)
@@ -6724,6 +6758,67 @@ contains
6724
         end block
6758
         end block
6725
     end subroutine navigate_to_workspace_symbol
6759
     end subroutine navigate_to_workspace_symbol
6726
 
6760
 
6761
+    ! Helper function to compare file paths (handles relative vs absolute)
6762
+    function paths_match(path1, path2) result(match)
6763
+        character(len=*), intent(in) :: path1, path2
6764
+        logical :: match
6765
+        character(len=:), allocatable :: p1, p2
6766
+
6767
+        match = .false.
6768
+
6769
+        ! Direct comparison first
6770
+        if (trim(path1) == trim(path2)) then
6771
+            match = .true.
6772
+            return
6773
+        end if
6774
+
6775
+        ! Try comparing just the filenames (basename) if one is relative
6776
+        p1 = get_path_basename(path1)
6777
+        p2 = get_path_basename(path2)
6778
+
6779
+        ! If basenames match and one path ends with the other, consider it a match
6780
+        if (trim(p1) == trim(p2)) then
6781
+            ! Check if one path is a suffix of the other
6782
+            if (index(path1, trim(path2)) > 0 .or. index(path2, trim(path1)) > 0) then
6783
+                match = .true.
6784
+                return
6785
+            end if
6786
+            ! Also match if the absolute path ends with the relative path
6787
+            if (len_trim(path1) > len_trim(path2)) then
6788
+                if (path1(len_trim(path1)-len_trim(path2)+1:) == trim(path2)) then
6789
+                    match = .true.
6790
+                    return
6791
+                end if
6792
+            else if (len_trim(path2) > len_trim(path1)) then
6793
+                if (path2(len_trim(path2)-len_trim(path1)+1:) == trim(path1)) then
6794
+                    match = .true.
6795
+                    return
6796
+                end if
6797
+            end if
6798
+        end if
6799
+    end function paths_match
6800
+
6801
+    ! Get basename from a path
6802
+    function get_path_basename(path) result(basename)
6803
+        character(len=*), intent(in) :: path
6804
+        character(len=:), allocatable :: basename
6805
+        integer :: i, last_slash
6806
+
6807
+        last_slash = 0
6808
+        do i = len_trim(path), 1, -1
6809
+            if (path(i:i) == '/') then
6810
+                last_slash = i
6811
+                exit
6812
+            end if
6813
+        end do
6814
+
6815
+        if (last_slash > 0 .and. last_slash < len_trim(path)) then
6816
+            basename = path(last_slash+1:len_trim(path))
6817
+        else
6818
+            basename = trim(path)
6819
+        end if
6820
+    end function get_path_basename
6821
+
6727
     ! ==================================================
6822
     ! ==================================================
6728
     ! LSP Definition Response Handler
6823
     ! LSP Definition Response Handler
6729
     ! ==================================================
6824
     ! ==================================================
src/lsp/lsp_server_manager_module.f90modified
@@ -9,7 +9,7 @@ module lsp_server_manager_module
9
 
9
 
10
     public :: lsp_server_t
10
     public :: lsp_server_t
11
     public :: lsp_manager_t
11
     public :: lsp_manager_t
12
-    public :: init_lsp_manager, cleanup_lsp_manager
12
+    public :: init_lsp_manager, cleanup_lsp_manager, set_lsp_workspace_root
13
     public :: get_or_start_server, stop_server
13
     public :: get_or_start_server, stop_server
14
     public :: send_request, send_notification
14
     public :: send_request, send_notification
15
     public :: process_server_messages
15
     public :: process_server_messages
@@ -121,6 +121,7 @@ module lsp_server_manager_module
121
         type(callback_entry_t), allocatable :: callbacks(:)
121
         type(callback_entry_t), allocatable :: callbacks(:)
122
         integer :: num_callbacks = 0
122
         integer :: num_callbacks = 0
123
         procedure(diagnostics_callback), pointer, nopass :: diagnostics_handler => null()
123
         procedure(diagnostics_callback), pointer, nopass :: diagnostics_handler => null()
124
+        character(len=512) :: workspace_root = '.'  ! LSP workspace root (cwd by default)
124
     end type lsp_manager_t
125
     end type lsp_manager_t
125
 
126
 
126
     ! C interfaces
127
     ! C interfaces
@@ -168,17 +169,40 @@ module lsp_server_manager_module
168
 
169
 
169
 contains
170
 contains
170
 
171
 
171
-    subroutine init_lsp_manager(manager)
172
+    subroutine init_lsp_manager(manager, workspace_root)
172
         type(lsp_manager_t), intent(out) :: manager
173
         type(lsp_manager_t), intent(out) :: manager
174
+        character(len=*), intent(in), optional :: workspace_root
175
+        character(len=512) :: cwd
176
+        integer :: status
173
 
177
 
174
         allocate(manager%servers(0))
178
         allocate(manager%servers(0))
175
         allocate(manager%configs(0))
179
         allocate(manager%configs(0))
176
         allocate(manager%callbacks(0))
180
         allocate(manager%callbacks(0))
177
 
181
 
182
+        ! Set workspace root (default to cwd if not provided)
183
+        if (present(workspace_root) .and. len_trim(workspace_root) > 0) then
184
+            manager%workspace_root = trim(workspace_root)
185
+        else
186
+            call getcwd(cwd, status)
187
+            if (status == 0) then
188
+                manager%workspace_root = trim(cwd)
189
+            else
190
+                manager%workspace_root = '.'
191
+            end if
192
+        end if
193
+
178
         ! Load default server configurations
194
         ! Load default server configurations
179
         call load_default_configs(manager)
195
         call load_default_configs(manager)
180
     end subroutine init_lsp_manager
196
     end subroutine init_lsp_manager
181
 
197
 
198
+    ! Set the workspace root for LSP servers (call before opening files)
199
+    subroutine set_lsp_workspace_root(manager, workspace_root)
200
+        type(lsp_manager_t), intent(inout) :: manager
201
+        character(len=*), intent(in) :: workspace_root
202
+
203
+        manager%workspace_root = trim(workspace_root)
204
+    end subroutine set_lsp_workspace_root
205
+
182
     subroutine cleanup_lsp_manager(manager)
206
     subroutine cleanup_lsp_manager(manager)
183
         type(lsp_manager_t), intent(inout) :: manager
207
         type(lsp_manager_t), intent(inout) :: manager
184
         integer :: i
208
         integer :: i
@@ -861,8 +885,6 @@ contains
861
         character(len=*), intent(in) :: filename
885
         character(len=*), intent(in) :: filename
862
         integer :: server_index
886
         integer :: server_index
863
         character(len=:), allocatable :: language
887
         character(len=:), allocatable :: language
864
-        character(len=256) :: workspace_path
865
-        integer :: slash_pos
866
 
888
 
867
         server_index = 0
889
         server_index = 0
868
 
890
 
@@ -870,16 +892,8 @@ contains
870
         language = get_language_for_file(filename)
892
         language = get_language_for_file(filename)
871
         if (language == "") return
893
         if (language == "") return
872
 
894
 
873
-        ! Extract workspace path from filename (directory containing file)
895
+        ! Use manager's workspace_root (set during init, defaults to cwd)
874
-        slash_pos = index(filename, '/', back=.true.)
896
+        server_index = get_or_start_server(manager, language, trim(manager%workspace_root))
875
-        if (slash_pos > 0) then
876
-            workspace_path = filename(1:slash_pos-1)
877
-        else
878
-            workspace_path = "."
879
-        end if
880
-
881
-        ! Get or start server for this language
882
-        server_index = get_or_start_server(manager, language, trim(workspace_path))
883
     end function start_lsp_for_file
897
     end function start_lsp_for_file
884
 
898
 
885
     ! Start ALL LSP servers that match a file (multi-server support)
899
     ! Start ALL LSP servers that match a file (multi-server support)
@@ -889,8 +903,7 @@ contains
889
         integer, allocatable, intent(out) :: server_indices(:)
903
         integer, allocatable, intent(out) :: server_indices(:)
890
         integer, intent(out) :: num_servers
904
         integer, intent(out) :: num_servers
891
         character(len=:), allocatable :: language
905
         character(len=:), allocatable :: language
892
-        character(len=256) :: workspace_path
906
+        integer :: i, idx
893
-        integer :: slash_pos, i, idx
894
         integer :: temp_indices(20)  ! Max 20 servers per file
907
         integer :: temp_indices(20)  ! Max 20 servers per file
895
 
908
 
896
         num_servers = 0
909
         num_servers = 0
@@ -900,19 +913,12 @@ contains
900
         language = get_language_for_file(filename)
913
         language = get_language_for_file(filename)
901
         if (language == "") return
914
         if (language == "") return
902
 
915
 
903
-        ! Extract workspace path from filename
916
+        ! Use manager's workspace_root (set during init, defaults to cwd)
904
-        slash_pos = index(filename, '/', back=.true.)
905
-        if (slash_pos > 0) then
906
-            workspace_path = filename(1:slash_pos-1)
907
-        else
908
-            workspace_path = "."
909
-        end if
910
-
911
         ! Find ALL configs that match this language and start servers
917
         ! Find ALL configs that match this language and start servers
912
         do i = 1, manager%num_configs
918
         do i = 1, manager%num_configs
913
             if (manager%configs(i)%language == language) then
919
             if (manager%configs(i)%language == language) then
914
                 ! Start or get server for this config
920
                 ! Start or get server for this config
915
-                idx = get_or_start_server_by_config(manager, i, trim(workspace_path))
921
+                idx = get_or_start_server_by_config(manager, i, trim(manager%workspace_root))
916
                 if (idx > 0 .and. num_servers < 20) then
922
                 if (idx > 0 .and. num_servers < 20) then
917
                     num_servers = num_servers + 1
923
                     num_servers = num_servers + 1
918
                     temp_indices(num_servers) = idx
924
                     temp_indices(num_servers) = idx