CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Facsimile (fac) is a terminal text editor written in modern Fortran with VSCode-style keybindings. It uses a gap buffer for text storage and pure ANSI escape sequences for terminal rendering.
Build Commands
# Standard build (recommended)
make
# Clean and rebuild
make clean && make
# Development build with comprehensive warnings
make dev
# Debug build with runtime checks
make debug
# Show compiler info
make info
The Makefile auto-detects the platform:
- macOS arm64: Uses gfortran-15 or flang-new from Homebrew
- macOS Intel/Linux: Uses standard gfortran
Version Management
# Check current version
make version
# Bump versions
make bump-patch # 0.9.1 -> 0.9.2
make bump-minor # 0.9.1 -> 0.10.0
make bump-major # 0.9.1 -> 1.0.0
# Full release build with checklist
make release
The VERSION file is the single source of truth. The Makefile auto-generates src/version_module.f90.
Architecture
Core Data Flow
Input → input_handler_module → command_handler_module → buffer operations → renderer_module → Terminal
Key Modules
-
src/buffer/text_buffer_module.f90: Gap buffer implementation for text storage. All operations maintain gap position for efficient insertions. -
src/editor_state_module.f90: Central state management. Containseditor_state_twith tabs, panes, cursors, LSP state, and UI panels. This is the "god object" that gets passed around. -
src/terminal/input_handler_module.f90: Raw keyboard input processing. Handles escape sequences, mouse events, and key combinations. -
src/terminal/renderer_module.f90: Screen rendering with ANSI escape sequences. Handles syntax highlighting, status bar, and split panes. -
src/commands/command_handler_module.f90: Main command dispatch. Maps key inputs to editor actions (~7000 lines). -
src/terminal/termios_wrapper.c: C wrapper for terminal raw mode via termios.
Module Dependency Order
The Makefile's SOURCES list defines the required compilation order. Fortran modules must be compiled before modules that use them. The .NOTPARALLEL directive enforces sequential builds.
UTF-8 Handling
All cursor positions use CHARACTER indices, not byte indices. The utf8_module provides conversion functions:
utf8_char_count()- count characters in stringbuffer_byte_to_char_col()- convert byte position to character columnbuffer_char_to_byte_col()- convert character column to byte position
Pane/Tab Architecture
editor_state_t
└── tabs[] (multiple open files)
└── panes[] (split views of same file)
├── buffer (text content)
├── cursors[] (multiple cursor support)
└── viewport (scroll position)
LSP Integration
Located in src/lsp/. Communicates with language servers via JSON-RPC over stdio:
lsp_server_manager_module.f90- Server lifecycle managementjson_module.f90- JSON parsing/generationlsp_client_module.f90- Request/response handling
Fortran-Specific Constraints
Line Length Limit
Fortran has a 132-character line limit. Unicode characters (like box-drawing ═) count as multiple bytes. Long lines must be split using & continuation:
! Bad - will fail compilation
line = '═══════════════════════════════════════════════════════════════════'
! Good - split across lines
line = '═══════════════════════' // &
'═══════════════════════' // &
'═══════════════════════'
Module Files
Compilation generates .mod files in the root directory. These are binary module interfaces, not source files.
Distribution
The project is distributed via three channels:
- Homebrew:
homebrew-facsimilerepo withfacsimile.rbformula - AUR: Arch User Repository PKGBUILD
- RPM: Spec file at
~/rpmbuild/SPECS/facsimile.spec
When releasing, update all three with the new version and SHA256 hash from the GitHub release tarball.
Testing
# LSP module tests
make test-lsp
# Test LSP with editor
make test-lsp-editor
# Manual key testing
./keytest # Basic key codes
./keytest_fac # With fac's termios settings
Running
./fac [filename] # Open file
./fac --version # Show version
./fac --help # Show help
Key bindings follow VSCode conventions: Ctrl-S save, Ctrl-Q quit, Ctrl-B file tree, Ctrl-F search, Ctrl-Z undo.
View source
| 1 | # CLAUDE.md |
| 2 | |
| 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | |
| 5 | ## Project Overview |
| 6 | |
| 7 | Facsimile (`fac`) is a terminal text editor written in modern Fortran with VSCode-style keybindings. It uses a gap buffer for text storage and pure ANSI escape sequences for terminal rendering. |
| 8 | |
| 9 | ## Build Commands |
| 10 | |
| 11 | ```bash |
| 12 | # Standard build (recommended) |
| 13 | make |
| 14 | |
| 15 | # Clean and rebuild |
| 16 | make clean && make |
| 17 | |
| 18 | # Development build with comprehensive warnings |
| 19 | make dev |
| 20 | |
| 21 | # Debug build with runtime checks |
| 22 | make debug |
| 23 | |
| 24 | # Show compiler info |
| 25 | make info |
| 26 | ``` |
| 27 | |
| 28 | The Makefile auto-detects the platform: |
| 29 | - **macOS arm64**: Uses gfortran-15 or flang-new from Homebrew |
| 30 | - **macOS Intel/Linux**: Uses standard gfortran |
| 31 | |
| 32 | ## Version Management |
| 33 | |
| 34 | ```bash |
| 35 | # Check current version |
| 36 | make version |
| 37 | |
| 38 | # Bump versions |
| 39 | make bump-patch # 0.9.1 -> 0.9.2 |
| 40 | make bump-minor # 0.9.1 -> 0.10.0 |
| 41 | make bump-major # 0.9.1 -> 1.0.0 |
| 42 | |
| 43 | # Full release build with checklist |
| 44 | make release |
| 45 | ``` |
| 46 | |
| 47 | The `VERSION` file is the single source of truth. The Makefile auto-generates `src/version_module.f90`. |
| 48 | |
| 49 | ## Architecture |
| 50 | |
| 51 | ### Core Data Flow |
| 52 | |
| 53 | ``` |
| 54 | Input → input_handler_module → command_handler_module → buffer operations → renderer_module → Terminal |
| 55 | ``` |
| 56 | |
| 57 | ### Key Modules |
| 58 | |
| 59 | - **`src/buffer/text_buffer_module.f90`**: Gap buffer implementation for text storage. All operations maintain gap position for efficient insertions. |
| 60 | |
| 61 | - **`src/editor_state_module.f90`**: Central state management. Contains `editor_state_t` with tabs, panes, cursors, LSP state, and UI panels. This is the "god object" that gets passed around. |
| 62 | |
| 63 | - **`src/terminal/input_handler_module.f90`**: Raw keyboard input processing. Handles escape sequences, mouse events, and key combinations. |
| 64 | |
| 65 | - **`src/terminal/renderer_module.f90`**: Screen rendering with ANSI escape sequences. Handles syntax highlighting, status bar, and split panes. |
| 66 | |
| 67 | - **`src/commands/command_handler_module.f90`**: Main command dispatch. Maps key inputs to editor actions (~7000 lines). |
| 68 | |
| 69 | - **`src/terminal/termios_wrapper.c`**: C wrapper for terminal raw mode via termios. |
| 70 | |
| 71 | ### Module Dependency Order |
| 72 | |
| 73 | The Makefile's `SOURCES` list defines the required compilation order. Fortran modules must be compiled before modules that `use` them. The `.NOTPARALLEL` directive enforces sequential builds. |
| 74 | |
| 75 | ### UTF-8 Handling |
| 76 | |
| 77 | All cursor positions use CHARACTER indices, not byte indices. The `utf8_module` provides conversion functions: |
| 78 | - `utf8_char_count()` - count characters in string |
| 79 | - `buffer_byte_to_char_col()` - convert byte position to character column |
| 80 | - `buffer_char_to_byte_col()` - convert character column to byte position |
| 81 | |
| 82 | ### Pane/Tab Architecture |
| 83 | |
| 84 | ``` |
| 85 | editor_state_t |
| 86 | └── tabs[] (multiple open files) |
| 87 | └── panes[] (split views of same file) |
| 88 | ├── buffer (text content) |
| 89 | ├── cursors[] (multiple cursor support) |
| 90 | └── viewport (scroll position) |
| 91 | ``` |
| 92 | |
| 93 | ### LSP Integration |
| 94 | |
| 95 | Located in `src/lsp/`. Communicates with language servers via JSON-RPC over stdio: |
| 96 | - `lsp_server_manager_module.f90` - Server lifecycle management |
| 97 | - `json_module.f90` - JSON parsing/generation |
| 98 | - `lsp_client_module.f90` - Request/response handling |
| 99 | |
| 100 | ## Fortran-Specific Constraints |
| 101 | |
| 102 | ### Line Length Limit |
| 103 | Fortran has a 132-character line limit. Unicode characters (like box-drawing `═`) count as multiple bytes. Long lines must be split using `&` continuation: |
| 104 | |
| 105 | ```fortran |
| 106 | ! Bad - will fail compilation |
| 107 | line = '═══════════════════════════════════════════════════════════════════' |
| 108 | |
| 109 | ! Good - split across lines |
| 110 | line = '═══════════════════════' // & |
| 111 | '═══════════════════════' // & |
| 112 | '═══════════════════════' |
| 113 | ``` |
| 114 | |
| 115 | ### Module Files |
| 116 | Compilation generates `.mod` files in the root directory. These are binary module interfaces, not source files. |
| 117 | |
| 118 | ## Distribution |
| 119 | |
| 120 | The project is distributed via three channels: |
| 121 | - **Homebrew**: `homebrew-facsimile` repo with `facsimile.rb` formula |
| 122 | - **AUR**: Arch User Repository PKGBUILD |
| 123 | - **RPM**: Spec file at `~/rpmbuild/SPECS/facsimile.spec` |
| 124 | |
| 125 | When releasing, update all three with the new version and SHA256 hash from the GitHub release tarball. |
| 126 | |
| 127 | ## Testing |
| 128 | |
| 129 | ```bash |
| 130 | # LSP module tests |
| 131 | make test-lsp |
| 132 | |
| 133 | # Test LSP with editor |
| 134 | make test-lsp-editor |
| 135 | |
| 136 | # Manual key testing |
| 137 | ./keytest # Basic key codes |
| 138 | ./keytest_fac # With fac's termios settings |
| 139 | ``` |
| 140 | |
| 141 | ## Running |
| 142 | |
| 143 | ```bash |
| 144 | ./fac [filename] # Open file |
| 145 | ./fac --version # Show version |
| 146 | ./fac --help # Show help |
| 147 | ``` |
| 148 | |
| 149 | Key bindings follow VSCode conventions: Ctrl-S save, Ctrl-Q quit, Ctrl-B file tree, Ctrl-F search, Ctrl-Z undo. |