Version: 0.1 (Alpha)
Standard: C++20
Honeymoon is a terminal editor written in C++20. It bypasses
libraries like ncurses to interact directly with the terminal
driver via termios and ANSI escape codes.
The core is a synchronous blocking loop. It renders the state to a memory buffer, flushes it to the terminal, and waits for a single keypress.
// src/editor.hpp
void run() {
while (!should_quit) {
refresh_screen();
process_keypress();
}
terminal.write_raw("\x1b[2J\x1b[H"); // Clear on exit
}
We disable Canonical Mode (line buffering) and Echo to take full control of the terminal. This allows us to process every keystroke immediately.
// src/terminal.hpp
bool enable_raw_mode() {
tcgetattr(STDIN_FILENO, &orig_termios);
struct termios raw = orig_termios;
// Disable signals (Ctrl-C/Z), canonical mode, and echo
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
// Disable software flow control
raw.c_iflag &= ~(IXON);
// Turn off output processing (e.g. \n -> \r\n translation)
raw.c_oflag &= ~(OPOST);
return tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != -1;
}
Text is stored in a Gap Buffer. This is a
std::vector that maintains a "hole" at the cursor
position.
[ 'H', 'E', _, _, _, 'L', 'L', 'O' ]
^ Cursor (Gap Start)
Insertion is O(1) because we write into the gap. Moving the
cursor is O(N) because we must shift the gap using
memmove (via std::copy).
// src/buffer.hpp
void move_gap(size_type position) {
if (position < gap_start) {
size_type move = gap_start - position;
std::copy(buffer.begin() + position,
buffer.begin() + gap_start,
buffer.begin() + gap_end - move);
gap_start -= move; gap_end -= move;
}
// ...
}
To prevent tearing and flicker, we construct the entire frame in
a std::string before making a single
write syscall.
// src/editor.hpp
void refresh_screen() {
output_buffer.clear();
output_buffer.append("\x1b[?25l\x1b[H"); // Hide cursor, go home
draw_rows(); // Render text content
draw_status_bar(); // Render UI
place_cursor(); // Move real terminal cursor
output_buffer.append("\x1b[?25h"); // Show cursor
terminal.write_raw(output_buffer); // Atomic update
}
Escape sequences (like Arrow Keys) are parsed manually. We detect the Escape byte (27) and look ahead.
// src/terminal.hpp
Key read_key() {
char c;
if (read(STDIN_FILENO, &c, 1) != 1) return Key::None;
if (c == 27) { // Escape sequence start
char seq[3];
if (read(STDIN_FILENO, &seq[0], 1) != 1) return Key::Esc;
if (read(STDIN_FILENO, &seq[1], 1) != 1) return Key::Esc;
if (seq[0] == '[') {
switch (seq[1]) {
case 'A': return Key::ArrowUp;
case 'B': return Key::ArrowDown;
// ...
}
}
return Key::Esc;
}
return static_cast<Key>(c);
}
Honeymoon uses a trie (prefix tree) to handle
multi-key chords like C-x C-s. All keybindings are
loaded from keybinds.moon, making them fully
customizable without recompilation.
Each node in the trie represents a key in the sequence. Leaf nodes store action names that map to lambdas.
// src/editor.hpp
struct KeyNode {
std::map<Key, std::shared_ptr<KeyNode>> children;
std::string action;
};
void handle_input(EditorState &, Key k) {
auto it = current_node->children.find(k);
if (it != current_node->children.end()) {
current_node = it->second;
pending_keys.push_back(k);
if (!current_node->action.empty() && current_node->children.empty()) {
// Leaf node - execute action
actions[current_node->action]();
current_node = root_node;
pending_keys.clear();
}
}
// ...
}
The config parser expands M-x (Meta key) into
[Esc, x] automatically. This provides clean
Emacs-style syntax in the config file.
// src/keybinder.hpp
for (size_t i = 0; i < words.size() - 1; ++i) {
std::string token = words[i];
// Handle M- prefix (Meta key = Esc + key)
if (token.size() >= 3 && token[0] == 'M' && token[1] == '-') {
b.keys.push_back(Key::Esc);
token = token.substr(2); // Remove "M-"
}
Key k = key_from_string(token);
if (k != Key::None)
b.keys.push_back(k);
}
keybinds.moon uses a simple whitespace-separated
format:
# Comment lines start with # M-w copy # Meta-w copies selection C-x C-s save_file # Chord: Ctrl-x then Ctrl-s C-g cancel # Single key binding
Supported key formats:
C-x → Ctrl+xM-x → Meta/Alt+x (expands to Esc then x)Enter, Tab,
Backspace, Del, Esc
Up, Down, Left,
Right
a, Z,
/
keybinds.moon.
Edit the file and restart the editor to apply changes.