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
1818
     use binary_prompt_module, only: binary_file_prompt
1919
     use lsp_server_manager_module, only: notify_file_opened, notify_file_changed, &
2020
                                          notify_file_closed, process_server_messages, &
21
-                                         set_diagnostics_handler
21
+                                         set_diagnostics_handler, set_lsp_workspace_root
2222
     use lsp_protocol_module, only: lsp_message_t
2323
     implicit none
2424
 
2525
     type(editor_state_t) :: editor
2626
     type(buffer_t) :: buffer
2727
     character(len=32) :: key_input
28
-    character(len=512) :: filename, arg, workspace_dir
28
+    character(len=512) :: filename, arg, workspace_dir, lsp_workspace
2929
     logical :: running, should_quit, is_workspace_mode, workspace_success
3030
     logical :: welcome_cancelled, is_browse, nav_cancelled, is_directory
31
+    logical :: explicit_lsp_workspace
3132
     character(len=:), allocatable :: selected_path
32
-    integer :: status, argc, rows, cols
33
+    integer :: status, argc, rows, cols, i
3334
 
3435
 
3536
     ! Get command line arguments
@@ -37,9 +38,37 @@ program facsimile
3738
     is_workspace_mode = .false.
3839
     workspace_dir = ""
3940
     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
4061
 
41
-    if (argc > 0) then
42
-        call get_command_argument(1, arg)
62
+    ! Second pass: handle other arguments
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
4372
 
4473
         ! Handle version flags
4574
         if (trim(arg) == '--version' .or. trim(arg) == '-v') then
@@ -53,6 +82,7 @@ program facsimile
5382
             stop
5483
         end if
5584
 
85
+        ! This must be the file/directory argument
5686
         ! Check if argument is a directory (workspace mode)
5787
         ! Use test -d which is POSIX compliant (works on Linux, macOS, BSD)
5888
         call execute_command_line("test -d '" // trim(arg) // &
@@ -71,7 +101,10 @@ program facsimile
71101
             end if
72102
             filename = arg
73103
         end if
74
-    else
104
+        i = i + 1
105
+    end do
106
+
107
+    if (argc == 0) then
75108
         ! No arguments - launch Fortress welcome menu (Phase 5)
76109
         call terminal_init()
77110
         call show_welcome_menu(selected_path, welcome_cancelled)
@@ -168,6 +201,11 @@ program facsimile
168201
     call init_editor(editor)
169202
     running = .true.
170203
 
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
+
171209
     ! Set up diagnostics handler for LSP
172210
     call set_diagnostics_handler(editor%lsp_manager, handle_diagnostics)
173211
 
@@ -541,9 +579,12 @@ contains
541579
         write(output_unit, '(A)') ''
542580
         write(output_unit, '(A)') 'Usage:'
543581
         write(output_unit, '(A)') '  fac [filename]       Open a file for editing'
582
+        write(output_unit, '(A)') '  fac [directory]      Open directory in workspace mode'
544583
         write(output_unit, '(A)') '  fac                  Start with empty buffer'
545584
         write(output_unit, '(A)') '  fac --version, -v    Show version information'
546585
         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'
547588
         write(output_unit, '(A)') ''
548589
         write(output_unit, '(A)') 'Key Bindings:'
549590
         write(output_unit, '(A)') '  Ctrl-Q               Quit'
src/commands/command_handler_module.f90modified
@@ -6644,7 +6644,7 @@ contains
66446644
     subroutine navigate_to_workspace_symbol(editor, buffer, symbol, should_quit)
66456645
         use workspace_symbols_panel_module, only: workspace_symbol_t
66466646
         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
66486648
         use text_buffer_module, only: buffer_load_file, copy_buffer
66496649
         type(editor_state_t), intent(inout) :: editor
66506650
         type(buffer_t), intent(inout) :: buffer
@@ -6669,27 +6669,55 @@ contains
66696669
                 editor%cursors(editor%active_cursor)%column)
66706670
         end if
66716671
 
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
66736688
         do i = 1, size(editor%tabs)
6689
+            if (i == editor%active_tab_index) cycle  ! Skip active tab, already checked
66746690
             if (allocated(editor%tabs(i)%filename)) then
6675
-                if (trim(editor%tabs(i)%filename) == trim(filepath)) then
6676
-                    call switch_to_tab(editor, i)
6691
+                if (paths_match(editor%tabs(i)%filename, filepath)) then
6692
+                    ! Save current buffer and switch to existing tab
6693
+                    call switch_to_tab_with_buffer(editor, i, buffer)
66776694
                     ! Jump to the symbol's position
66786695
                     editor%cursors(editor%active_cursor)%line = symbol%line + 1  ! LSP is 0-based
66796696
                     editor%cursors(editor%active_cursor)%column = symbol%column + 1
66806697
                     editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1
66816698
                     editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2)
6699
+                    call sync_editor_to_pane(editor)
66826700
                     return
