โ˜… Final Challenge

โ€บ duck โ€” a Terminal Text Editor

100 pts

Assembly time. Every layer you've built converges into one program โ€” call it duck โ€” that opens a file in a terminal, edits it, and saves it back. The layering you'll wire together, bottom to top:

             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   output โ—„โ”€โ”€โ”ค editor_render     frame -> escape sequences โ”‚
             โ”‚   editor_scroll   viewport invariant        โ”‚
             โ”‚   cx_to_rx        tabs -> screen columns    โ”‚
             โ”‚   format_status_bar                         โ”‚
             โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    state    โ”‚ struct editor     lines, cursor, dirty      โ”‚
             โ”‚   insert/delete/newline/move/open/save      โ”‚
             โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    input โ”€โ”€โ–บโ”‚ editor_feed_input bytes -> keys -> actions  โ”‚
             โ”‚   key_decode      escape-sequence grammar   โ”‚
             โ”‚   editor_handle_key                dispatch โ”‚
             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The starter provides the editing core and file I/O (reference versions of your earlier work โ€” swap in your own; they're yours now) plus the abuf. You bring three of your solutions and write two integration functions:

Paste in (from your previous challenges): key_decode (parse-keys), editor_handle_key (main-loop-impl), format_status_bar (status-line-impl).

Write new:

editor_render(e, ab) โ€” serialize one complete frame into the abuf, exactly this recipe:

  1. Call editor_scroll(e) (also yours to write โ€” the two-axis invariant from the scrolling challenge, using render columns).
  2. \x1b[?25l\x1b[H โ€” hide cursor, home.
  3. For each of the screen_rows text rows: the visible slice of file row rowoff + i โ€” render columns [coloff, coloff + screen_cols) with tabs expanded to TABSTOP-column stops โ€” or a single ~ for rows past the end of the file; then \x1b[K and \r\n.
  4. The status bar: \x1b[7m, then format_status_bar(...) at exactly screen_cols wide, then \x1b[m.
  5. Park the terminal cursor on the editor cursor: \x1b[<cursor_row - rowoff + 1>;<rx - coloff + 1>H (1-based; rx from cx_to_rx).
  6. \x1b[?25h โ€” show cursor.

editor_feed_input(e, buf, len) โ€” drain a chunk of raw input: loop key_decode over the buffer; for each event, dispatch through editor_handle_key; execute the actions (ED_SAVE โ†’ editor_save(e, e->filename); ED_FIND โ†’ ignore here; ED_QUIT โ†’ stop processing and return ED_QUIT). When key_decode returns 0 (incomplete trailing sequence) or the buffer runs out, return ED_CONTINUE. (A real loop buffers the incomplete tail and applies the ESC timeout โ€” the DEMO main does; the graded function may discard it.)

The tests run your editor headless through a full scripted session: load a file from disk, page and arrow around it (checking the rendered frames: viewport contents, ~ filler, status bar, cursor parking), type into it, save with Ctrl+S, verify the bytes that landed on disk, and quit with the dirty-buffer confirmation dance. If the previous challenges pass, most of your work here is careful plumbing โ€” which is the point: systems are assembled from parts that were designed to be assembled.

When it's green, run the real thing:

cc -std=c17 -Wall -DDEMO solution.c -o duck
./duck some_file.txt

Raw mode, alternate screen, arrows, editing, Ctrl+S, Ctrl+Q. A terminal program you built from read(2) up. Take the moment โ€” then go read kilo's source and see how close you landed, and the extended-reading list for where to go next (windows! syntax highlighting! a pty of your own with a shell inside!).

Log in to submit a solution and earn points.