Terminal file explorer written in modern Fortran with git integration
A terminal file explorer written in modern Fortran (2008+) featuring dual-pane navigation, integrated git operations, fuzzy search, move mode, multi-select clipboard, and an intuitive keyboard-driven interface. Native binary compilation for fast startup and minimal resource usage.
fortress is available for Fedora/RHEL, Arch Linux (AUR), macOS (Homebrew), and can be built from source using the Fortran Package Manager (fpm).
For the cd-on-exit feature to work (press c to change directory and exit),
you must source the shell integration script. This wraps the binary and reads the
target directory after fortress exits.
Add to your ~/.bashrc or ~/.zshrc:
Add to your ~/.config/fish/config.fish:
Launch fortress in the current directory and start exploring:
fortress uses environment variables and a favorites file for configuration. There is no separate config file - the tool follows Unix conventions with sensible defaults and environment-based customization.
| Option | Type | Default | Description |
|---|---|---|---|
HOME | string | (required) | User home directory for temp files |
EDITOR | string | - | Text editor for file opening (priority 1) |
VISUAL | string | - | Visual editor (priority 2) |
TERM_PROGRAM | string | auto | Terminal type detection |
FORTRESS_BIN | path | auto | Override binary location |
FORTRESS_DIR | path | - | Development mode repo root |
| Option | Type | Default | Description |
|---|---|---|---|
~/.fortress_favorites | file | - | User favorites list (10 max) |
~/.fortress_cd | file | - | Directory for cd-on-exit feature |
~/.fortress_ls | temp | - | File listing cache |
~/.fortress_fzf | temp | - | fzf search results |
~/.fortress_pwd | temp | - | Current working directory cache |
~/.fortress_repo | temp | - | Git repository name cache |
~/.fortress_repo_root | temp | - | Git repo root cache |
~/.fortress_size | temp | - | Terminal size cache |
Temporary files (~/.fortress_*) are cleaned up automatically after use.
Only ~/.fortress_favorites and ~/.fortress_cd persist.
When you press o (or Alt+v) to open a file, fortress
checks environment variables in order:
$EDITOR - Primary text editor$VISUAL - Fallback visual editoropen (macOS) or xdg-open (Linux)
fortress auto-detects your terminal emulator via $TERM_PROGRAM to
apply appropriate display padding:
The favorites file at ~/.fortress_favorites stores up to 10
bookmarked directories, one path per line:
For development, you can override the binary location:
When building from source, you can customize optimization:
fortress is a terminal file explorer that combines the dual-pane layout of classic file managers with modern features like fuzzy search, git integration, and keyboard-first navigation. Written in modern Fortran (2008+), it compiles to a native binary with fast startup and minimal resource usage.
Navigation uses arrow keys following standard terminal conventions:
↑ / ↓ - Move cursor up/down through the file list→ - Enter the selected directory or follow a symlink← - Go back to the parent directoryThe cursor wraps around when reaching the top or bottom of the list. The scroll offset automatically adjusts to keep the cursor visible.
Quick jumps to common locations:
~ - Jump to your home directory/ - Jump to the filesystem root8 - Open favorites picker for saved directoriesWhen navigating into a directory, fortress remembers your previous position. Returning to a parent directory restores the cursor to the subdirectory you came from.
Simply start typing to search for files in the current directory. fortress uses incremental fuzzy matching with intelligent scoring:
-, _, or . to searchThe fuzzy search prioritizes matches in this order:
/, -, _, .Example: Typing cfg matches config.sh before myconfig.json
fortress maintains a clipboard for file operations. The clipboard persists across multiple paste operations for copying, but clears after pasting when cutting.
y to copy the selected file or directoryCOPY: filename in greenx to cut the selected file or directoryCUT: filename in yellowp to paste from the clipboardIf the destination already exists, fortress automatically appends a numeric suffix:
file.txt → file-1.txt, file-2.txtdirectory → directory-1, directory-2Move mode provides a two-cursor interface for intuitive file moving:
v (or Alt+m) on a file or directoryv or Enter to confirm the moveq to cancel
In move mode, ↑/↓ only stop on directories (skipping files).
The destination cursor appears with white background and underline.
Press n (or Alt+n) to enter rename mode:
RENAME MODE in yellow
Only alphanumeric characters, -, _, .,
and space are allowed. Case-sensitive renames are supported (e.g.,
README to Readme).
Press r (or Alt+r) to delete the selected item:
y to confirm, any other key to cancelToggle selection on individual items using the Space key:
Use Shift+arrow keys for block selection:
Shift+↑ - Extend selection upwardShift+↓ - Extend selection downwardThis creates a contiguous range selection, useful for selecting many adjacent files quickly.
After selecting multiple items:
y - Copy all selected items to clipboardx - Cut all selected items to clipboardr - Delete all selected items (with confirmation)p - Paste all items from clipboarde or E - Clear all selections
When inside a git repository, press Alt+g to toggle git mode:
repo_name:branch_name [GIT MODE]↑ - Staged changes (green)✗ - Unstaged changes (red)✗ - Untracked files (grey)↓ - Incoming changes from remote (yellow)In git mode:
a - Stage the selected file or directoryu - Unstage the selected file or directoryWhen staging a directory, all files within it are staged recursively. The status indicators update immediately after the operation.
Press m in git mode to commit:
f - Fetch from remotel - Pull from remoteh - Push to remote (h = "Host")d - Show diff for selected filet - Create a git tagWhen pushing without an upstream configured, fortress launches fzf to let you select the remote branch to track.
Bookmark frequently used directories for quick access:
* - Add or remove current directory as favorite8 - Open favorites picker (fzf-based)
Favorites are stored in ~/.fortress_favorites, persisting
across sessions. Maximum 10 favorites; adding an 11th prompts you to
replace an existing one.
Favorited directories display a ★ symbol next to their name.
fortress is implemented in approximately 3,831 lines of modern Fortran (2008+) across 7 main modules plus a main program file. The architecture follows a modular design with clear separation between UI, filesystem operations, terminal control, and git integration.
The project is organized into functional modules:
fortress/
├── app/
│ └── main.f90 # Main event loop, state management
├── src/
│ ├── filesystem/
│ │ └── fs_ops.f90 # File system operations
│ ├── terminal/
│ │ └── term_control.f90 # Terminal control, ANSI codes
│ ├── ui/
│ │ ├── display.f90 # Dual-pane rendering
│ │ └── preview.f90 # File preview (future)
│ ├── git/
│ │ └── git_ops.f90 # Git integration
│ └── version/
│ └── version.f90 # Auto-generated version info
└── test/
├── test_display_sim.f90 # Display tests
└── test_fortran_ansi.f90 # ANSI code tests
The main program (app/main.f90) implements an infinite event loop:
State variables persist across iterations, maintaining navigation history, clipboard contents, selection state, and git mode status.
Key state variables in the main program:
! Navigation state
character(len=MAX_PATH) :: current_dir, parent_dir
integer :: selected = 1, scroll_offset = 0
! Mode management
character(len=10) :: mode = 'normal' ! 'normal' or 'git'
logical :: move_mode = .false.
logical :: in_rename_mode = .false.
! Clipboard
logical :: has_clipboard = .false.
logical :: clipboard_is_cut = .false.
character(len=MAX_PATH) :: clipboard_source_path
integer :: clipboard_count
! Multi-select
logical, dimension(MAX_FILES) :: is_selected
integer :: selection_count = 0
! Git integration
logical :: in_git_repo = .false.
character(len=256) :: repo_name, branch_name fortress uses termios to configure the terminal for character-by-character input without echo:
! Enable raw mode
stty -icanon -echo min 1 time 0
! Restore canonical mode on exit
stty icanon echo This allows immediate response to keypresses without waiting for Enter, essential for interactive navigation.
The terminal control module defines ANSI escape sequences for display:
ESC = char(27) ! Escape character
! Screen control
ALT_SCREEN_ON = ESC // "[?1049h"
ALT_SCREEN_OFF = ESC // "[?1049l"
CURSOR_HIDE = ESC // "[?25l"
CURSOR_SHOW = ESC // "[?25h"
! Colors
BLUE = ESC // "[34m"
GREEN = ESC // "[32m"
RED = ESC // "[31m"
YELLOW = ESC // "[33m"
GREY = ESC // "[90m"
WHITE = ESC // "[37m"
! Styles
BOLD = ESC // "[1m"
DIM = ESC // "[2m"
UNDERLINE = ESC // "[4m"
REVERSE = ESC // "[7m"
RESET = ESC // "[0m" The display layout divides the terminal into regions:
┌─────────────────────────────────────────────┐
│ Header (2 lines) │
│ FORTRESS - /current/dir/path │
│ <status line: mode, clipboard, etc.> │
├──────────────┬──────────────────────────────┤
│ Parent (30%) │ Current (70%) │
│ [dimmed] │ [active, colored] │
│ filename │ filename ↑ (staged) │
│ → dir/ │ → dir/ │
│ .. │ file.txt ✗ (modified) │
├──────────────┴──────────────────────────────┤
│ Footer: Controls and help text │
└─────────────────────────────────────────────┘ /↑✗✗
The git module (src/git/git_ops.f90) provides:
git status --porcelaingit rev-parse --abbrev-ref HEADgit rev-parse --show-toplevelGit status is cached and only refreshed when changing directories or after git operations (add, unstage, commit, etc.).
The filesystem module (src/filesystem/fs_ops.f90) defines constants
and operations:
! Constants
integer, parameter :: MAX_PATH = 512 ! Maximum path length
integer, parameter :: MAX_FILES = 500 ! Maximum files per directory get_file_list() - Read directory, detect file types via shellget_pwd() - Get current working directoryget_parent_path() - Extract parent directory pathjoin_path() - Safely concatenate path componentsfzf_search() - Launch fzf for recursive searchwrite_exit_dir() - Write target directory for cd-on-exitopen_file_in_default_app() - Open with editor/platform openerFile types are detected via a shell command that checks each file:
-d flag, displayed with / suffix-x flag and not directory| Option | Type | Default | Description |
|---|---|---|---|
Arrow Up / Down | binding | - | Move cursor through file list |
Arrow Right | binding | - | Enter directory or follow symlink |
Arrow Left | binding | - | Go back to parent directory |
~ | binding | - | Jump to home directory |
/ | binding | - | Jump to root directory |
Shift+Up / Shift+Down | binding | - | Extend selection range |
| Option | Type | Default | Description |
|---|---|---|---|
y / Alt+y | binding | - | Copy to clipboard (yank) |
x / Alt+x | binding | - | Cut to clipboard |
p / Alt+p | binding | - | Paste from clipboard |
v / Alt+m | binding | - | Enter move mode / confirm move |
n / Alt+n | binding | - | Rename file or directory |
r / Alt+r | binding | - | Delete with confirmation |
o / Alt+v | binding | - | Open with $EDITOR or $VISUAL |
| Option | Type | Default | Description |
|---|---|---|---|
s / Alt+s | binding | - | fzf recursive search |
c / Alt+c | binding | - | CD to directory and exit |
. | binding | - | Toggle dotfile visibility |
a-z, 0-9 | binding | - | Fuzzy jump search (incremental) |
| Option | Type | Default | Description |
|---|---|---|---|
Space | binding | - | Toggle selection on current item |
Shift+Up / Shift+Down | binding | - | Extend selection range |
e / E | binding | - | Exit selection mode (clear all) |
| Option | Type | Default | Description |
|---|---|---|---|
Alt+g | binding | - | Toggle git mode |
a | binding | - | Stage file/directory (batch recursive) |
u | binding | - | Unstage file/directory |
m | binding | - | Commit with message prompt |
d | binding | - | Show git diff (files only) |
f | binding | - | Fetch from remote |
l | binding | - | Pull from remote |
h | binding | - | Push to remote |
t | binding | - | Create git tag |
| Option | Type | Default | Description |
|---|---|---|---|
* | binding | - | Add/remove current directory as favorite |
8 | binding | - | Open favorites picker (fzf) |
| Option | Type | Default | Description |
|---|---|---|---|
q | binding | - | Quit (or exit move mode) |
Ctrl+q | binding | - | Force quit |
ESC | binding | - | Cancel current operation |
Cause: Shell integration script not sourced.
Solution:
Cause: Terminal emulator not properly detected for padding adjustment.
Solution: Check your TERM_PROGRAM environment variable. Try using a tested terminal emulator (WezTerm, iTerm2, Alacritty, GNOME Terminal).
Cause: fzf not installed.
Solution:
Cause: Git not installed or not in a git repository.
Solution: Install git and ensure you're inside a git repository. The Alt+g toggle only appears when fortress detects a .git directory.
Cause: No $EDITOR or $VISUAL set, and no system opener available.
Solution:
Cause: Terminal may have been left in canonical mode after a suspend (Ctrl+Z).
Solution:
Terminal dimensions are cached for 100 screen redraws to prevent expensive
tput calls on every frame. If the display looks wrong after
resizing your terminal, navigate to a different directory to force a refresh.
Git status is cached with a 500ms TTL per directory. The cache is invalidated:
MAX_FILES = 500 limits the number of files displayed per directory.
Very large directories may be truncated.
fortress requires gfortran 10.0 or newer. On macOS with Apple Silicon, you may need to install a newer gfortran via Homebrew.