// lesson: main-loop

The Event Loop, Signals, and Cleanup

Every piece is on the bench. The event loop is the engine block they bolt onto โ€” and it's small, because you've already built everything hard about it:

enable raw mode; enter the alternate screen;
while (running) {
    editor_scroll(e);                     /* enforce the invariant  */
    editor_render(e, &frame);             /* model -> one buffer    */
    write_all(STDOUT_FILENO, frame...);   /* -> one write           */
    wait for input (poll, with a timeout);
    read; decode key events; dispatch each;
}
leave the alternate screen; restore termios;

Render, wait, dispatch, repeat. vim, less, htop, tmux: this loop. Three topics deserve real attention before you wire it: interruptions, crashes, and where the logic should live.

SIGWINCH and the rules of signal handlers

When the user drags the terminal's corner, the kernel sends your process SIGWINCH (window change). You must re-query the size (ioctl(fd, TIOCGWINSZ, &winsize)), resize the editor's viewport, and repaint. The temptation is to do all that in the signal handler. Don't โ€” a signal handler interrupts your program at an arbitrary instruction. Mid-malloc, mid-printf, mid anything. Call anything non-reentrant from the handler and you corrupt whatever the interrupted code was in the middle of; POSIX blesses only a short list of "async-signal-safe" functions (write is on it; malloc, printf, and friends are not).

The professional pattern makes the handler trivial and moves the work to the loop:

static volatile sig_atomic_t g_resized = 0;
static void on_winch(int sig) { (void)sig; g_resized = 1; }

/* in the loop, at the top: */
if (g_resized) { g_resized = 0; requery_size(e); /* repaint */ }

volatile sig_atomic_t is the one type C guarantees is safe to write from a handler and read from normal code. And notice the loop is already shaped to notice the flag promptly: the signal interrupts the blocked poll (that EINTR you handled properly in wait_readable โ€” this is why), the loop comes around, sees the flag, repaints at the new size.

Dying well

Your program owns the user's terminal and their unsaved text; it must exit cleanly even when things go wrong.

  • Normal exits: leave the alternate screen, show the cursor, restore termios. Registering the restore with atexit() gives every exit() path the cleanup for free.
  • Errors: the classic die() helper โ€” restore terminal first, then perror + exit(1), so the error message prints onto a sane screen instead of into raw-mode garbage.
  • Ctrl+C: raw mode disabled ISIG, so it's just a byte (0x03) โ€” your dispatch decides what it means. (Ours ignores it; Ctrl+Q is quit.)
  • Even on SIGSEGV, a crash-handler that write()s the restore-terminal escape sequence before dying spares the user a wrecked shell (remember: write is signal-safe; this is the same trick as the resize flag, one rung more desperate).

Dispatch is policy; keep it pure

The last design decision, and the one this challenge grades: what happens when a key arrives? Resist writing it inline in the loop. Make it a function โ€”

enum ed_action { ED_CONTINUE, ED_QUIT, ED_SAVE, ED_FIND };
enum ed_action editor_handle_key(struct editor *e, struct key_event k);

โ€” that mutates the editor and reports what the loop should do, touching no I/O itself. Saving involves a filesystem; quitting involves the loop's control flow; so the function returns intent (ED_SAVE, ED_QUIT) and the loop executes it. The payoff is the one you've collected throughout the course: the entire keyboard personality of the editor becomes a value-in, value-out function the tests (and you) can drive without a terminal.

One piece of dispatch state earns its keep: quit confirmation. Ctrl+Q with unsaved changes shouldn't kill the buffer on the spot; the convention (kilo's, among others) is press-again-to-confirm: first Ctrl+Q on a dirty buffer arms quit_pending and continues; a second consecutive Ctrl+Q quits; any other key disarms it. It's a two-state state machine โ€” after the VT parser, barely worth the name โ€” but it's the difference between an editor and a data-loss device.

โ€บ Key Dispatch

30 pts

The starter provides a working editing core (line buffer, movement, all from your earlier challenges โ€” reference versions included so this challenge stands alone). You write editor_handle_key:

key effect returns
printable KEY_CHAR (incl. tab) insert, dirty = 1 ED_CONTINUE
KEY_ENTER split line, dirty = 1 ED_CONTINUE
KEY_BACKSPACE / KEY_DELETE the usual, dirty = 1 ED_CONTINUE
arrows / Home / End / PgUp / PgDn editor_move ED_CONTINUE
Ctrl+S โ€” ED_SAVE
Ctrl+F โ€” ED_FIND
Ctrl+Q quit-confirm logic below ED_QUIT / ED_CONTINUE
anything else ignored ED_CONTINUE

Quit-confirm: Ctrl+Q returns ED_QUIT immediately if the buffer is clean or quit_pending is set; otherwise it sets quit_pending = 1 and returns ED_CONTINUE. Every key other than Ctrl+Q resets quit_pending to 0.

Log in to submit a solution and earn points.