Files
kte/docs/DEVELOPER_GUIDE.md
Kyle Isom daeeecb342 Standardize error handling patterns and improve ErrorHandler integration.
- Added a comprehensive error propagation standardization report detailing dominant patterns, inconsistencies, and recommended remediations (`docs/audits/error-propagation-standardization.md`).
- Integrated `ErrorHandler` into key components, including `main.cc` for robust exception reporting, and added centralized logging to a user state path.
- Introduced EINTR-safe syscall wrappers (`SyscallWrappers.h`, `.cc`) to improve resilience of file and metadata operations.
- Enhanced `DEVELOPER_GUIDE.md` with an error handling conventions section, covering pattern guidelines and best practices.
- Identified gaps in `PieceTable` and internal helpers; deferred fixes with detailed recommendations for improved memory allocation error reporting.
2026-02-17 21:25:19 -08:00

968 lines
28 KiB
Markdown

# kte Developer Guide
Welcome to kte development! This guide will help you understand the
codebase, make changes, and contribute effectively.
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Core Components](#core-components)
3. [Code Organization](#code-organization)
4. [Building and Testing](#building-and-testing)
5. [Making Changes](#making-changes)
6. [Code Style](#code-style)
7. [Error Handling Conventions](#error-handling-conventions)
8. [Common Tasks](#common-tasks)
## Architecture Overview
kte follows a clean separation of concerns with three main layers:
```
┌─────────────────────────────────────────┐
│ Frontend Layer (Terminal/ImGui/Qt) │
│ - TerminalFrontend / ImGuiFrontend │
│ - InputHandler + Renderer interfaces │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Command Layer │
│ - Command registry and execution │
│ - All editing operations │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ Core Model Layer │
│ - Editor (top-level state) │
│ - Buffer (document model) │
│ - PieceTable (text storage) │
│ - UndoSystem (undo/redo) │
│ - SwapManager (crash recovery) │
└─────────────────────────────────────────┘
```
### Design Principles
- **Frontend Independence**: Core editing logic is independent of UI.
Frontends implement `Frontend`, `InputHandler`, and `Renderer`
interfaces.
- **Command Pattern**: All editing operations go through the command
system, enabling consistent undo/redo and testing.
- **Piece Table**: Efficient text storage using a piece table data
structure that avoids copying large buffers.
- **Lazy Materialization**: Text is materialized on-demand to minimize
memory allocations.
## Core Components
### Editor (`Editor.h/.cc`)
The top-level editor state container. Manages:
- Multiple buffers
- Editor modes (normal, k-command prefix, prompts)
- Kill ring (clipboard history)
- Universal argument state
- Search state
- Status messages
- Swap file management
**Key Insight**: Editor is primarily a state holder with many
getter/setter pairs. It doesn't contain editing logic - that's in
commands.
### Buffer (`Buffer.h/.cc`)
Represents an open document. Manages:
- File I/O (open, save, external modification detection)
- Cursor position and viewport offsets
- Mark (selection start point)
- Visual line mode state
- Syntax highlighting integration
- Undo system integration
- Swap recording integration
**Key Insight**: Buffer wraps a PieceTable and provides a higher-level
interface. The nested `Buffer::Line` class is a legacy wrapper that has
been largely phased out in favor of direct PieceTable operations.
**Line Access APIs**: Buffer provides three ways to access line content:
- `GetLineView(row)` - Zero-copy `string_view` (fastest, 11x faster than
Rows())
- `GetLineString(row)` - Returns `std::string` copy (1.7x faster than
Rows())
- `Rows()` - Materializes all lines into cache (legacy, avoid in new
code)
See `docs/BENCHMARKS.md` for detailed performance analysis and usage
guidance.
### PieceTable (`PieceTable.h/.cc`)
The core text storage data structure. Provides:
- Efficient insert/delete operations without copying entire buffer
- Line-based queries (line count, get line, line ranges)
- Position conversion (byte offset ↔ line/column)
- Substring extraction
- Search functionality
- Automatic consolidation to prevent piece fragmentation
**Key Insight**: PieceTable uses lazy materialization - the full text is
only assembled when `Data()` is called. Most operations work directly on
the piece list.
### UndoSystem (`UndoSystem.h/.cc`, `UndoTree.h/.cc`, `UndoNode.h/.cc`)
Implements undo/redo with a tree structure supporting:
- Linear undo/redo
- Branching history (future enhancement)
- Checkpointing and compaction
- Memory-efficient node pooling
**Key Insight**: The undo system records operations at the PieceTable
level, not at the command level.
### Command System (`Command.h/.cc`)
All editing operations are implemented as commands:
- File operations (save, open, close)
- Navigation (move cursor, page up/down, word movement)
- Editing (insert, delete, kill, yank)
- Search and replace
- Buffer management
- Configuration (syntax, theme, font)
**Key Insight**: `Command.cc` is currently a monolithic 5000-line file.
This is the biggest maintainability challenge in the codebase.
### Frontend Abstraction
Three interfaces define the frontend contract:
- **Frontend** (`Frontend.h`): Top-level lifecycle (Init/Step/Shutdown)
- **InputHandler** (`InputHandler.h`): Converts UI events to commands
- **Renderer** (`Renderer.h`): Draws the editor state
Implementations:
- **Terminal**: ncurses-based (`TerminalFrontend`,
`TerminalInputHandler`, `TerminalRenderer`)
- **ImGui**: Dear ImGui-based (`ImGuiFrontend`, `ImGuiInputHandler`,
`ImGuiRenderer`)
- **Qt**: Qt-based (`QtFrontend`, `QtInputHandler`, `QtRenderer`)
- **Test**: Programmatic testing (`TestFrontend`, `TestInputHandler`,
`TestRenderer`)
## Code Organization
### Directory Structure
```
kte/
├── *.h, *.cc # Core implementation (root level)
├── main.cc # Entry point
├── docs/ # Documentation
│ ├── ke.md # Original ke editor reference (keybindings)
│ ├── swap.md # Swap file design
│ ├── syntax.md # Syntax highlighting
│ ├── themes.md # Theme system
│ └── plans/ # Design documents
├── tests/ # Test suite
│ ├── Test.h # Minimal test framework
│ ├── TestRunner.cc # Test runner
│ └── test_*.cc # Individual test files
├── syntax/ # Syntax highlighting engines
├── fonts/ # Embedded fonts for GUI
├── themes/ # Color themes
└── ext/ # External dependencies (imgui)
```
### File Naming Conventions
- Headers: `ComponentName.h`
- Implementation: `ComponentName.cc`
- Tests: `test_feature_name.cc`
### Key Files by Size
Large files that may need attention:
- `Command.cc` (4995 lines) - **Needs refactoring**: Consider splitting
into logical groups
- `Swap.cc` (1300 lines) - Crash recovery system (migrated to direct
PieceTable operations)
- `QtFrontend.cc` (985 lines) - Qt integration
- `ImGuiRenderer.cc` (930 lines) - ImGui rendering
- `PieceTable.cc` (800 lines) - Core data structure
- `Buffer.cc` (763 lines) - Document model
## Building and Testing
### Build System
kte uses CMake with multiple build profiles:
```bash
# Debug build (terminal only)
cmake -S . -B cmake-build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build cmake-build-debug
# Release build with GUI
cmake -S . -B cmake-build-release -DCMAKE_BUILD_TYPE=Release -DBUILD_GUI=ON
cmake --build cmake-build-release
# Build specific target
cmake --build cmake-build-debug --target kte_tests
```
### CMake Targets
- `kte` - Terminal editor executable
- `kge` - GUI editor executable (when `BUILD_GUI=ON`)
- `kte_tests` - Test suite
- `imgui` - Dear ImGui library (when `BUILD_GUI=ON`)
### Running Tests
```bash
# Build and run all tests
cmake --build cmake-build-debug --target kte_tests && ./cmake-build-debug/kte_tests
# Run tests with verbose output
./cmake-build-debug/kte_tests
```
### Test Organization
The test suite uses a minimal custom framework (`Test.h`):
```cpp
TEST(TestName) {
// Test body
ASSERT_EQ(actual, expected);
ASSERT_TRUE(condition);
EXPECT_TRUE(condition); // Non-fatal
}
```
Test files by category:
- **Core Data Structures**:
- `test_piece_table.cc` - PieceTable operations, line indexing,
random edits
- `test_buffer_rows.cc` - Buffer row operations
- `test_buffer_io.cc` - File I/O (open, save, SaveAs)
- **Editing Operations**:
- `test_command_semantics.cc` - Command execution
- `test_kkeymap.cc` - Keybinding system
- `test_visual_line_mode.cc` - Visual line selection
- **Search and Replace**:
- `test_search.cc` - Search functionality
- `test_search_replace_flow.cc` - Interactive search/replace
- **Text Reflow**:
- `test_reflow_paragraph.cc` - Paragraph reformatting
- `test_reflow_indented_bullets.cc` - Indented list handling
- **Undo System**:
- `test_undo.cc` - Undo/redo operations
- **Swap Files** (Crash Recovery):
- `test_swap_recorder.cc` - Recording operations
- `test_swap_writer.cc` - Writing swap files
- `test_swap_replay.cc` - Replaying operations
- `test_swap_recovery_prompt.cc` - Recovery UI
- `test_swap_cleanup.cc` - Cleanup logic
- `test_swap_git_editor.cc` - Git editor integration
- **Performance and Migration**:
- `test_benchmarks.cc` - Performance benchmarks for core operations
- `test_migration_coverage.cc` - Buffer::Line migration validation
- **Integration Tests**:
- `test_daily_workflows.cc` - Real-world editing scenarios
- `test_daily_driver_harness.cc` - Workflow test infrastructure
**Total**: 98 tests across 22 test files. See `docs/BENCHMARKS.md` for
performance benchmark results.
### Docker/Podman for Linux Builds
A minimal `Dockerfile` is provided for **testing Linux builds** without
requiring a native Linux system. The Dockerfile creates a build
environment container with all necessary dependencies. Your source tree
is mounted into the container at runtime, allowing you to test
compilation and run tests on Linux.
**Important**: This is intended for testing Linux builds, not for
running
kte locally. The container expects the source tree to be mounted when
run.
This is particularly useful for:
- **macOS/Windows developers** testing Linux compatibility
- **CI/CD pipelines** ensuring cross-platform builds
- **Reproducible builds** with a known Alpine Linux 3.19 environment
#### Prerequisites
Install Docker or Podman:
- **macOS**: `brew install podman` (Docker Desktop also works)
- **Linux**: Use your distribution's package manager
- **Windows**: Docker Desktop or Podman Desktop
If using Podman on macOS, start the VM:
```bash
podman machine init
podman machine start
```
#### Building the Docker Image
The Dockerfile installs all build dependencies including GUI support (
g++ 13.2.1, CMake 3.27.8, ncurses-dev, SDL2, OpenGL/Mesa, Freetype). It
does not copy or build the source code.
From the project root:
```bash
# Build the environment image
docker build -t kte-linux .
# Or with Podman
podman build -t kte-linux .
```
#### Testing Linux Builds
Mount your source tree and run the build + tests:
```bash
# Build and test (default command)
docker run --rm -v "$(pwd):/kte" kte-linux
# Expected output: "98 tests passed, 0 failed"
```
The default command builds both `kte` (terminal) and `kge` (GUI)
executables with full GUI support (`-DBUILD_GUI=ON`) and runs the
complete test suite.
#### Custom Build Commands
```bash
# Open a shell in the build environment
docker run --rm -it -v "$(pwd):/kte" kte-linux /bin/bash
# Then inside the container:
cmake -B build -DBUILD_GUI=ON -DBUILD_TESTS=ON
cmake --build build --target kte # Terminal version
cmake --build build --target kge # GUI version
cmake --build build --target kte_tests
./build/kte_tests
# Or run kte directly
./build/kte --help
# Terminal-only build (smaller, faster)
cmake -B build -DBUILD_GUI=OFF -DBUILD_TESTS=ON
cmake --build build --target kte
```
#### Running kte Interactively
To test kte's terminal UI on Linux:
```bash
# Run kte with a file from your host system
docker run --rm -it -v "$(pwd):/kte" kte-linux sh -c "cmake -B build -DBUILD_GUI=OFF && cmake --build build --target kte && ./build/kte README.md"
```
#### CI/CD Integration
Example GitHub Actions workflow:
```yaml
- name: Test Linux Build
run: |
docker build -t kte-linux .
docker run --rm -v "${{ github.workspace }}:/kte" kte-linux
```
#### Troubleshooting
**"Cannot connect to Podman socket"** (macOS):
```bash
podman machine start
```
**"Permission denied"** (Linux):
```bash
# Add your user to the docker group
sudo usermod -aG docker $USER
# Log out and back in
```
**Build fails with ncurses errors**:
The Dockerfile explicitly installs `ncurses-dev` (wide-character
ncurses). If you modify the Dockerfile, ensure this dependency remains.
**"No such file or directory" errors**:
Ensure you're mounting the source tree with `-v "$(pwd):/kte"` when
running the container.
### Writing Tests
When adding new functionality:
1. **Add a test first** - Write a failing test that demonstrates the
desired behavior
2. **Use descriptive names** - Test names should explain what's being
validated
3. **Test edge cases** - Empty buffers, EOF, beginning of file, etc.
4. **Use TestFrontend** - For integration tests, use the programmatic
test frontend
Example test structure:
```cpp
TEST(Feature_Behavior_Scenario) {
// Setup
Buffer buf;
buf.insert_text(0, 0, "test content\n");
// Exercise
buf.delete_text(0, 5, 4);
// Verify
ASSERT_EQ(buf.GetLineString(0), std::string("test\n"));
}
```
## Making Changes
### Development Workflow
1. **Understand the change scope**:
- Pure UI change? → Modify frontend only
- New editing operation? → Add command in `Command.cc`
- Core data structure? → Modify `PieceTable` or `Buffer`
2. **Find relevant code**:
- Use `git grep` or IDE search to find similar functionality
- Check `Command.cc` for existing command patterns
- Look at tests to understand expected behavior
3. **Make the change**:
- Follow existing code style (see below)
- Add or update tests
- Update documentation if needed
4. **Test thoroughly**:
- Run the full test suite
- Manually test in both terminal and GUI (if applicable)
- Test edge cases (empty files, large files, EOF, etc.)
### Common Pitfalls
- **Don't modify `Buffer::Rows()` directly** - Use the PieceTable API (
`insert_text`, `delete_text`, etc.) to ensure undo and swap recording
work correctly.
- **Prefer efficient line access** - Use `GetLineView()` for read-only
access (11x faster than `Rows()`), or `GetLineString()` when you need
a copy. Avoid `Rows()` in new code.
- **Remember to invalidate caches** - If you modify PieceTable
internals, ensure line index and materialization caches are
invalidated.
- **Cursor visibility** - After editing operations, call
`ensure_cursor_visible()` to update viewport offsets.
- **Undo boundaries** - Use `buf.Undo()->BeginGroup()` and `EndGroup()`
to group related operations.
- **GetLineView() lifetime** - The returned `string_view` is only valid
until the next buffer modification. Use immediately or copy to
`std::string`.
## Code Style
kte uses C++20 with these conventions:
### Naming
- **Classes/Structs**: `PascalCase` (e.g., `PieceTable`, `Buffer`)
- **Functions/Methods**: `PascalCase` (e.g., `GetLine`, `Insert`)
- **Variables**: `snake_case` with trailing underscore for members (
e.g., `total_size_`, `line_index_`)
- **Constants**: `snake_case` or `UPPER_CASE` depending on context
- **Private members**: Trailing underscore (e.g., `pieces_`, `dirty_`)
### Formatting
- **Indentation**: Tabs (width 8 in most files, but follow existing
style)
- **Braces**: Opening brace on same line for functions, control
structures
- **Line length**: No strict limit, but keep reasonable (~100-120 chars)
- **Includes**: Group by category (system, external, project) with blank
lines between
### Comments
- **File headers**: Brief description of the file's purpose
- **Function comments**: Explain non-obvious behavior, not what the code
obviously does
- **Inline comments**: Explain *why*, not *what*
- **TODO comments**: Use `TODO:` prefix for future work
Example:
```cpp
// Consolidate small pieces to prevent fragmentation.
// This is a heuristic: we only consolidate when piece count exceeds
// a threshold, and we cap the bytes processed per consolidation run.
void maybeConsolidate() {
if (pieces_.size() < piece_limit_)
return;
// ... implementation
}
```
## Error Handling Conventions
kte uses standardized error handling patterns to ensure consistency and
reliability across the codebase. This section documents when to use each
pattern and how to integrate with the centralized error handling system.
### Error Propagation Patterns
kte uses three standard patterns for error handling:
#### 1. `bool` + `std::string &err` (I/O and Fallible Operations)
**When to use**: Operations that can fail and need detailed error
messages
(file I/O, network operations, parsing, resource allocation).
**Pattern**:
```cpp
bool OperationName(args..., std::string &err) {
err.clear();
// Attempt operation
if (/* operation failed */) {
err = "Detailed error message with context";
ErrorHandler::Instance().Error("ComponentName", err, "optional_context");
return false;
}
return true;
}
```
**Examples**:
- `Buffer::OpenFromFile(const std::string &path, std::string &err)`
- `Buffer::Save(std::string &err)`
-
`SwapManager::ReplayFile(Buffer &buf, const std::string &path, std::string &err)`
**Guidelines**:
- Always clear `err` at the start of the function
- Provide actionable error messages with context (file paths, operation
details)
- Call `ErrorHandler::Instance().Error()` for centralized logging
- Return `false` on failure, `true` on success
- Capture `errno` immediately after syscall failures: `int saved_errno =
errno;`
- Use `std::strerror(saved_errno)` for syscall error messages
#### 2. `void` (Infallible State Changes)
**When to use**: Operations that modify internal state and cannot fail
(setters, cursor movement, flag toggles).
**Pattern**:
```cpp
void SetProperty(Type value) {
property_ = value;
// Update related state if needed
}
```
**Examples**:
- `Buffer::SetCursor(std::size_t x, std::size_t y)`
- `Buffer::SetDirty(bool d)`
- `Editor::SetStatus(const std::string &msg)`
**Guidelines**:
- Use for simple state changes that cannot fail
- No error reporting needed
- Keep operations atomic and side-effect free when possible
#### 3. `bool` without error parameter (Control Flow)
**When to use**: Operations where success/failure is sufficient
information
and detailed error messages aren't needed (validation checks, control
flow
decisions).
**Pattern**:
```cpp
bool CheckCondition() const {
return condition_is_met;
}
```
**Examples**:
- `Editor::SwitchTo(std::size_t index)` - returns false if index invalid
- `Editor::CloseBuffer(std::size_t index)` - returns false if can't
close
**Guidelines**:
- Use when the caller only needs to know success/failure
- Typically for validation or control flow decisions
- Don't use for operations that need error diagnostics
### ErrorHandler Integration
All error-prone operations should report errors to the centralized
`ErrorHandler` for logging and UI integration.
**Severity Levels**:
```cpp
ErrorHandler::Instance().Info("Component", "message", "context"); // Informational
ErrorHandler::Instance().Warning("Component", "message", "context"); // Warning
ErrorHandler::Instance().Error("Component", "message", "context"); // Error
ErrorHandler::Instance().Critical("Component", "message", "context"); // Critical
```
**When to use each severity**:
- **Info**: Non-error events (file saved, operation completed)
- **Warning**: Recoverable issues (external file modification detected)
- **Error**: Operation failures (file I/O errors, allocation failures)
- **Critical**: Fatal errors (unhandled exceptions, data corruption)
**Component names**: Use the class name ("Buffer", "SwapManager",
"Editor", "main")
**Context**: Optional string providing additional context (filename,
buffer
name, operation details)
### Error Handling in Different Contexts
#### File I/O Operations
```cpp
bool Buffer::Save(std::string &err) const {
if (!is_file_backed_ || filename_.empty()) {
err = "Buffer is not file-backed; use SaveAs()";
return false;
}
const std::size_t sz = content_.Size();
const char *data = sz ? content_.Data() : nullptr;
if (!atomic_write_file(filename_, data ? data : "", sz, err)) {
ErrorHandler::Instance().Error("Buffer", err, filename_);
return false;
}
return true;
}
```
#### Syscall Error Handling with EINTR-Safe Wrappers
kte provides EINTR-safe syscall wrappers in `SyscallWrappers.h` that
automatically retry on `EINTR`. **Always use these wrappers instead of
direct syscalls.**
```cpp
#include "SyscallWrappers.h"
bool open_file(const std::string &path, std::string &err) {
int fd = kte::syscall::Open(path.c_str(), O_RDONLY);
if (fd < 0) {
int saved_errno = errno; // Capture immediately!
err = "Failed to open file '" + path + "': " + std::strerror(saved_errno);
ErrorHandler::Instance().Error("Component", err, path);
return false;
}
// ... use fd
kte::syscall::Close(fd);
return true;
}
```
**Available EINTR-safe wrappers**:
- `kte::syscall::Open(path, flags, mode)` - wraps `open(2)`
- `kte::syscall::Close(fd)` - wraps `close(2)`
- `kte::syscall::Fsync(fd)` - wraps `fsync(2)`
- `kte::syscall::Fstat(fd, buf)` - wraps `fstat(2)`
- `kte::syscall::Fchmod(fd, mode)` - wraps `fchmod(2)`
- `kte::syscall::Mkstemp(template)` - wraps `mkstemp(3)`
**Note**: `rename(2)` and `unlink(2)` are NOT wrapped because they
operate on filesystem metadata atomically and don't need EINTR retry.
#### Background Thread Errors
```cpp
void background_worker() {
try {
// ... work
} catch (const std::exception &e) {
std::string msg = std::string("Exception in worker: ") + e.what();
ErrorHandler::Instance().Error("WorkerThread", msg);
} catch (...) {
ErrorHandler::Instance().Error("WorkerThread", "Unknown exception");
}
}
```
#### Top-Level Exception Handling
```cpp
int main(int argc, char *argv[]) {
try {
// ... main logic
return 0;
} catch (const std::exception &e) {
std::string msg = std::string("Unhandled exception: ") + e.what();
ErrorHandler::Instance().Critical("main", msg);
std::cerr << "FATAL ERROR: " << e.what() << "\n";
return 1;
} catch (...) {
ErrorHandler::Instance().Critical("main", "Unknown exception");
std::cerr << "FATAL ERROR: Unknown exception\n";
return 1;
}
}
```
### Error Handling Anti-Patterns
**❌ Don't**: Silently ignore errors
```cpp
// BAD
void process() {
std::string err;
if (!operation(err)) {
// Error ignored!
}
}
```
**✅ Do**: Always handle or propagate errors
```cpp
// GOOD
bool process(std::string &err) {
if (!operation(err)) {
// err already set by operation()
return false;
}
return true;
}
```
**❌ Don't**: Use generic error messages
```cpp
// BAD
err = "Operation failed";
```
**✅ Do**: Provide specific, actionable error messages
```cpp
// GOOD
err = "Failed to open file '" + path + "': " + std::strerror(errno);
```
**❌ Don't**: Forget to capture errno
```cpp
// BAD
if (::write(fd, data, len) < 0) {
// errno might be overwritten by other calls!
err = std::strerror(errno);
}
```
**✅ Do**: Capture errno immediately
```cpp
// GOOD
if (::write(fd, data, len) < 0) {
int saved_errno = errno;
err = std::strerror(saved_errno);
}
```
### Error Log Location
All errors are automatically logged to:
```
~/.local/state/kte/error.log
```
Log format:
```
[2026-02-17 20:12:34.567] [ERROR] SwapManager (buffer.txt): Failed to write swap record
[2026-02-17 20:12:35.123] [CRITICAL] main: Unhandled exception: out of memory
```
### Migration Guide
When updating existing code to follow these conventions:
1. **Identify error-prone operations** - File I/O, syscalls, allocations
2. **Add `std::string &err` parameter** if not present
3. **Add ErrorHandler calls** at all error sites
4. **Capture errno** for syscall failures
5. **Update callers** to handle the error parameter
6. **Write tests** that verify error handling
## Common Tasks
### Adding a New Command
1. **Define the command function** in `Command.cc`:
```cpp
bool cmd_my_feature(CommandContext &ctx) {
Editor &ed = ctx.ed;
Buffer *buf = ed.CurrentBuffer();
if (!buf) return false;
// Implement the command
buf->insert_text(buf->Cury(), buf->Curx(), "text");
return true;
}
```
2. **Register the command** in `InstallDefaultCommands()`:
```cpp
CommandRegistry::Register({
CommandId::MyFeature,
"my-feature",
"Description of what it does",
cmd_my_feature
});
```
3. **Add keybinding** in the appropriate `InputHandler` (e.g.,
`TerminalInputHandler.cc`).
4. **Write tests** in `tests/test_command_semantics.cc` or a new test
file.
### Adding a New Frontend
1. **Implement the three interfaces**:
- `Frontend` - Lifecycle management
- `InputHandler` - Event → Command translation
- `Renderer` - Draw the editor state
2. **Study existing implementations**:
- `TerminalFrontend` - Simplest, good starting point
- `ImGuiFrontend` - More complex, shows GUI patterns
3. **Register in `main.cc`** to make it selectable.
### Modifying the PieceTable
The PieceTable is performance-critical. When making changes:
1. **Understand the piece list** - Each piece references a range in
either `original_` or `add_` buffer
2. **Maintain invariants**:
- `total_size_` must match sum of piece lengths
- Line index must be invalidated on content changes
- Version must increment on mutations
3. **Test thoroughly** - Use `test_piece_table.cc` random edit test as a
reference model
4. **Profile if needed** - Large file performance is a key goal
### Adding Syntax Highlighting
1. **Create a new highlighter** in `syntax/` directory:
- Inherit from `HighlighterEngine`
- Implement `HighlightLine()` method
2. **Register in `HighlighterRegistry`** (
`syntax/HighlighterRegistry.cc`)
3. **Add file extension mapping** in the registry
4. **Test with sample files** of that language
### Debugging Tips
- **Use the test frontend** - Write a test that reproduces the issue
- **Enable assertions** - Build in Debug mode
- **Check swap files** - Look in `/tmp/kte-swap-*` for recorded
operations
- **Print debugging** - Use `std::cerr` (stdout is used by ncurses)
- **GDB/LLDB** - Standard debuggers work fine with kte
## Getting Help
- **Read the code** - kte is designed to be understandable; follow the
data flow
- **Check existing tests** - Tests often show how to use APIs correctly
- **Look at git history** - See how similar features were implemented
- **Read design docs** - Check `docs/plans/` for design rationale
## Future Improvements
Areas where the codebase could be improved:
1. **Split Command.cc** - Break into logical groups (editing,
navigation, file ops, etc.)
2. **Complete Buffer::Line migration** - A few legacy editing functions
in Command.cc still use `Buffer::Rows()` directly (see lines 86-90
comment)
3. **Add more inline documentation** - Especially for complex algorithms
4. **Improve test coverage** - Add more edge case tests (current: 98
tests)
5. **Performance profiling** - Continue monitoring performance with
benchmark suite
6. **API documentation** - Consider adding Doxygen-style comments
---
Welcome aboard! Start small, read the code, and don't hesitate to ask
questions.