bug fixes with tabs and f6 menu
- SHA
098cb2b7b6f04c4b57ab14e284973a763d2a52ca- Parents
-
b845f07 - Tree
878a44f
098cb2b
098cb2b7b6f04c4b57ab14e284973a763d2a52cab845f07
878a44f| Status | File | + | - |
|---|---|---|---|
| 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 | 18 | use binary_prompt_module, only: binary_file_prompt |
| 19 | 19 | use lsp_server_manager_module, only: notify_file_opened, notify_file_changed, & |
| 20 | 20 | notify_file_closed, process_server_messages, & |
| 21 | - set_diagnostics_handler | |
| 21 | + set_diagnostics_handler, set_lsp_workspace_root | |
| 22 | 22 | use lsp_protocol_module, only: lsp_message_t |
| 23 | 23 | implicit none |
| 24 | 24 | |
| 25 | 25 | type(editor_state_t) :: editor |
| 26 | 26 | type(buffer_t) :: buffer |
| 27 | 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 | 29 | logical :: running, should_quit, is_workspace_mode, workspace_success |
| 30 | 30 | logical :: welcome_cancelled, is_browse, nav_cancelled, is_directory |
| 31 | + logical :: explicit_lsp_workspace | |
| 31 | 32 | character(len=:), allocatable :: selected_path |
| 32 | - integer :: status, argc, rows, cols | |
| 33 | + integer :: status, argc, rows, cols, i | |
| 33 | 34 | |
| 34 | 35 | |
| 35 | 36 | ! Get command line arguments |
@@ -37,9 +38,37 @@ program facsimile | ||
| 37 | 38 | is_workspace_mode = .false. |
| 38 | 39 | workspace_dir = "" |
| 39 | 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 | |
| 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 | |
| 43 | 72 | |
| 44 | 73 | ! Handle version flags |
| 45 | 74 | if (trim(arg) == '--version' .or. trim(arg) == '-v') then |
@@ -53,6 +82,7 @@ program facsimile | ||
| 53 | 82 | stop |
| 54 | 83 | end if |
| 55 | 84 | |
| 85 | + ! This must be the file/directory argument | |
| 56 | 86 | ! Check if argument is a directory (workspace mode) |
| 57 | 87 | ! Use test -d which is POSIX compliant (works on Linux, macOS, BSD) |
| 58 | 88 | call execute_command_line("test -d '" // trim(arg) // & |
@@ -71,7 +101,10 @@ program facsimile | ||
| 71 | 101 | end if |
| 72 | 102 | filename = arg |
| 73 | 103 | end if |
| 74 | - else | |
| 104 | + i = i + 1 | |
| 105 | + end do | |
| 106 | + | |
| 107 | + if (argc == 0) then | |
| 75 | 108 | ! No arguments - launch Fortress welcome menu (Phase 5) |
| 76 | 109 | call terminal_init() |
| 77 | 110 | call show_welcome_menu(selected_path, welcome_cancelled) |
@@ -168,6 +201,11 @@ program facsimile | ||
| 168 | 201 | call init_editor(editor) |
| 169 | 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 | 209 | ! Set up diagnostics handler for LSP |
| 172 | 210 | call set_diagnostics_handler(editor%lsp_manager, handle_diagnostics) |
| 173 | 211 | |
@@ -541,9 +579,12 @@ contains | ||
| 541 | 579 | write(output_unit, '(A)') '' |
| 542 | 580 | write(output_unit, '(A)') 'Usage:' |
| 543 | 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 | 583 | write(output_unit, '(A)') ' fac Start with empty buffer' |
| 545 | 584 | write(output_unit, '(A)') ' fac --version, -v Show version information' |
| 546 | 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 | 588 | write(output_unit, '(A)') '' |
| 548 | 589 | write(output_unit, '(A)') 'Key Bindings:' |
| 549 | 590 | write(output_unit, '(A)') ' Ctrl-Q Quit' |
src/commands/command_handler_module.f90modified@@ -6644,7 +6644,7 @@ contains | ||
| 6644 | 6644 | subroutine navigate_to_workspace_symbol(editor, buffer, symbol, should_quit) |
| 6645 | 6645 | use workspace_symbols_panel_module, only: workspace_symbol_t |
| 6646 | 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 | 6648 | use text_buffer_module, only: buffer_load_file, copy_buffer |
| 6649 | 6649 | type(editor_state_t), intent(inout) :: editor |
| 6650 | 6650 | type(buffer_t), intent(inout) :: buffer |
@@ -6669,27 +6669,55 @@ contains | ||
| 6669 | 6669 | editor%cursors(editor%active_cursor)%column) |
| 6670 | 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 | 6688 | do i = 1, size(editor%tabs) |
| 6689 | + if (i == editor%active_tab_index) cycle ! Skip active tab, already checked | |
| 6674 | 6690 | 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) | |
| 6677 | 6694 | ! Jump to the symbol's position |
| 6678 | 6695 | editor%cursors(editor%active_cursor)%line = symbol%line + 1 ! LSP is 0-based |
| 6679 | 6696 | editor%cursors(editor%active_cursor)%column = symbol%column + 1 |
| 6680 | 6697 | editor%cursors(editor%active_cursor)%desired_column = symbol%column + 1 |
| 6681 | 6698 | editor%viewport_line = max(1, symbol%line + 1 - editor%screen_rows / 2) |
| 6699 | + call sync_editor_to_pane(editor) | |
| 6682 | 6700 | return |
| 6683 | 6701 | end if |
| 6684 | 6702 | end if |
| 6685 | 6703 | end do |
| 6686 | 6704 | |
| 6687 | 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 | 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 | 6722 | new_tab_idx = size(editor%tabs) ! The tab we just created |
| 6695 | 6723 | |
@@ -6702,8 +6730,14 @@ contains | ||
| 6702 | 6730 | call copy_buffer(editor%tabs(new_tab_idx)%panes(1)%buffer, editor%tabs(new_tab_idx)%buffer) |
| 6703 | 6731 | end if |
| 6704 | 6732 | |
| 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 | |
| 6707 | 6741 | |
| 6708 | 6742 | ! Sync the pane to editor state (this updates editor%cursors, etc.) |
| 6709 | 6743 | call sync_pane_to_editor(editor, new_tab_idx, 1) |
@@ -6724,6 +6758,67 @@ contains | ||
| 6724 | 6758 | end block |
| 6725 | 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 | 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 | 10 | public :: lsp_server_t |
| 11 | 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 | 13 | public :: get_or_start_server, stop_server |
| 14 | 14 | public :: send_request, send_notification |
| 15 | 15 | public :: process_server_messages |
@@ -121,6 +121,7 @@ module lsp_server_manager_module | ||
| 121 | 121 | type(callback_entry_t), allocatable :: callbacks(:) |
| 122 | 122 | integer :: num_callbacks = 0 |
| 123 | 123 | procedure(diagnostics_callback), pointer, nopass :: diagnostics_handler => null() |
| 124 | + character(len=512) :: workspace_root = '.' ! LSP workspace root (cwd by default) | |
| 124 | 125 | end type lsp_manager_t |
| 125 | 126 | |
| 126 | 127 | ! C interfaces |
@@ -168,17 +169,40 @@ module lsp_server_manager_module | ||
| 168 | 169 | |
| 169 | 170 | contains |
| 170 | 171 | |
| 171 | - subroutine init_lsp_manager(manager) | |
| 172 | + subroutine init_lsp_manager(manager, workspace_root) | |
| 172 | 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 | 178 | allocate(manager%servers(0)) |
| 175 | 179 | allocate(manager%configs(0)) |
| 176 | 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 | 194 | ! Load default server configurations |
| 179 | 195 | call load_default_configs(manager) |
| 180 | 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 | 206 | subroutine cleanup_lsp_manager(manager) |
| 183 | 207 | type(lsp_manager_t), intent(inout) :: manager |
| 184 | 208 | integer :: i |
@@ -861,8 +885,6 @@ contains | ||
| 861 | 885 | character(len=*), intent(in) :: filename |
| 862 | 886 | integer :: server_index |
| 863 | 887 | character(len=:), allocatable :: language |
| 864 | - character(len=256) :: workspace_path | |
| 865 | - integer :: slash_pos | |
| 866 | 888 | |
| 867 | 889 | server_index = 0 |
| 868 | 890 | |
@@ -870,16 +892,8 @@ contains | ||
| 870 | 892 | language = get_language_for_file(filename) |
| 871 | 893 | if (language == "") return |
| 872 | 894 | |
| 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)) | |
| 883 | 897 | end function start_lsp_for_file |
| 884 | 898 | |
| 885 | 899 | ! Start ALL LSP servers that match a file (multi-server support) |
@@ -889,8 +903,7 @@ contains | ||
| 889 | 903 | integer, allocatable, intent(out) :: server_indices(:) |
| 890 | 904 | integer, intent(out) :: num_servers |
| 891 | 905 | character(len=:), allocatable :: language |
| 892 | - character(len=256) :: workspace_path | |
| 893 | - integer :: slash_pos, i, idx | |
| 906 | + integer :: i, idx | |
| 894 | 907 | integer :: temp_indices(20) ! Max 20 servers per file |
| 895 | 908 | |
| 896 | 909 | num_servers = 0 |
@@ -900,19 +913,12 @@ contains | ||
| 900 | 913 | language = get_language_for_file(filename) |
| 901 | 914 | if (language == "") return |
| 902 | 915 | |
| 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) | |
| 911 | 917 | ! Find ALL configs that match this language and start servers |
| 912 | 918 | do i = 1, manager%num_configs |
| 913 | 919 | if (manager%configs(i)%language == language) then |
| 914 | 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 | 922 | if (idx > 0 .and. num_servers < 20) then |
| 917 | 923 | num_servers = num_servers + 1 |
| 918 | 924 | temp_indices(num_servers) = idx |