root-editor

A terminal based text editor written in C. Made and tested on Arch Linux and Debian-based distros.

← Back to portfolio

Installation

root-editor is a terminal text editor written in C. It has been made and tested on Arch Linux and Debian-based distros (Tails specifically), and should work fine on all major distributions.

  1. Clone the repository:
    git clone https://github.com/execRooted/root-editor.git
  2. Navigate to the project directory:
    cd root-editor
  3. Run the installer for system-wide installation:
    sudo ./install.sh

If installed system-wide, simply run:

re [filename]

or

root-editor [filename]

Features

  • Syntax Highlighting — supports highlighting for various programming languages (configurable via config/syntax.json).
  • Plugin Support — extensible architecture allowing users to add custom functionality via shared libraries (.so files on Linux).

Control keys

KeybindAction
Ctrl+QQuit
Ctrl+SSave
Ctrl+OOpen file
Ctrl+WEnter select mode
Ctrl+ASelect all
Ctrl+XCut
Ctrl+CCopy
Ctrl+VPaste
Ctrl+FFind (enters Find mode)
Ctrl+RReplace
Ctrl+LJump to line
Ctrl+TAuto indent
Ctrl+KAuto complete
Ctrl+UComment toggle
Ctrl+H / F1Help

Function keys

KeyAction
F1Help
F2Find
F3Replace
F4Cut
F5Copy
F6Paste
F7Word wrap
F8Syntax highlighting
F9Autosave

Selection mode

root-editor features a selection mode that allows you to select and edit text efficiently. When in selection mode, syntax highlighting is disabled to ensure clear visibility of selected text.

Entering selection mode

  • Ctrl + W — enter selection mode.
  • Shift + Arrow Keys — start selection and extend it (works left → right and top → bottom).

Navigation in selection mode

  • Arrow Keys — move cursor and extend selection.
  • Enter — move cursor down (like the down arrow).
  • Backspace — deletes selection/word and exits selection mode.
  • Home / End — move to line start/end and extend selection.

Exiting selection mode

  • Esc — first press marks for exit (shows SELECT in the status bar).
  • Enter — second press exits selection mode, re-enables syntax highlighting, and enables writing.

Selection operations

  • Ctrl+W — start selection mode.
  • Ctrl+A — select all text.
  • Ctrl+X — cut selected text.
  • Ctrl+C — copy selected text.
  • Delete / Backspace — delete selected text (when a selection exists).

Visual feedback

  • Selected text appears with reverse video (highlighted background).
  • Status bar shows SELECT when in selection mode.
  • Syntax highlighting is disabled during selection for better visibility.

Find mode

Enter Find mode with Ctrl+F. It is a straightforward implementation to help you find text more easily.

Exiting Find mode

  • Esc — first press marks for exit (shows FIND in the status bar).
  • Enter — second press exits Find mode, re-enables syntax highlighting, and enables writing.

Plugin system

root-editor features a powerful and flexible plugin system that allows developers to extend the editor's functionality through shared libraries (.so files on Linux). Plugins can hook into various editor events, enabling custom features such as enhanced rendering, specialized key handling, file processing, and syntax highlighting extensions.

Plugins are loaded dynamically at runtime and can access the full editor state, including the current file content, cursor position, selection, and configuration settings. The system is designed to be safe and stable, with proper error handling to prevent crashes from malfunctioning plugins.

Plugin architecture

Plugins interact with the editor through a well-defined interface consisting of hook functions that are called at specific points during editor operation. Each plugin must export a PluginInterface structure that describes the plugin and provides function pointers for the hooks it implements.

Available hook functions

HookDescription
on_loadCalled when the plugin is first loaded. Initialize plugin-specific data, allocate memory, or set up initial state. Receives EditorState*.
on_unloadCalled when the plugin is unloaded (editor quits or manual unload). Clean up resources and free memory. Receives EditorState*.
on_keypressCalled for every key press before default handling. Return non-zero to indicate the key was handled and should not be processed by default. Receives EditorState* and key code int ch.
on_renderCalled during rendering, after main content is drawn but before the final refresh. Add custom UI, overlays, or status info. Receives EditorState*.
on_file_loadCalled after a file is successfully loaded. Receives EditorState* and const char* filename.
on_file_saveCalled before a file is saved. Allows modifying content or preprocessing. Receives EditorState* and const char* filename.
on_quitCalled when the editor is about to quit. Final cleanup or saving plugin state. Receives EditorState*.
on_highlight_lineCalled for each line during syntax highlighting. Implement custom highlighting rules. Receives EditorState*, line number int, screen row int, line number width int, and horizontal scroll offset int.

Creating a plugin

  1. Create a plugin source file in the plugins/ directory (e.g. my_plugin.c).
  2. Include the necessary headers:
    #include "../src/editor.h"
    #include <stdlib.h>  // For memory management
    #include <string.h>  // For string operations
  3. Implement hook functions. Each hook receives an EditorState* giving access to the full editor state:
    static void my_on_render(EditorState* state) {
        // Access editor state
        int cursor_y = state->cursor_y;
        int cursor_x = state->cursor_x;
    
        // Add custom rendering logic here
        // Use ncurses functions like mvprintw(), attron(), etc.
    }
    
    static int my_on_keypress(EditorState* state, int ch) {
        if (ch == 'm') {  // Example: handle 'm' key
            // Custom key handling
            return 1;  // Return 1 to prevent default handling
        }
        return 0;  // Return 0 to allow default handling
    }
  4. Define the PluginInterface struct as a static instance:
    static PluginInterface plugin_interface = {
        .name = "My Custom Plugin",
        .version = "1.0.0",
        .description = "A plugin that adds custom functionality to Root Editor",
        .on_load = NULL,           // Set to your function or NULL
        .on_unload = NULL,         // Set to your function or NULL
        .on_keypress = my_on_keypress,
        .on_render = my_on_render,
        .on_file_load = NULL,
        .on_file_save = NULL,
        .on_quit = NULL,
        .on_highlight_line = NULL
    };
  5. Export the interface by providing get_plugin_interface:
    PluginInterface* get_plugin_interface(void) {
        return &plugin_interface;
    }
  6. Compile the plugin using the provided Makefile:
    cd plugins/
    make
    This generates a .so file with the same name as your source file.

Accessing editor state

Plugins receive a pointer to the EditorState struct, which contains all the editor's current state. Key fields include:

  • char** lines — array of strings representing file content.
  • int line_count — number of lines in the file.
  • int cursor_x, cursor_y — current cursor position.
  • char filename[256] — current file name.
  • int select_mode — whether text is selected.
  • int select_start_x, select_start_y, select_end_x, select_end_y — selection boundaries.
  • Various configuration flags like syntax_enabled, autosave_enabled, etc.
Important: Always check for NULL pointers and validate array bounds when accessing editor state to prevent crashes.

Loading and managing plugins

  • Automatic loading — plugins in the plugins/ directory are automatically loaded when the editor starts.
  • Manual loading — use the interactive plugin loader (via editor commands) to load plugins from custom locations.
  • System-wide installation — after building new plugins, run sudo ./install.sh to install them system-wide.
  • Plugin management — the editor provides commands to list, load, and unload plugins dynamically.

Example plugin

See plugins/word_count_plugin.c for a complete example that displays the word count in the status bar. This plugin demonstrates:

  • Accessing selected text or entire file content.
  • Using ncurses functions for rendering.
  • Proper memory management.
  • Integration with the editor's rendering pipeline.