Add benchmarks for core operations, migration edge case tests, improved buffer I/O tests, and developer guide - Introduced `test_benchmarks.cc` for performance benchmarking of key operations in `PieceTable` and `Buffer`, including syntax highlighting and iteration patterns. - Added `test_migration_coverage.cc` to provide comprehensive tests for migration of `Buffer::Rows()` to `PieceTable` APIs, with edge cases, boundary handling, and consistency checks. - Enhanced `test_buffer_io.cc` with additional cases for save/load workflows, file handling, and better integration with the core API. - Documented architectural details and core concepts in a new `DEVELOPER_GUIDE.md`. Highlighted design principles, code organization, and contribution workflows.
16 KiB
kte Developer Guide
Welcome to kte development! This guide will help you understand the codebase, make changes, and contribute effectively.
Table of Contents
- Architecture Overview
- Core Components
- Code Organization
- Building and Testing
- Making Changes
- Code Style
- 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, andRendererinterfaces. - 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-copystring_view(fastest, 11x faster than Rows())GetLineString(row)- Returnsstd::stringcopy (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 groupsSwap.cc(1300 lines) - Crash recovery system (migrated to direct PieceTable operations)QtFrontend.cc(985 lines) - Qt integrationImGuiRenderer.cc(930 lines) - ImGui renderingPieceTable.cc(800 lines) - Core data structureBuffer.cc(763 lines) - Document model
Building and Testing
Build System
kte uses CMake with multiple build profiles:
# 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 executablekge- GUI editor executable (whenBUILD_GUI=ON)kte_tests- Test suiteimgui- Dear ImGui library (whenBUILD_GUI=ON)
Running Tests
# 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):
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 editstest_buffer_rows.cc- Buffer row operationstest_buffer_io.cc- File I/O (open, save, SaveAs)
-
Editing Operations:
test_command_semantics.cc- Command executiontest_kkeymap.cc- Keybinding systemtest_visual_line_mode.cc- Visual line selection
-
Search and Replace:
test_search.cc- Search functionalitytest_search_replace_flow.cc- Interactive search/replace
-
Text Reflow:
test_reflow_paragraph.cc- Paragraph reformattingtest_reflow_indented_bullets.cc- Indented list handling
-
Undo System:
test_undo.cc- Undo/redo operations
-
Swap Files (Crash Recovery):
test_swap_recorder.cc- Recording operationstest_swap_writer.cc- Writing swap filestest_swap_replay.cc- Replaying operationstest_swap_recovery_prompt.cc- Recovery UItest_swap_cleanup.cc- Cleanup logictest_swap_git_editor.cc- Git editor integration
-
Performance and Migration:
test_benchmarks.cc- Performance benchmarks for core operationstest_migration_coverage.cc- Buffer::Line migration validation
-
Integration Tests:
test_daily_workflows.cc- Real-world editing scenariostest_daily_driver_harness.cc- Workflow test infrastructure
Total: 98 tests across 22 test files. See docs/BENCHMARKS.md for
performance benchmark results.
Writing Tests
When adding new functionality:
- Add a test first - Write a failing test that demonstrates the desired behavior
- Use descriptive names - Test names should explain what's being validated
- Test edge cases - Empty buffers, EOF, beginning of file, etc.
- Use TestFrontend - For integration tests, use the programmatic test frontend
Example test structure:
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
-
Understand the change scope:
- Pure UI change? → Modify frontend only
- New editing operation? → Add command in
Command.cc - Core data structure? → Modify
PieceTableorBuffer
-
Find relevant code:
- Use
git grepor IDE search to find similar functionality - Check
Command.ccfor existing command patterns - Look at tests to understand expected behavior
- Use
-
Make the change:
- Follow existing code style (see below)
- Add or update tests
- Update documentation if needed
-
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 thanRows()), orGetLineString()when you need a copy. AvoidRows()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()andEndGroup()to group related operations. - GetLineView() lifetime - The returned
string_viewis only valid until the next buffer modification. Use immediately or copy tostd::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_casewith trailing underscore for members ( e.g.,total_size_,line_index_) - Constants:
snake_caseorUPPER_CASEdepending 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:
// 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
}
Common Tasks
Adding a New Command
- Define the command function in
Command.cc:
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;
}
- Register the command in
InstallDefaultCommands():
CommandRegistry::Register({
CommandId::MyFeature,
"my-feature",
"Description of what it does",
cmd_my_feature
});
-
Add keybinding in the appropriate
InputHandler(e.g.,TerminalInputHandler.cc). -
Write tests in
tests/test_command_semantics.ccor a new test file.
Adding a New Frontend
-
Implement the three interfaces:
Frontend- Lifecycle managementInputHandler- Event → Command translationRenderer- Draw the editor state
-
Study existing implementations:
TerminalFrontend- Simplest, good starting pointImGuiFrontend- More complex, shows GUI patterns
-
Register in
main.ccto make it selectable.
Modifying the PieceTable
The PieceTable is performance-critical. When making changes:
- Understand the piece list - Each piece references a range in
either
original_oradd_buffer - Maintain invariants:
total_size_must match sum of piece lengths- Line index must be invalidated on content changes
- Version must increment on mutations
- Test thoroughly - Use
test_piece_table.ccrandom edit test as a reference model - Profile if needed - Large file performance is a key goal
Adding Syntax Highlighting
-
Create a new highlighter in
syntax/directory:- Inherit from
HighlighterEngine - Implement
HighlightLine()method
- Inherit from
-
Register in
HighlighterRegistry(syntax/HighlighterRegistry.cc) -
Add file extension mapping in the registry
-
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:
- Split Command.cc - Break into logical groups (editing, navigation, file ops, etc.)
- Complete Buffer::Line migration - A few legacy editing functions
in Command.cc still use
Buffer::Rows()directly (see lines 86-90 comment) - Add more inline documentation - Especially for complex algorithms
- Improve test coverage - Add more edge case tests (current: 98 tests)
- Performance profiling - Continue monitoring performance with benchmark suite
- API documentation - Consider adding Doxygen-style comments
Welcome aboard! Start small, read the code, and don't hesitate to ask questions.