66836701
                 end if
66846702
             end if
66856703
         end do
66866704
 
66876705
         ! 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
66916706
         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)
66936721
 
66946722
             new_tab_idx = size(editor%tabs)  ! The tab we just created
66956723
 
@@ -6702,8 +6730,14 @@ contains
67026730
                     call copy_buffer(editor%tabs(new_tab_idx)%panes(1)%buffer, editor%tabs(new_tab_idx)%buffer)
67036731
                 end if
67046732
 
6705
-                ! Switch to the new tab
6706
-                call switch_to_tab(editor, new_tab_idx)
6733
+                ! Load the new tab's buffer into working buffer (create_tab already switched active_tab_index)
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
67076741
 
67086742
                 ! Sync the pane to editor state (this updates editor%cursors, etc.)
67096743
                 call sync_pane_to_editor(editor, new_tab_idx, 1)
@@ -6724,6 +6758,67 @@ contains
67246758
         end block
67256759
     end subroutine navigate_to_workspace_symbol
67266760
 
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
+
67276822
     ! ==================================================
67286823
     ! LSP Definition Response Handler
67296824
     ! ==================================================
src/lsp/lsp_server_manager_module.f90modified
@@ -9,7 +9,7 @@ module lsp_server_manager_module
99
 
1010
     public :: lsp_server_t
1111
     public :: lsp_manager_t
12
-    public :: init_lsp_manager, cleanup_lsp_manager
12
+    public :: init_lsp_manager, cleanup_lsp_manager, set_lsp_workspace_root
1313
     public :: get_or_start_server, stop_server
1414
     public :: send_request, send_notification
1515
     public :: process_server_messages
@@ -121,6 +121,7 @@ module lsp_server_manager_module
121121
         type(callback_entry_t), allocatable :: callbacks(:)
122122
         integer :: num_callbacks = 0
123123
         procedure(diagnostics_callback), pointer, nopass :: diagnostics_handler => null()
124
+        character(len=512) :: workspace_root = '.'  ! LSP workspace root (cwd by default)
124125
     end type lsp_manager_t
125126
 
126127
     ! C interfaces
@@ -168,17 +169,40 @@ module lsp_server_manager_module
168169
 
169170
 contains
170171
 
171
-    subroutine init_lsp_manager(manager)
172
+    subroutine init_lsp_manager(manager, workspace_root)
172173
         type(lsp_manager_t), intent(out) :: manager
174
+        character(len=*), intent(in), optional :: workspace_root
175
+        character(len=512) :: cwd
176
+        integer :: status
173177
 
174178
         allocate(manager%servers(0))
175179
         allocate(manager%configs(0))
176180
         allocate(manager%callbacks(0))
177181
 
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
+
178194
         ! Load default server configurations
179195
         call load_default_configs(manager)
180196
     end subroutine init_lsp_manager
181197
 
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
+
182206
     subroutine cleanup_lsp_manager(manager)
183207
         type(lsp_manager_t), intent(inout) :: manager
184208
         integer :: i
@@ -861,8 +885,6 @@ contains
861885
         character(len=*), intent(in) :: filename
862886
         integer :: server_index
863887
         character(len=:), allocatable :: language
864
-        character(len=256) :: workspace_path
865
-        integer :: slash_pos
866888
 
867889
         server_index = 0
868890
 
@@ -870,16 +892,8 @@ contains
870892
         language = get_language_for_file(filename)
871893
         if (language == "") return
872894
 
873
-        ! Extract workspace path from filename (directory containing file)
874
-        slash_pos = index(filename, '/', back=.true.)
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))
895
+        ! Use manager's workspace_root (set during init, defaults to cwd)
896
+        server_index = get_or_start_server(manager, language, trim(manager%workspace_root))
883897
     end function start_lsp_for_file
884898
 
885899
     ! Start ALL LSP servers that match a file (multi-server support)
@@ -889,8 +903,7 @@ contains
889903
         integer, allocatable, intent(out) :: server_indices(:)
890904
         integer, intent(out) :: num_servers
891905
         character(len=:), allocatable :: language
892
-        character(len=256) :: workspace_path
893
-        integer :: slash_pos, i, idx
906
+        integer :: i, idx
894907
         integer :: temp_indices(20)  ! Max 20 servers per file
895908
 
896909
         num_servers = 0
@@ -900,19 +913,12 @@ contains
900913
         language = get_language_for_file(filename)
901914
         if (language == "") return
902915
 
903
-        ! Extract workspace path from filename
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
-
916
+        ! Use manager's workspace_root (set during init, defaults to cwd)
911917
         ! Find ALL configs that match this language and start servers
912918
         do i = 1, manager%num_configs
913919
             if (manager%configs(i)%language == language) then
914920
                 ! 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))
916922
                 if (idx > 0 .and. num_servers < 20) then
917923
                     num_servers = num_servers + 1
918924
                     temp_indices(num_servers) = idx