C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
224: Building a Simple Text Editor
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
EditorStateand achar cas arguments. - You must use
reallocto expand the current line's memory if it's full. - Use
memmoveto shift existing characters to the right of the cursor to make room for the new character. - Update the
cursor_xposition 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
}There are no comments for now.