Skip to Content
Course content

224: Building a Simple Text Editor

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You might be tempted to think that building a text editor is essentially just a fancy wrapper around fgets() and fprintf(). The common misconception is that you can simply read an entire file into one massive string, manipulate that string in memory using strcpy or strcat, and then dump the whole thing back to disk whenever the user hits save. It sounds logical, but in practice, it's a performance disaster.

Imagine you have a 10MB text file. If you store it as one contiguous block of memory and the user decides to insert a single character at the very beginning of the file, you have to shift every single one of those 10 million bytes one position to the right just to make room. Do that for every keystroke, and your editor will start lagging before the user even finishes their first sentence. I've seen a lot of students hit this wall; they wonder why their "simple" editor feels like it's running through molasses.

The 'Read-Modify-Write' Fallacy vs. The Buffer Approach

Instead of treating the document as one giant string, we need to think about a "buffer." For a simple editor, the most intuitive starting point is a dynamic array of lines. Each line is its own allocated string. When you insert a character, you only shift the bytes within that specific line, not the entire document. If you add a new line, you're just adjusting a few pointers in your array.

Here is a basic way I'd structure the state of our editor:

struct EditorState {
    char **lines;      // Array of strings (each is a line)
    int line_count;    // Number of lines currently in the file
    int cursor_x;      // Character position in the current line
    int cursor_y;      // Which line the cursor is on
};

By decoupling the lines, we've turned a global memory shift into a local one. It's a massive win for efficiency.

Breaking Out of Canonical Mode

Now, there's a hurdle you'll hit immediately: the terminal. By default, C programs operate in "canonical mode." This means the operating system buffers everything the user types until they hit the Enter key. For a text editor, this is useless. You need to know the instant the user presses 'j' to move the cursor down or 'i' to enter insert mode.

To fix this, we have to dive into termios.h to put the terminal into "raw mode." I'll be honest—this part of the C API feels clunky and ancient because it is. You're essentially telling the terminal, "Stop helping me; just give me the raw bytes as they happen."

#include <termios.h>
#include <unistd.h>

void enableRawMode() {
    struct termios raw;
    tcgetattr(STDIN_FILENO, &raw); // Get current settings
    
    // Disable echoing (so characters don't appear twice) 
    // and disable canonical mode (read byte by byte)
    raw.c_lflag &= ~(ECHO | ICANON); 
    
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}

Once this is active, getchar() won't wait for a newline. It will return the moment a key is pressed. This is the "heartbeat" of your editor's input loop.

The Render-Loop Pattern

A text editor isn't like a command-line tool that prints and exits. It's more like a game. You have a loop that constantly clears the screen, draws the current state of the buffer, and then waits for a single input to update that state.

I recommend using ANSI escape codes for this. Instead of clearing the whole screen (which causes a distracting flicker), you can move the cursor to the top-left corner using \x1b[H. This allows you to overwrite the previous frame smoothly. Your loop should look roughly like this: Clear/Reset $\rightarrow$ Draw Buffer $\rightarrow$ Handle Input $\rightarrow$ Repeat.




📋 Practical Task

Implement the Line-Insertion Logic for the Buffer

Your task is to write a function insert_character that handles the actual modification of the text buffer. You are provided with the EditorState struct. Your function must handle the logic of inserting a character at the current cursor position.

Requirements:

  • The function should take the EditorState and a char c as arguments.
  • You must use realloc to expand the current line's memory if it's full.
  • Use memmove to shift existing characters to the right of the cursor to make room for the new character.
  • Update the cursor_x position after the insertion.
  • Ensure the string remains null-terminated.

Starter Code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct EditorState {
    char **lines;
    int line_count;
    int cursor_x;
    int cursor_y;
};

void insert_character(struct EditorState *state, char c) {
    // Your implementation here
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.