A terminal-based text editor written in C from scratch. This project serves primarily as a personal learning exercise in building a TUI application, exploring low-level terminal I/O, and structuring a C application in a modular way, all without dependencies like ncurses.
While it includes modern features, it is a hobby project with known limitations and areas for improvement. It is not intended to be a production-ready editor.
- Handwritten UI Toolkit: A custom-built widget system from scratch, featuring a status bar, main menu, notifications, and an editor widget.
- Syntax Highlighting: A flexible, regex-based highlighting engine. Syntaxes are defined in simple
.inifiles, making it easy to add new languages. - Efficient UTF-8 Support: A revised string library that handles UTF-8 characters correctly and efficiently, with optimized indexing for large strings.
- Modern Text Editing: A gap buffer implementation for the current line allows for efficient character insertion and deletion.
- Advanced Text Layout: Supports line wrapping that correctly handles wide characters and tab expansion.
- Asynchronous Events: A timer system for timed events like cursor blinking and auto-hiding notifications.
Make sure to have CMake, a C compiler (like GCC or Clang) with the C standard library, and the POSIX C library available.
cmake -B build
cmake --build buildTo open a file, simply pass its name as an argument:
./build/clieditor src/main.cclieditor <filename>: Open a specific file.clieditor -s <syntax> <filename>: Open a file and force a specific syntax highlighting profile (e.g.,-s c ...). The name corresponds to a<syntax>.inifile in thedata/syntaxdirectory.clieditor -h: Show the help message.
The editor is designed with a clean separation of concerns, organized into distinct modules. This makes the codebase easier to navigate, maintain, and extend.
main.c: The application's entry point. It handles initialization, argument parsing, and runs the main event loop.common/: A collection of fundamental, reusable data structures and utilities. This includes the coreStringlibrary, a hashTable, a dynamicStack, theiniparser, and theConfigmanager.io/: The layer for all direct interaction with the system, such as terminal I/O (Terminal,Screen), userInput, file operations (File), and the eventTimer.display/: An abstract rendering layer. It defines what to draw through primitives likeWidget,Canvas, andCell, but not how it's rendered on screen.document/: The core data model of the editor, managing the text content independently of the UI. This includes theTextBuffer,TextLayoutfor visual calculation, andTextEditfor modification logic.syntax/: The syntax highlighting engine. It includes theSyntaxDefinitionloader, theSyntaxHighlightingprocessor, and bindings to connect it to the editor's text layout.widgets/: A library of all concrete UI components, built upon thedisplayprimitives. It is divided intoprimitives(likeLabel,Menu) andcomponents(likeEditorView,BottomBar), withAppserving as the root of the widget hierarchy.
The editor is built on several key concepts that are crucial to understanding its design and its current limitations.
The editor uses a custom, UTF-8 aware string library designed for a balance of performance and memory efficiency.
- Representation: A
Stringis a standardchar*byte buffer, keeping memory usage minimal. - Efficient Indexing: To overcome the O(N) cost of character indexing in variable-length UTF-8 strings, the
Stringstruct maintains an auxiliary array (multibytes) that caches the byte offsets of multi-byte characters. This allows for O(log N) character-based indexing via binary search.
The UI is a tree of widgets, with App at its root. This system was built entirely from scratch as a learning exercise.
- Inheritance: Polymorphism is achieved using a
WidgetOpsstruct within the baseWidget, which acts as a "vtable" pointing to a widget's specific implementations fordraw,on_input,on_resize, etc. - Drawing: The system uses a double-buffering strategy. Each widget draws to a
Canvas, and the final canvas is compared against the previous frame to write only changedCells to the terminal. - Limitations: As a from-scratch implementation, the widget, drawing, and focus systems are quite basic. They demonstrate core TUI concepts but could be significantly improved for more complex UI scenarios and are a known area of weakness.
The core of the text editing functionality is split into three main parts:
TextBuffer: This is the data model. It stores the document's text as a doubly-linked list ofLinestructs. For efficient editing, it employs a gap buffer within theStringof the current line. Insertions and deletions happen at the gap, which is almost always an O(1) operation.- Note: This is a good example of the project's learning nature. The performance benefit of the gap buffer is currently limited, as other features (namely the syntax highlighter) frequently force the gap to be merged back into the text. Optimizing this interaction is a known area for future work.
TextLayout: This is the "view model". It is responsible for calculating how the text in theTextBuffershould be displayed, handling line wrapping, tab expansion, and scrolling.TextEdit: This is the "controller". It provides an API for all text manipulations (e.g.,TextEdit_InsertChar,TextEdit_MoveUp), modifying theTextBufferand marking theTextLayoutas dirty.
The highlighting engine is designed to be extensible.
- Declarative Definitions: Syntax rules are defined in simple
.inifiles, consisting of a[meta]section and multiple[block:...]sections. - Regex-Based Blocks: Each block is defined by a
startregex and an optionalendregex. Blocks can be nested by specifyingchild_blocks. - Stateful Parsing: The engine (
SyntaxHighlighting) processes text line by line, maintaining a stack of open blocks. It uses the context from the end of the previous line to correctly highlight constructs that span multiple lines.
If you're new to the codebase, here's a recommended path:
- Start at
src/main.c: Understand the initialization sequence and the main event loop. - Explore
common/: Look atstring.handtable.h, as these are used everywhere. - Understand the UI:
display/widget.his the base for all UI elements, andwidgets/components/editor.his the main editor widget. - Dive into the Editor Core:
document/textbuffer.h(storage),document/textlayout.h(display logic), andsyntax/highlighting.h(highlighting logic).
The project follows a consistent C coding style.
- Naming Conventions:
- Types and structs are
PascalCase(e.g.,TextBuffer). - Public functions are
Module_Function(e.g.,TextBuffer_Init). - Enums and macros are
ALL_CAPS. - Object-Oriented C: The code emulates object-oriented patterns. Modules are centered around a primary struct, and functions that operate on it take a pointer to that struct as their first argument (e.g.,
void Widget_Draw(Widget *self, ...)). - Memory Management: All memory is managed manually.
_Createfunctionsmallocresources, and_Destroyfunctionsfreethem._Initand_Deinitpairs manage the contents of a struct without allocating/freeing the struct itself. - Formatting: 4-space indentation. The opening brace
{is always placed on the same line as the corresponding function declaration or control statement (if,for,while, etc.).
This project is actively used for learning and experimentation. Here are some of the planned improvements and refactoring goals:
-
Adopt a Rope Data Structure: Replace the current
TextBufferimplementation (a linked list of lines) with a Rope. This more advanced data structure should offer better performance for large files and complex editing operations. This will also require a corresponding redesign of the syntax highlighting engine to work efficiently with the new structure. -
Implement Common Editor Features: While not the primary focus, implementing standard text editor functionalities such as searching, and find and replace, remains a long-term goal.
-
Simplify the Widget System: Refactor the core widget system, particularly the drawing and focus management logic. The goal is to create a simpler, more robust implementation that is easier to maintain, since the application's UI needs are relatively modest and centered around the main editor component.
-
Refactor Application Setup: Move the responsibility for creating and connecting widgets from the
mainfunction into theAppmodule. This will centralize UI setup and makeAppthe true root of the application, improving modularity. -
Improve
const-Correctness in the String Library: Modify theStringimplementation to rebuild its internal UTF-8 indexing cache immediately after any modification. This change will allow read-only functions (likeString_GetChar) to accept aconst String*, enforcing betterconst-correctness throughout the API.
This project is licensed under the GNU General Public License v3.0 (GPL-3.0).
You may obtain a copy of the license at: https://www.gnu.org/licenses/gpl-3.0.html
