Fortress Integration Plan
Version: 1.0 Last Updated: 2025-01-05
Overview
This document details how to integrate the Fortress file navigator into fac for workspace and file navigation. Fortress is a Fortran-based dual-pane file explorer located at ../fortress/.
Goal: Embed Fortress functionality into fac as a modal UI component triggered by Ctrl-O.
Approach: Copy and adapt Fortress modules rather than shelling out to external process.
Rationale:
- Single binary (no external dependencies)
- Full control over UI integration
- Shared terminal state management
- Better performance (no process spawning)
Fortress Repository Structure
../fortress/
├── app/
│ ├── main.f90 # Main program (don't copy)
│ ├── filesystem/
│ │ ├── directory_module.f90 # ✅ Directory operations
│ │ └── path_utils_module.f90 # ✅ Path manipulation
│ ├── terminal/
│ │ ├── terminal_module.f90 # ⚠️ Adapt for fac's terminal
│ │ └── input_module.f90 # ⚠️ May need adaptation
│ └── ui/
│ ├── dual_pane_module.f90 # ✅ Dual-pane rendering
│ ├── navigation_module.f90 # ✅ Navigation logic
│ └── selection_module.f90 # ✅ File/dir selection
├── src/
│ └── (library code if any)
└── fpm.toml
Modules to Copy
Phase 1: Core Navigation (Minimum Viable)
1. filesystem/directory_module.f90
Purpose: Directory listing, stat operations, file type detection
Key Functions:
list_directory(path, entries, count)- Get directory contentsis_directory(path)- Check if path is directoryis_file(path)- Check if path is regular fileget_parent_directory(path)- Navigate upresolve_path(path)- Canonicalize pathspath_exists(path)- Check existence
Changes Needed: None (should be portable)
Dependencies: Standard Fortran, possibly POSIX C bindings
Copy To: src/fortress/filesystem/directory_module.f90
2. filesystem/path_utils_module.f90
Purpose: Path string manipulation
Key Functions:
join_paths(base, relative)- Combine pathsbasename(path)- Extract filenamedirname(path)- Extract directorynormalize_path(path)- Remove.and..expand_tilde(path)- Expand~to home directory
Changes Needed: Verify home directory detection works with fac's environment
Dependencies: None (pure Fortran string manipulation)
Copy To: src/fortress/filesystem/path_utils_module.f90
3. ui/dual_pane_module.f90
Purpose: Render dual-pane display (parent 30% | current 70%)
Key Functions:
render_dual_pane(parent_entries, current_entries, selected_index, width, height)format_entry(entry, is_selected, is_directory)- Format single linecalculate_layout(total_width)- Determine pane widths
Changes Needed:
- Use fac's
terminal_io_modulefor output instead of Fortress's - Adapt to fac's color scheme constants
- Ensure coordinate system matches fac's (1-based)
Dependencies:
directory_module(for entry types)- fac's
terminal_io_module(for output)
Copy To: src/fortress/ui/dual_pane_module.f90
4. ui/navigation_module.f90
Purpose: Handle navigation logic and key input
Key Functions:
navigate_up()- Move cursor upnavigate_down()- Move cursor downnavigate_into()- Enter directory (→ or Enter)navigate_back()- Go to parent (←)jump_to_home()- Navigate to home (~)jump_to_root()- Navigate to root (/)
Changes Needed:
- Use fac's
input_handler_modulefor key input - Adapt key codes to match fac's constants
- Integrate with fac's event loop
Dependencies:
directory_module(for directory operations)- fac's
input_handler_module(for key input)
Copy To: src/fortress/ui/navigation_module.f90
5. ui/selection_module.f90
Purpose: Handle selection and return value
Key Functions:
select_current()- Return selected pathcancel_selection()- Return empty (ESC pressed)get_selection_type()- Determine if file or directory
Changes Needed: None (pure logic)
Dependencies:
directory_module(for type checking)
Copy To: src/fortress/ui/selection_module.f90
Phase 1: Exclusions (For Now)
These Fortress features will NOT be included in Phase 1:
Git Integration
git_ops_module.f90- Git status indicators- Rationale: fac's fuss menu already has git integration; fortress doesn't need it
- Future: Maybe add git status to fortress in Phase 7
Fuzzy Search (fzf)
- Any fzf integration code
- Rationale: Adds complexity; not critical for MVP
- Future: Phase 7 polish
Multiselect
- Multiselect/bulk operations
- Rationale: Not needed for workspace switching
- Future: Maybe later for opening multiple files
Bookmarks (if separate from favorites)
- Fortress-specific bookmarks
- Rationale: fac has its own favorites system
- Future: Integrate with fac's favorites.json
Integration Architecture
New Module: navigator_module.f90
This is the main integration point that fac will call.
Location: src/fortress/navigator_module.f90
Purpose: Provide high-level API for fac to invoke fortress navigation
API:
module navigator_module
use directory_module
use dual_pane_module
use navigation_module
use selection_module
implicit none
contains
! Main entry point for Ctrl-O
subroutine open_fortress_navigator(selected_path, selection_type, cancelled, &
initial_path)
character(len=:), allocatable, intent(out) :: selected_path
character(len=*), intent(out) :: selection_type ! 'file' or 'directory'
logical, intent(out) :: cancelled
character(len=*), intent(in), optional :: initial_path
! Implementation:
! 1. Save current terminal state
! 2. Initialize fortress UI
! 3. Enter navigation loop
! 4. On selection: populate selected_path and selection_type
! 5. On ESC: set cancelled = .true.
! 6. Restore terminal state
! 7. Return to fac
end subroutine
! Entry point for welcome menu (favorites/recents) - Phase 5
subroutine open_fortress_welcome(selected_path, selection_type, cancelled)
character(len=:), allocatable, intent(out) :: selected_path
character(len=*), intent(out) :: selection_type
logical, intent(out) :: cancelled
! Implementation for Phase 5
end subroutine
end module
Terminal State Management
Challenge
fac already manages terminal state (raw mode, cursor, alternate screen). Fortress needs to work within this context.
Solution: Shared Terminal Module
Strategy: Make fortress use fac's existing terminal infrastructure.
fac's Terminal Modules:
src/terminal/raw_mode_module.f90- Raw mode managementsrc/terminal/terminal_io_module.f90- Output (colors, cursor, clear)src/terminal/input_handler_module.f90- Input (key codes, escape sequences)
Fortress Adaptation:
- Replace fortress's terminal output calls with fac's
terminal_io_module - Use fac's key code constants
- No need to enter/exit raw mode (already in raw mode)
- Save/restore cursor position before/after fortress UI
Example Adaptation:
! Fortress original:
call fortress_terminal_write_at(row, col, text)
! Adapted for fac:
call terminal_move_cursor(row, col)
call terminal_write(text)
Key Input Adaptation
Fortress Key Codes → fac Key Codes
Map fortress input handling to fac's constants:
| Key | Fortress Code | fac Code | Notes |
|---|---|---|---|
| ↑ | KEY_UP |
Check input_handler_module |
Arrow keys |
| ↓ | KEY_DOWN |
Check input_handler_module |
Arrow keys |
| → | KEY_RIGHT |
Check input_handler_module |
Arrow keys |
| ← | KEY_LEFT |
Check input_handler_module |
Arrow keys |
| Enter | CHAR_NEWLINE |
char(10) or CHAR_NEWLINE |
Standard |
| ESC | CHAR_ESC |
char(27) |
Standard |
| q | 'q' |
'q' |
Standard |
| ~ | '~' |
'~' |
Jump to home |
| / | '/' |
'/' |
Jump to root |
| 8 | '8' |
'8' |
Toggle favorites/recents (Phase 5) |
| f | 'f' |
'f' |
Add to favorites (Phase 5) |
Action: During copy, replace fortress key constants with fac equivalents.
Color Scheme Integration
Fortress Colors
Fortress likely has its own color definitions (e.g., selected = cyan, directory = blue).
fac Colors
fac has colors defined in terminal_io_module.f90:
- Cursor line highlight
- Selection highlight
- Status bar colors
- Tab colors (normal vs orphan)
Strategy
- Phase 1: Use fortress's original color scheme (minimal changes)
- Phase 7: Unify with fac's color palette for consistency
Note: Ensure colors are defined as named constants, not hardcoded ANSI codes.
Directory Structure After Integration
src/
├── fortress/
│ ├── filesystem/
│ │ ├── directory_module.f90
│ │ └── path_utils_module.f90
│ └── ui/
│ ├── dual_pane_module.f90
│ ├── navigation_module.f90
│ ├── selection_module.f90
│ └── navigator_module.f90 # NEW: Integration layer
├── terminal/
│ ├── raw_mode_module.f90
│ ├── terminal_io_module.f90
│ └── input_handler_module.f90
├── commands/
│ └── command_handler_module.f90 # Add Ctrl-O handler here
├── editor_state_module.f90
└── ...
Build Order (Makefile)
Module dependencies determine build order:
# Fortress modules (no dependencies)
src/fortress/filesystem/path_utils_module.f90
# Fortress modules (depends on path_utils)
src/fortress/filesystem/directory_module.f90
# Fortress UI modules (depends on filesystem + fac terminal)
src/fortress/ui/dual_pane_module.f90
src/fortress/ui/navigation_module.f90
src/fortress/ui/selection_module.f90
# Navigator integration (depends on all fortress modules)
src/fortress/navigator_module.f90
# Command handler (depends on navigator)
src/commands/command_handler_module.f90
# Main (depends on everything)
app/main.f90
Action: Add these to SOURCES in Makefile in correct order.
Integration Points in fac
1. Command Handler (Ctrl-O)
File: src/commands/command_handler_module.f90
Add:
use navigator_module
! In handle_key function:
else if (key_input == CTRL_O) then
call handle_fortress_navigator(editor, buffer)
end if
subroutine handle_fortress_navigator(editor, buffer)
type(editor_state_t), intent(inout) :: editor
type(text_buffer_t), intent(inout) :: buffer
character(len=:), allocatable :: selected_path
character(len=256) :: selection_type
logical :: cancelled
! Get current directory as starting point
! (from workspace path or buffer's file directory)
call open_fortress_navigator(selected_path, selection_type, cancelled)
if (.not. cancelled) then
if (trim(selection_type) == 'directory') then
! Switch to that workspace
call switch_workspace(editor, selected_path)
else if (trim(selection_type) == 'file') then
! Open as orphan tab
call open_orphan_tab(editor, selected_path)
end if
end if
! Re-render main UI
call render_screen(editor, buffer)
end subroutine
2. Workspace Switching (Phase 2)
File: src/workspace/workspace_module.f90 (to be created)
API:
subroutine switch_workspace(editor, new_workspace_path)
type(editor_state_t), intent(inout) :: editor
character(len=*), intent(in) :: new_workspace_path
! 1. Prompt to save current workspace dirty buffers
! 2. Save current workspace state
! 3. Load new workspace state
! 4. Restore tabs/panes/positions
end subroutine
3. Orphan Tab Creation (Phase 3)
File: src/tabs/tab_manager_module.f90 (existing)
Add:
subroutine create_orphan_tab(editor, file_path)
type(editor_state_t), intent(inout) :: editor
character(len=*), intent(in) :: file_path
! 1. Create new tab
! 2. Mark as orphan (orphan flag)
! 3. Load file
! 4. Set tab label to basename
! 5. Set tab color to greyish
end subroutine
State Management
Entering Fortress Navigator
Before showing fortress UI:
- Save current cursor position:
call terminal_get_cursor(saved_row, saved_col) - Clear screen or enter alternate screen:
call terminal_clear_screen() - Hide fac's status bar
- Initialize fortress state (current directory, selection index)
Exiting Fortress Navigator
After user selection or cancellation:
- Clear fortress UI
- Restore fac's screen:
call render_screen(editor, buffer) - Restore cursor position (if needed)
- Return control to main loop
No Need To:
- Enter/exit raw mode (already in raw mode)
- Change terminal settings (fac already configured)
Error Handling
Directory Access Errors
- Permission denied: Show error message in fortress UI, stay in current directory
- Directory deleted: Fall back to parent or home directory
File Open Errors
- Permission denied: Show error in fac status bar, don't create tab
- File too large: Prompt user to confirm before loading
Path Resolution Errors
- Symlink loops: Detect and break, show error
- Invalid paths: Fallback to current directory
Testing Strategy
Unit Tests (Per Module)
directory_module:
- List directory with various paths (., .., ~, /)
- Handle nonexistent directories
- Detect file types correctly
- Resolve paths correctly
navigation_module:
- Navigate up/down with boundaries
- Enter directories
- Go to parent
- Jump to home/root
dual_pane_module:
- Render with various terminal widths
- Handle empty directories
- Handle directories with many entries (scrolling)
- Format entries correctly (colors, icons)
Integration Tests
Test 1: Basic Navigation
./fac test.txt
# Press Ctrl-O
# Should see dual-pane navigator
# Navigate with arrows
# Press ESC
# Should return to editor
Test 2: Directory Selection
./fac test.txt
# Press Ctrl-O
# Navigate to a directory
# Press Enter on directory
# Should switch to workspace mode (Phase 2)
Test 3: File Selection
./fac test.txt # In workspace mode
# Press Ctrl-O
# Navigate to file outside workspace
# Press Enter on file
# Should open as orphan tab
Test 4: Edge Cases
- Navigate to root (/)
- Navigate to home (~)
- Navigate to nonexistent directory (should handle gracefully)
- Cancel with ESC (should return to editor unchanged)
Performance Considerations
Directory Listing
- Large directories: Limit display to visible entries + buffer (e.g., 1000 entries max)
- Lazy loading: Only stat entries when needed (not all 10k files upfront)
Rendering
- Only redraw changed panes: Track dirty state
- Batch terminal output: Use single write call per frame
Path Operations
- Cache directory listings: Don't re-scan on every cursor move
- Invalidate cache: Only on directory change or explicit refresh
Phase 1 Completion Criteria
Fortress integration is complete for Phase 1 when:
- ✅ All fortress modules copied and adapted
- ✅
navigator_module.f90created with clean API - ✅ Ctrl-O opens dual-pane navigator
- ✅ Can navigate filesystem with arrow keys
- ✅ Can enter directories (→ or Enter)
- ✅ Can go to parent (←)
- ✅ Can jump to home (~) and root (/)
- ✅ Can cancel (ESC) and return to editor
- ✅ Selecting directory returns path correctly
- ✅ Selecting file returns path correctly
- ✅ No regressions in fac's existing features
- ✅ Build system updated (Makefile)
- ✅ Basic tests pass
Not Required for Phase 1:
- Favorites/recents (Phase 5)
- Git integration
- Fuzzy search
- Multiselect
- Workspace switching (that's Phase 2)
- Orphan tab creation (that's Phase 3)
Debugging Tips
Terminal State Debugging
- Use
/tmp/fortress_debug.txtfor logging (not stdout/stderr) - Log cursor positions, key inputs, navigation state
Visual Debugging
- Add visual markers for debugging (e.g., border characters)
- Temporarily show state in header line
Common Issues
- Cursor disappears: Check if fortress is hiding cursor (should use fac's cursor management)
- Colors wrong: Verify ANSI codes match expectations
- Keys not working: Check key code constants match
- Rendering artifacts: Ensure screen clearing before redraw
Future Enhancements (Post-Phase 1)
Phase 5: Favorites & Recents
- Add
favorites_module.f90to load/save favorites - Integrate with navigator_module
- Add keybindings ('8' toggle, 'f' add favorite)
Phase 7: Polish
- Add git status indicators (use fuss menu's git module)
- Add fuzzy search (optional fzf integration)
- Unify color scheme with fac
- Performance optimization
- Add icons for file types (if UTF-8 safe)
Related Documents
WORKSPACE_VISION.md- Overall design visionworkspace_spec.md- JSON schema for workspace.jsonconfig_spec.md- favorites.json and recents.json formats (next doc)WORKSPACE_ROADMAP.md- Implementation phases
End of Integration Plan
View source
| 1 | # Fortress Integration Plan |
| 2 | |
| 3 | **Version**: 1.0 |
| 4 | **Last Updated**: 2025-01-05 |
| 5 | |
| 6 | --- |
| 7 | |
| 8 | ## Overview |
| 9 | |
| 10 | This document details how to integrate the Fortress file navigator into fac for workspace and file navigation. Fortress is a Fortran-based dual-pane file explorer located at `../fortress/`. |
| 11 | |
| 12 | **Goal**: Embed Fortress functionality into fac as a modal UI component triggered by Ctrl-O. |
| 13 | |
| 14 | **Approach**: Copy and adapt Fortress modules rather than shelling out to external process. |
| 15 | |
| 16 | **Rationale**: |
| 17 | - Single binary (no external dependencies) |
| 18 | - Full control over UI integration |
| 19 | - Shared terminal state management |
| 20 | - Better performance (no process spawning) |
| 21 | |
| 22 | --- |
| 23 | |
| 24 | ## Fortress Repository Structure |
| 25 | |
| 26 | ``` |
| 27 | ../fortress/ |
| 28 | ├── app/ |
| 29 | │ ├── main.f90 # Main program (don't copy) |
| 30 | │ ├── filesystem/ |
| 31 | │ │ ├── directory_module.f90 # ✅ Directory operations |
| 32 | │ │ └── path_utils_module.f90 # ✅ Path manipulation |
| 33 | │ ├── terminal/ |
| 34 | │ │ ├── terminal_module.f90 # ⚠️ Adapt for fac's terminal |
| 35 | │ │ └── input_module.f90 # ⚠️ May need adaptation |
| 36 | │ └── ui/ |
| 37 | │ ├── dual_pane_module.f90 # ✅ Dual-pane rendering |
| 38 | │ ├── navigation_module.f90 # ✅ Navigation logic |
| 39 | │ └── selection_module.f90 # ✅ File/dir selection |
| 40 | ├── src/ |
| 41 | │ └── (library code if any) |
| 42 | └── fpm.toml |
| 43 | ``` |
| 44 | |
| 45 | --- |
| 46 | |
| 47 | ## Modules to Copy |
| 48 | |
| 49 | ### Phase 1: Core Navigation (Minimum Viable) |
| 50 | |
| 51 | #### 1. `filesystem/directory_module.f90` |
| 52 | **Purpose**: Directory listing, stat operations, file type detection |
| 53 | |
| 54 | **Key Functions**: |
| 55 | - `list_directory(path, entries, count)` - Get directory contents |
| 56 | - `is_directory(path)` - Check if path is directory |
| 57 | - `is_file(path)` - Check if path is regular file |
| 58 | - `get_parent_directory(path)` - Navigate up |
| 59 | - `resolve_path(path)` - Canonicalize paths |
| 60 | - `path_exists(path)` - Check existence |
| 61 | |
| 62 | **Changes Needed**: None (should be portable) |
| 63 | |
| 64 | **Dependencies**: Standard Fortran, possibly POSIX C bindings |
| 65 | |
| 66 | **Copy To**: `src/fortress/filesystem/directory_module.f90` |
| 67 | |
| 68 | --- |
| 69 | |
| 70 | #### 2. `filesystem/path_utils_module.f90` |
| 71 | **Purpose**: Path string manipulation |
| 72 | |
| 73 | **Key Functions**: |
| 74 | - `join_paths(base, relative)` - Combine paths |
| 75 | - `basename(path)` - Extract filename |
| 76 | - `dirname(path)` - Extract directory |
| 77 | - `normalize_path(path)` - Remove `.` and `..` |
| 78 | - `expand_tilde(path)` - Expand `~` to home directory |
| 79 | |
| 80 | **Changes Needed**: Verify home directory detection works with fac's environment |
| 81 | |
| 82 | **Dependencies**: None (pure Fortran string manipulation) |
| 83 | |
| 84 | **Copy To**: `src/fortress/filesystem/path_utils_module.f90` |
| 85 | |
| 86 | --- |
| 87 | |
| 88 | #### 3. `ui/dual_pane_module.f90` |
| 89 | **Purpose**: Render dual-pane display (parent 30% | current 70%) |
| 90 | |
| 91 | **Key Functions**: |
| 92 | - `render_dual_pane(parent_entries, current_entries, selected_index, width, height)` |
| 93 | - `format_entry(entry, is_selected, is_directory)` - Format single line |
| 94 | - `calculate_layout(total_width)` - Determine pane widths |
| 95 | |
| 96 | **Changes Needed**: |
| 97 | - Use fac's `terminal_io_module` for output instead of Fortress's |
| 98 | - Adapt to fac's color scheme constants |
| 99 | - Ensure coordinate system matches fac's (1-based) |
| 100 | |
| 101 | **Dependencies**: |
| 102 | - `directory_module` (for entry types) |
| 103 | - fac's `terminal_io_module` (for output) |
| 104 | |
| 105 | **Copy To**: `src/fortress/ui/dual_pane_module.f90` |
| 106 | |
| 107 | --- |
| 108 | |
| 109 | #### 4. `ui/navigation_module.f90` |
| 110 | **Purpose**: Handle navigation logic and key input |
| 111 | |
| 112 | **Key Functions**: |
| 113 | - `navigate_up()` - Move cursor up |
| 114 | - `navigate_down()` - Move cursor down |
| 115 | - `navigate_into()` - Enter directory (→ or Enter) |
| 116 | - `navigate_back()` - Go to parent (←) |
| 117 | - `jump_to_home()` - Navigate to home (~) |
| 118 | - `jump_to_root()` - Navigate to root (/) |
| 119 | |
| 120 | **Changes Needed**: |
| 121 | - Use fac's `input_handler_module` for key input |
| 122 | - Adapt key codes to match fac's constants |
| 123 | - Integrate with fac's event loop |
| 124 | |
| 125 | **Dependencies**: |
| 126 | - `directory_module` (for directory operations) |
| 127 | - fac's `input_handler_module` (for key input) |
| 128 | |
| 129 | **Copy To**: `src/fortress/ui/navigation_module.f90` |
| 130 | |
| 131 | --- |
| 132 | |
| 133 | #### 5. `ui/selection_module.f90` |
| 134 | **Purpose**: Handle selection and return value |
| 135 | |
| 136 | **Key Functions**: |
| 137 | - `select_current()` - Return selected path |
| 138 | - `cancel_selection()` - Return empty (ESC pressed) |
| 139 | - `get_selection_type()` - Determine if file or directory |
| 140 | |
| 141 | **Changes Needed**: None (pure logic) |
| 142 | |
| 143 | **Dependencies**: |
| 144 | - `directory_module` (for type checking) |
| 145 | |
| 146 | **Copy To**: `src/fortress/ui/selection_module.f90` |
| 147 | |
| 148 | --- |
| 149 | |
| 150 | ### Phase 1: Exclusions (For Now) |
| 151 | |
| 152 | These Fortress features will NOT be included in Phase 1: |
| 153 | |
| 154 | #### Git Integration |
| 155 | - `git_ops_module.f90` - Git status indicators |
| 156 | - **Rationale**: fac's fuss menu already has git integration; fortress doesn't need it |
| 157 | - **Future**: Maybe add git status to fortress in Phase 7 |
| 158 | |
| 159 | #### Fuzzy Search (fzf) |
| 160 | - Any fzf integration code |
| 161 | - **Rationale**: Adds complexity; not critical for MVP |
| 162 | - **Future**: Phase 7 polish |
| 163 | |
| 164 | #### Multiselect |
| 165 | - Multiselect/bulk operations |
| 166 | - **Rationale**: Not needed for workspace switching |
| 167 | - **Future**: Maybe later for opening multiple files |
| 168 | |
| 169 | #### Bookmarks (if separate from favorites) |
| 170 | - Fortress-specific bookmarks |
| 171 | - **Rationale**: fac has its own favorites system |
| 172 | - **Future**: Integrate with fac's favorites.json |
| 173 | |
| 174 | --- |
| 175 | |
| 176 | ## Integration Architecture |
| 177 | |
| 178 | ### New Module: `navigator_module.f90` |
| 179 | |
| 180 | This is the main integration point that fac will call. |
| 181 | |
| 182 | **Location**: `src/fortress/navigator_module.f90` |
| 183 | |
| 184 | **Purpose**: Provide high-level API for fac to invoke fortress navigation |
| 185 | |
| 186 | **API**: |
| 187 | ```fortran |
| 188 | module navigator_module |
| 189 | use directory_module |
| 190 | use dual_pane_module |
| 191 | use navigation_module |
| 192 | use selection_module |
| 193 | implicit none |
| 194 | |
| 195 | contains |
| 196 | ! Main entry point for Ctrl-O |
| 197 | subroutine open_fortress_navigator(selected_path, selection_type, cancelled, & |
| 198 | initial_path) |
| 199 | character(len=:), allocatable, intent(out) :: selected_path |
| 200 | character(len=*), intent(out) :: selection_type ! 'file' or 'directory' |
| 201 | logical, intent(out) :: cancelled |
| 202 | character(len=*), intent(in), optional :: initial_path |
| 203 | |
| 204 | ! Implementation: |
| 205 | ! 1. Save current terminal state |
| 206 | ! 2. Initialize fortress UI |
| 207 | ! 3. Enter navigation loop |
| 208 | ! 4. On selection: populate selected_path and selection_type |
| 209 | ! 5. On ESC: set cancelled = .true. |
| 210 | ! 6. Restore terminal state |
| 211 | ! 7. Return to fac |
| 212 | end subroutine |
| 213 | |
| 214 | ! Entry point for welcome menu (favorites/recents) - Phase 5 |
| 215 | subroutine open_fortress_welcome(selected_path, selection_type, cancelled) |
| 216 | character(len=:), allocatable, intent(out) :: selected_path |
| 217 | character(len=*), intent(out) :: selection_type |
| 218 | logical, intent(out) :: cancelled |
| 219 | |
| 220 | ! Implementation for Phase 5 |
| 221 | end subroutine |
| 222 | end module |
| 223 | ``` |
| 224 | |
| 225 | --- |
| 226 | |
| 227 | ## Terminal State Management |
| 228 | |
| 229 | ### Challenge |
| 230 | fac already manages terminal state (raw mode, cursor, alternate screen). Fortress needs to work within this context. |
| 231 | |
| 232 | ### Solution: Shared Terminal Module |
| 233 | |
| 234 | **Strategy**: Make fortress use fac's existing terminal infrastructure. |
| 235 | |
| 236 | **fac's Terminal Modules**: |
| 237 | - `src/terminal/raw_mode_module.f90` - Raw mode management |
| 238 | - `src/terminal/terminal_io_module.f90` - Output (colors, cursor, clear) |
| 239 | - `src/terminal/input_handler_module.f90` - Input (key codes, escape sequences) |
| 240 | |
| 241 | **Fortress Adaptation**: |
| 242 | 1. Replace fortress's terminal output calls with fac's `terminal_io_module` |
| 243 | 2. Use fac's key code constants |
| 244 | 3. No need to enter/exit raw mode (already in raw mode) |
| 245 | 4. Save/restore cursor position before/after fortress UI |
| 246 | |
| 247 | **Example Adaptation**: |
| 248 | ```fortran |
| 249 | ! Fortress original: |
| 250 | call fortress_terminal_write_at(row, col, text) |
| 251 | |
| 252 | ! Adapted for fac: |
| 253 | call terminal_move_cursor(row, col) |
| 254 | call terminal_write(text) |
| 255 | ``` |
| 256 | |
| 257 | --- |
| 258 | |
| 259 | ## Key Input Adaptation |
| 260 | |
| 261 | ### Fortress Key Codes → fac Key Codes |
| 262 | |
| 263 | Map fortress input handling to fac's constants: |
| 264 | |
| 265 | | Key | Fortress Code | fac Code | Notes | |
| 266 | |-----|---------------|----------|-------| |
| 267 | | ↑ | `KEY_UP` | Check `input_handler_module` | Arrow keys | |
| 268 | | ↓ | `KEY_DOWN` | Check `input_handler_module` | Arrow keys | |
| 269 | | → | `KEY_RIGHT` | Check `input_handler_module` | Arrow keys | |
| 270 | | ← | `KEY_LEFT` | Check `input_handler_module` | Arrow keys | |
| 271 | | Enter | `CHAR_NEWLINE` | `char(10)` or `CHAR_NEWLINE` | Standard | |
| 272 | | ESC | `CHAR_ESC` | `char(27)` | Standard | |
| 273 | | q | `'q'` | `'q'` | Standard | |
| 274 | | ~ | `'~'` | `'~'` | Jump to home | |
| 275 | | / | `'/'` | `'/'` | Jump to root | |
| 276 | | 8 | `'8'` | `'8'` | Toggle favorites/recents (Phase 5) | |
| 277 | | f | `'f'` | `'f'` | Add to favorites (Phase 5) | |
| 278 | |
| 279 | **Action**: During copy, replace fortress key constants with fac equivalents. |
| 280 | |
| 281 | --- |
| 282 | |
| 283 | ## Color Scheme Integration |
| 284 | |
| 285 | ### Fortress Colors |
| 286 | Fortress likely has its own color definitions (e.g., selected = cyan, directory = blue). |
| 287 | |
| 288 | ### fac Colors |
| 289 | fac has colors defined in `terminal_io_module.f90`: |
| 290 | - Cursor line highlight |
| 291 | - Selection highlight |
| 292 | - Status bar colors |
| 293 | - Tab colors (normal vs orphan) |
| 294 | |
| 295 | ### Strategy |
| 296 | 1. **Phase 1**: Use fortress's original color scheme (minimal changes) |
| 297 | 2. **Phase 7**: Unify with fac's color palette for consistency |
| 298 | |
| 299 | **Note**: Ensure colors are defined as named constants, not hardcoded ANSI codes. |
| 300 | |
| 301 | --- |
| 302 | |
| 303 | ## Directory Structure After Integration |
| 304 | |
| 305 | ``` |
| 306 | src/ |
| 307 | ├── fortress/ |
| 308 | │ ├── filesystem/ |
| 309 | │ │ ├── directory_module.f90 |
| 310 | │ │ └── path_utils_module.f90 |
| 311 | │ └── ui/ |
| 312 | │ ├── dual_pane_module.f90 |
| 313 | │ ├── navigation_module.f90 |
| 314 | │ ├── selection_module.f90 |
| 315 | │ └── navigator_module.f90 # NEW: Integration layer |
| 316 | ├── terminal/ |
| 317 | │ ├── raw_mode_module.f90 |
| 318 | │ ├── terminal_io_module.f90 |
| 319 | │ └── input_handler_module.f90 |
| 320 | ├── commands/ |
| 321 | │ └── command_handler_module.f90 # Add Ctrl-O handler here |
| 322 | ├── editor_state_module.f90 |
| 323 | └── ... |
| 324 | ``` |
| 325 | |
| 326 | --- |
| 327 | |
| 328 | ## Build Order (Makefile) |
| 329 | |
| 330 | Module dependencies determine build order: |
| 331 | |
| 332 | ```makefile |
| 333 | # Fortress modules (no dependencies) |
| 334 | src/fortress/filesystem/path_utils_module.f90 |
| 335 | |
| 336 | # Fortress modules (depends on path_utils) |
| 337 | src/fortress/filesystem/directory_module.f90 |
| 338 | |
| 339 | # Fortress UI modules (depends on filesystem + fac terminal) |
| 340 | src/fortress/ui/dual_pane_module.f90 |
| 341 | src/fortress/ui/navigation_module.f90 |
| 342 | src/fortress/ui/selection_module.f90 |
| 343 | |
| 344 | # Navigator integration (depends on all fortress modules) |
| 345 | src/fortress/navigator_module.f90 |
| 346 | |
| 347 | # Command handler (depends on navigator) |
| 348 | src/commands/command_handler_module.f90 |
| 349 | |
| 350 | # Main (depends on everything) |
| 351 | app/main.f90 |
| 352 | ``` |
| 353 | |
| 354 | **Action**: Add these to `SOURCES` in Makefile in correct order. |
| 355 | |
| 356 | --- |
| 357 | |
| 358 | ## Integration Points in fac |
| 359 | |
| 360 | ### 1. Command Handler (Ctrl-O) |
| 361 | |
| 362 | **File**: `src/commands/command_handler_module.f90` |
| 363 | |
| 364 | **Add**: |
| 365 | ```fortran |
| 366 | use navigator_module |
| 367 | |
| 368 | ! In handle_key function: |
| 369 | else if (key_input == CTRL_O) then |
| 370 | call handle_fortress_navigator(editor, buffer) |
| 371 | end if |
| 372 | |
| 373 | subroutine handle_fortress_navigator(editor, buffer) |
| 374 | type(editor_state_t), intent(inout) :: editor |
| 375 | type(text_buffer_t), intent(inout) :: buffer |
| 376 | character(len=:), allocatable :: selected_path |
| 377 | character(len=256) :: selection_type |
| 378 | logical :: cancelled |
| 379 | |
| 380 | ! Get current directory as starting point |
| 381 | ! (from workspace path or buffer's file directory) |
| 382 | |
| 383 | call open_fortress_navigator(selected_path, selection_type, cancelled) |
| 384 | |
| 385 | if (.not. cancelled) then |
| 386 | if (trim(selection_type) == 'directory') then |
| 387 | ! Switch to that workspace |
| 388 | call switch_workspace(editor, selected_path) |
| 389 | else if (trim(selection_type) == 'file') then |
| 390 | ! Open as orphan tab |
| 391 | call open_orphan_tab(editor, selected_path) |
| 392 | end if |
| 393 | end if |
| 394 | |
| 395 | ! Re-render main UI |
| 396 | call render_screen(editor, buffer) |
| 397 | end subroutine |
| 398 | ``` |
| 399 | |
| 400 | ### 2. Workspace Switching (Phase 2) |
| 401 | |
| 402 | **File**: `src/workspace/workspace_module.f90` (to be created) |
| 403 | |
| 404 | **API**: |
| 405 | ```fortran |
| 406 | subroutine switch_workspace(editor, new_workspace_path) |
| 407 | type(editor_state_t), intent(inout) :: editor |
| 408 | character(len=*), intent(in) :: new_workspace_path |
| 409 | |
| 410 | ! 1. Prompt to save current workspace dirty buffers |
| 411 | ! 2. Save current workspace state |
| 412 | ! 3. Load new workspace state |
| 413 | ! 4. Restore tabs/panes/positions |
| 414 | end subroutine |
| 415 | ``` |
| 416 | |
| 417 | ### 3. Orphan Tab Creation (Phase 3) |
| 418 | |
| 419 | **File**: `src/tabs/tab_manager_module.f90` (existing) |
| 420 | |
| 421 | **Add**: |
| 422 | ```fortran |
| 423 | subroutine create_orphan_tab(editor, file_path) |
| 424 | type(editor_state_t), intent(inout) :: editor |
| 425 | character(len=*), intent(in) :: file_path |
| 426 | |
| 427 | ! 1. Create new tab |
| 428 | ! 2. Mark as orphan (orphan flag) |
| 429 | ! 3. Load file |
| 430 | ! 4. Set tab label to basename |
| 431 | ! 5. Set tab color to greyish |
| 432 | end subroutine |
| 433 | ``` |
| 434 | |
| 435 | --- |
| 436 | |
| 437 | ## State Management |
| 438 | |
| 439 | ### Entering Fortress Navigator |
| 440 | |
| 441 | **Before showing fortress UI**: |
| 442 | 1. Save current cursor position: `call terminal_get_cursor(saved_row, saved_col)` |
| 443 | 2. Clear screen or enter alternate screen: `call terminal_clear_screen()` |
| 444 | 3. Hide fac's status bar |
| 445 | 4. Initialize fortress state (current directory, selection index) |
| 446 | |
| 447 | ### Exiting Fortress Navigator |
| 448 | |
| 449 | **After user selection or cancellation**: |
| 450 | 1. Clear fortress UI |
| 451 | 2. Restore fac's screen: `call render_screen(editor, buffer)` |
| 452 | 3. Restore cursor position (if needed) |
| 453 | 4. Return control to main loop |
| 454 | |
| 455 | **No Need To**: |
| 456 | - Enter/exit raw mode (already in raw mode) |
| 457 | - Change terminal settings (fac already configured) |
| 458 | |
| 459 | --- |
| 460 | |
| 461 | ## Error Handling |
| 462 | |
| 463 | ### Directory Access Errors |
| 464 | - **Permission denied**: Show error message in fortress UI, stay in current directory |
| 465 | - **Directory deleted**: Fall back to parent or home directory |
| 466 | |
| 467 | ### File Open Errors |
| 468 | - **Permission denied**: Show error in fac status bar, don't create tab |
| 469 | - **File too large**: Prompt user to confirm before loading |
| 470 | |
| 471 | ### Path Resolution Errors |
| 472 | - **Symlink loops**: Detect and break, show error |
| 473 | - **Invalid paths**: Fallback to current directory |
| 474 | |
| 475 | --- |
| 476 | |
| 477 | ## Testing Strategy |
| 478 | |
| 479 | ### Unit Tests (Per Module) |
| 480 | |
| 481 | **directory_module**: |
| 482 | - List directory with various paths (., .., ~, /) |
| 483 | - Handle nonexistent directories |
| 484 | - Detect file types correctly |
| 485 | - Resolve paths correctly |
| 486 | |
| 487 | **navigation_module**: |
| 488 | - Navigate up/down with boundaries |
| 489 | - Enter directories |
| 490 | - Go to parent |
| 491 | - Jump to home/root |
| 492 | |
| 493 | **dual_pane_module**: |
| 494 | - Render with various terminal widths |
| 495 | - Handle empty directories |
| 496 | - Handle directories with many entries (scrolling) |
| 497 | - Format entries correctly (colors, icons) |
| 498 | |
| 499 | ### Integration Tests |
| 500 | |
| 501 | **Test 1: Basic Navigation** |
| 502 | ```bash |
| 503 | ./fac test.txt |
| 504 | # Press Ctrl-O |
| 505 | # Should see dual-pane navigator |
| 506 | # Navigate with arrows |
| 507 | # Press ESC |
| 508 | # Should return to editor |
| 509 | ``` |
| 510 | |
| 511 | **Test 2: Directory Selection** |
| 512 | ```bash |
| 513 | ./fac test.txt |
| 514 | # Press Ctrl-O |
| 515 | # Navigate to a directory |
| 516 | # Press Enter on directory |
| 517 | # Should switch to workspace mode (Phase 2) |
| 518 | ``` |
| 519 | |
| 520 | **Test 3: File Selection** |
| 521 | ```bash |
| 522 | ./fac test.txt # In workspace mode |
| 523 | # Press Ctrl-O |
| 524 | # Navigate to file outside workspace |
| 525 | # Press Enter on file |
| 526 | # Should open as orphan tab |
| 527 | ``` |
| 528 | |
| 529 | **Test 4: Edge Cases** |
| 530 | - Navigate to root (/) |
| 531 | - Navigate to home (~) |
| 532 | - Navigate to nonexistent directory (should handle gracefully) |
| 533 | - Cancel with ESC (should return to editor unchanged) |
| 534 | |
| 535 | --- |
| 536 | |
| 537 | ## Performance Considerations |
| 538 | |
| 539 | ### Directory Listing |
| 540 | - **Large directories**: Limit display to visible entries + buffer (e.g., 1000 entries max) |
| 541 | - **Lazy loading**: Only stat entries when needed (not all 10k files upfront) |
| 542 | |
| 543 | ### Rendering |
| 544 | - **Only redraw changed panes**: Track dirty state |
| 545 | - **Batch terminal output**: Use single write call per frame |
| 546 | |
| 547 | ### Path Operations |
| 548 | - **Cache directory listings**: Don't re-scan on every cursor move |
| 549 | - **Invalidate cache**: Only on directory change or explicit refresh |
| 550 | |
| 551 | --- |
| 552 | |
| 553 | ## Phase 1 Completion Criteria |
| 554 | |
| 555 | Fortress integration is complete for Phase 1 when: |
| 556 | |
| 557 | - ✅ All fortress modules copied and adapted |
| 558 | - ✅ `navigator_module.f90` created with clean API |
| 559 | - ✅ Ctrl-O opens dual-pane navigator |
| 560 | - ✅ Can navigate filesystem with arrow keys |
| 561 | - ✅ Can enter directories (→ or Enter) |
| 562 | - ✅ Can go to parent (←) |
| 563 | - ✅ Can jump to home (~) and root (/) |
| 564 | - ✅ Can cancel (ESC) and return to editor |
| 565 | - ✅ Selecting directory returns path correctly |
| 566 | - ✅ Selecting file returns path correctly |
| 567 | - ✅ No regressions in fac's existing features |
| 568 | - ✅ Build system updated (Makefile) |
| 569 | - ✅ Basic tests pass |
| 570 | |
| 571 | **Not Required for Phase 1**: |
| 572 | - Favorites/recents (Phase 5) |
| 573 | - Git integration |
| 574 | - Fuzzy search |
| 575 | - Multiselect |
| 576 | - Workspace switching (that's Phase 2) |
| 577 | - Orphan tab creation (that's Phase 3) |
| 578 | |
| 579 | --- |
| 580 | |
| 581 | ## Debugging Tips |
| 582 | |
| 583 | ### Terminal State Debugging |
| 584 | - Use `/tmp/fortress_debug.txt` for logging (not stdout/stderr) |
| 585 | - Log cursor positions, key inputs, navigation state |
| 586 | |
| 587 | ### Visual Debugging |
| 588 | - Add visual markers for debugging (e.g., border characters) |
| 589 | - Temporarily show state in header line |
| 590 | |
| 591 | ### Common Issues |
| 592 | - **Cursor disappears**: Check if fortress is hiding cursor (should use fac's cursor management) |
| 593 | - **Colors wrong**: Verify ANSI codes match expectations |
| 594 | - **Keys not working**: Check key code constants match |
| 595 | - **Rendering artifacts**: Ensure screen clearing before redraw |
| 596 | |
| 597 | --- |
| 598 | |
| 599 | ## Future Enhancements (Post-Phase 1) |
| 600 | |
| 601 | ### Phase 5: Favorites & Recents |
| 602 | - Add `favorites_module.f90` to load/save favorites |
| 603 | - Integrate with navigator_module |
| 604 | - Add keybindings ('8' toggle, 'f' add favorite) |
| 605 | |
| 606 | ### Phase 7: Polish |
| 607 | - Add git status indicators (use fuss menu's git module) |
| 608 | - Add fuzzy search (optional fzf integration) |
| 609 | - Unify color scheme with fac |
| 610 | - Performance optimization |
| 611 | - Add icons for file types (if UTF-8 safe) |
| 612 | |
| 613 | --- |
| 614 | |
| 615 | ## Related Documents |
| 616 | |
| 617 | - `WORKSPACE_VISION.md` - Overall design vision |
| 618 | - `workspace_spec.md` - JSON schema for workspace.json |
| 619 | - `config_spec.md` - favorites.json and recents.json formats (next doc) |
| 620 | - `WORKSPACE_ROADMAP.md` - Implementation phases |
| 621 | |
| 622 | --- |
| 623 | |
| 624 | **End of Integration Plan** |