about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2017-05-13 12:42:17 -0700
committerKartik K. Agaram <vc@akkartik.com>2017-05-13 12:42:17 -0700
commit0c0d1ea5cdb96a98e7eb62edbd1acb534ae12940 (patch)
tree7b64a6e98fb16d2bf02c5003acc2c14d7d1d6043
parent8195ed4ee94f490d377b91caa0d79f21dd3e86ed (diff)
downloadmu-0c0d1ea5cdb96a98e7eb62edbd1acb534ae12940.tar.gz
3854
Revert commits 3824, 3850 and 3852. We'll redo them more carefully.
-rw-r--r--080display.cc91
-rw-r--r--081print.mu59
-rw-r--r--100trace_browser.cc10
-rw-r--r--cannot_write_tests_for10
-rw-r--r--edit/001-editor.mu2
-rw-r--r--edit/002-typing.mu7
-rw-r--r--edit/004-programming-environment.mu31
-rw-r--r--edit/005-sandbox.mu27
-rw-r--r--edit/006-sandbox-copy.mu2
-rw-r--r--edit/007-sandbox-delete.mu2
-rw-r--r--edit/008-sandbox-edit.mu2
-rw-r--r--edit/009-sandbox-test.mu2
-rw-r--r--edit/010-sandbox-trace.mu2
-rw-r--r--sandbox/001-editor.mu2
-rw-r--r--sandbox/002-typing.mu7
-rw-r--r--sandbox/004-programming-environment.mu26
-rw-r--r--sandbox/005-sandbox.mu33
-rw-r--r--sandbox/006-sandbox-copy.mu2
-rw-r--r--sandbox/007-sandbox-delete.mu2
-rw-r--r--sandbox/008-sandbox-edit.mu2
-rw-r--r--sandbox/009-sandbox-test.mu2
-rw-r--r--sandbox/010-sandbox-trace.mu2
-rw-r--r--termbox/termbox.c156
-rw-r--r--termbox/termbox.h36
24 files changed, 402 insertions, 115 deletions
diff --git a/080display.cc b/080display.cc
index 691b6a00..847d114c 100644
--- a/080display.cc
+++ b/080display.cc
@@ -7,6 +7,7 @@
 :(before "End Globals")
 int Display_row = 0;
 int Display_column = 0;
+bool Autodisplay = true;
 
 :(before "End Includes")
 #define CHECK_SCREEN \
@@ -37,7 +38,6 @@ case OPEN_CONSOLE: {
 :(before "End Primitive Recipe Implementations")
 case OPEN_CONSOLE: {
   tb_init();
-  tb_clear();
   Display_row = Display_column = 0;
   int width = tb_width();
   int height = tb_height();
@@ -84,6 +84,21 @@ case CLEAR_DISPLAY: {
 }
 
 :(before "End Primitive Recipe Declarations")
+SYNC_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "sync-display", SYNC_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SYNC_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SYNC_DISPLAY: {
+  CHECK_SCREEN;
+  tb_sync();
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
 PRINT_CHARACTER_TO_DISPLAY,
 :(before "End Primitive Recipe Numbers")
 put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY);
@@ -118,7 +133,7 @@ case PRINT_CHARACTER_TO_DISPLAY: {
   int height = (h >= 0) ? h : 0;
   int width = (w >= 0) ? w : 0;
   int c = ingredients.at(0).at(0);
-  int color = TB_WHITE;
+  int color = TB_BLACK;
   if (SIZE(ingredients) > 1) {
     color = ingredients.at(1).at(0);
   }
@@ -133,6 +148,7 @@ case PRINT_CHARACTER_TO_DISPLAY: {
       Display_column = 0;
       ++Display_row;
       tb_set_cursor(Display_column, Display_row);
+      if (Autodisplay) tb_present();
     }
     break;
   }
@@ -141,6 +157,7 @@ case PRINT_CHARACTER_TO_DISPLAY: {
       tb_change_cell(Display_column-1, Display_row, ' ', color, bg_color);
       --Display_column;
       tb_set_cursor(Display_column, Display_row);
+      if (Autodisplay) tb_present();
     }
     break;
   }
@@ -148,6 +165,7 @@ case PRINT_CHARACTER_TO_DISPLAY: {
     ++Display_column;
     tb_set_cursor(Display_column, Display_row);
   }
+  if (Autodisplay) tb_present();
   break;
 }
 
@@ -194,6 +212,7 @@ case MOVE_CURSOR_ON_DISPLAY: {
   Display_row = ingredients.at(0).at(0);
   Display_column = ingredients.at(1).at(0);
   tb_set_cursor(Display_column, Display_row);
+  if (Autodisplay) tb_present();
   break;
 }
 
@@ -213,6 +232,7 @@ case MOVE_CURSOR_DOWN_ON_DISPLAY: {
   if (Display_row < height-1) {
     ++Display_row;
     tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -231,6 +251,7 @@ case MOVE_CURSOR_UP_ON_DISPLAY: {
   if (Display_row > 0) {
     --Display_row;
     tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -251,6 +272,7 @@ case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
   if (Display_column < width-1) {
     ++Display_column;
     tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -269,6 +291,7 @@ case MOVE_CURSOR_LEFT_ON_DISPLAY: {
   if (Display_column > 0) {
     --Display_column;
     tb_set_cursor(Display_column, Display_row);
+    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -284,6 +307,7 @@ void move_cursor_to_start_of_next_line_on_display() {
   else Display_row = 0;
   Display_column = 0;
   tb_set_cursor(Display_column, Display_row);
+  if (Autodisplay) tb_present();
 }
 
 :(before "End Primitive Recipe Declarations")
@@ -318,6 +342,67 @@ case DISPLAY_HEIGHT: {
   break;
 }
 
+:(before "End Primitive Recipe Declarations")
+HIDE_CURSOR_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "hide-cursor-on-display", HIDE_CURSOR_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case HIDE_CURSOR_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case HIDE_CURSOR_ON_DISPLAY: {
+  CHECK_SCREEN;
+  tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SHOW_CURSOR_ON_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "show-cursor-on-display", SHOW_CURSOR_ON_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SHOW_CURSOR_ON_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SHOW_CURSOR_ON_DISPLAY: {
+  CHECK_SCREEN;
+  tb_set_cursor(Display_row, Display_column);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+HIDE_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "hide-display", HIDE_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case HIDE_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case HIDE_DISPLAY: {
+  CHECK_SCREEN;
+  Autodisplay = false;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SHOW_DISPLAY,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "show-display", SHOW_DISPLAY);
+:(before "End Primitive Recipe Checks")
+case SHOW_DISPLAY: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SHOW_DISPLAY: {
+  CHECK_SCREEN;
+  Autodisplay = true;
+  tb_present();
+  break;
+}
+
 //:: Keyboard/mouse management
 
 :(before "End Primitive Recipe Declarations")
@@ -441,6 +526,7 @@ case CLEAR_LINE_ON_DISPLAY: {
     tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_BLACK);
   }
   tb_set_cursor(Display_column, Display_row);
+  if (Autodisplay) tb_present();
   break;
 }
 
@@ -466,5 +552,6 @@ case CLEAR_DISPLAY_FROM: {
       tb_change_cell(column, row, ' ', TB_WHITE, TB_BLACK);
     }
   }
+  if (Autodisplay) tb_present();
   break;
 }
diff --git a/081print.mu b/081print.mu
index 1d3bf8dc..487696c5 100644
--- a/081print.mu
+++ b/081print.mu
@@ -50,6 +50,13 @@ def clear-screen screen:&:screen -> screen:&:screen [
   *screen <- put *screen, cursor-column:offset, 0
 ]
 
+def sync-screen screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  return-if screen  # do nothing for fake screens
+  sync-display
+]
+
 def fake-screen-is-empty? screen:&:screen -> result:bool [
   local-scope
   load-ingredients
@@ -101,27 +108,15 @@ def print screen:&:screen, c:char -> screen:&:screen [
   row:num <- get *screen, cursor-row:offset
   row <- round row
   legal?:bool <- greater-or-equal row, 0
-  {
-    break-if legal?
-    row <- copy 0
-  }
+  return-unless legal?
   legal? <- lesser-than row, height
-  {
-    break-if legal?
-    row <- subtract height, 1
-  }
+  return-unless legal?
   column:num <- get *screen, cursor-column:offset
   column <- round column
   legal? <- greater-or-equal column, 0
-  {
-    break-if legal?
-    column <- copy 0
-  }
+  return-unless legal?
   legal? <- lesser-than column, width
-  {
-    break-if legal?
-    column <- subtract width, 1
-  }
+  return-unless legal?
 #?     $print [print-character (], row, [, ], column, [): ], c, 10/newline
   # special-case: newline
   {
@@ -609,6 +604,38 @@ def screen-height screen:&:screen -> height:num [
   height <- display-height
 ]
 
+def hide-cursor screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  return-if screen  # fake screen; do nothing
+  # real screen
+  hide-cursor-on-display
+]
+
+def show-cursor screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  return-if screen  # fake screen; do nothing
+  # real screen
+  show-cursor-on-display
+]
+
+def hide-screen screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  return-if screen  # fake screen; do nothing
+  # real screen
+  hide-display
+]
+
+def show-screen screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  return-if screen  # fake screen; do nothing
+  # real screen
+  show-display
+]
+
 def print screen:&:screen, s:text -> screen:&:screen [
   local-scope
   load-ingredients
diff --git a/100trace_browser.cc b/100trace_browser.cc
index ffa66b41..42a4d848 100644
--- a/100trace_browser.cc
+++ b/100trace_browser.cc
@@ -276,6 +276,7 @@ bool start_search_editor(search_direction dir) {
   tb_change_cell(col, bottom_screen_line, '/', TB_WHITE, TB_BLACK);
   ++col;
   tb_set_cursor(col, bottom_screen_line);
+  tb_present();
   string pattern;
   while (true) {
     int key = read_key();
@@ -293,21 +294,25 @@ bool start_search_editor(search_direction dir) {
       if (col > /*slash*/1) {
         --col;
         tb_set_cursor(col, bottom_screen_line);
+        tb_present();
       }
     }
     else if (key == TB_KEY_ARROW_RIGHT) {
       if (col-/*slash*/1 < SIZE(pattern)) {
         ++col;
         tb_set_cursor(col, bottom_screen_line);
+        tb_present();
       }
     }
     else if (key == TB_KEY_HOME || key == TB_KEY_CTRL_A) {
       col = /*skip slash*/1;
       tb_set_cursor(col, bottom_screen_line);
+      tb_present();
     }
     else if (key == TB_KEY_END || key == TB_KEY_CTRL_E) {
       col = SIZE(pattern)+/*skip slash*/1;
       tb_set_cursor(col, bottom_screen_line);
+      tb_present();
     }
     else if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
       if (col > /*slash*/1) {
@@ -325,6 +330,7 @@ bool start_search_editor(search_direction dir) {
           tb_change_cell(SIZE(pattern)+/*skip slash*/1, bottom_screen_line, ' ', TB_WHITE, TB_BLACK);
         }
         tb_set_cursor(col, bottom_screen_line);
+        tb_present();
       }
     }
     else if (key == TB_KEY_CTRL_K) {
@@ -333,6 +339,7 @@ bool start_search_editor(search_direction dir) {
       for (int x = col;  x < old_pattern_size+/*slash*/1;  ++x)
         tb_change_cell(x, bottom_screen_line, ' ', TB_WHITE, TB_BLACK);
       tb_set_cursor(col, bottom_screen_line);
+      tb_present();
     }
     else if (key == TB_KEY_CTRL_U) {
       int old_pattern_size = SIZE(pattern);
@@ -342,6 +349,7 @@ bool start_search_editor(search_direction dir) {
       for (int x = SIZE(pattern)+/*slash*/1;  x < old_pattern_size+/*skip slash*/1;  ++x)
         tb_change_cell(x, bottom_screen_line, ' ', TB_WHITE, TB_BLACK);
       tb_set_cursor(/*start of pattern skipping slash*/1, bottom_screen_line);
+      tb_present();
     }
     else if (key < 128) {  // ascii only
       // update pattern
@@ -352,6 +360,7 @@ bool start_search_editor(search_direction dir) {
         tb_change_cell(x, bottom_screen_line, pattern.at(x-/*slash*/1), TB_WHITE, TB_BLACK);
       ++col;
       tb_set_cursor(col, bottom_screen_line);
+      tb_present();
     }
   }
 }
@@ -435,6 +444,7 @@ void render() {
     render_line(screen_row, "~", /*cursor_line?*/false);
   // move cursor back to display row at the end
   tb_set_cursor(0, Display_row);
+  tb_present();
 }
 
 int lines_hidden(int screen_row) {
diff --git a/cannot_write_tests_for b/cannot_write_tests_for
index 1bd9ce3c..444aafd5 100644
--- a/cannot_write_tests_for
+++ b/cannot_write_tests_for
@@ -1,12 +1,14 @@
 0. main's ingredients
 1. assertion failures or errors inside scenarios
 2. screen background color
-3. more touch event types
-4. sandbox isolation
-5. errors in reading/writing files (missing directory, others?)
-6. has-more-events?
+3. has-more-events?
+4. hide/show screen
+5. more touch event types
+6. sandbox isolation
+7. errors in reading/writing files (missing directory, others?)
 
 termbox issues are implementation-specific and not worth testing:
+  whether we clear junk from other processes
   latency in interpreting low-level escape characters
 
 calls to update-cursor are currently duplicated:
diff --git a/edit/001-editor.mu b/edit/001-editor.mu
index a6dde85b..d81278ae 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -6,8 +6,10 @@ def main text:text [
   local-scope
   load-ingredients
   open-console
+  hide-screen 0/screen
   e:&:editor <- new-editor text, 0/left, 5/right
   render 0/screen, e
+  show-screen 0/screen
   wait-for-event 0/console
   close-console
 ]
diff --git a/edit/002-typing.mu b/edit/002-typing.mu
index d5120f2f..4f704f0d 100644
--- a/edit/002-typing.mu
+++ b/edit/002-typing.mu
@@ -266,17 +266,10 @@ def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:
   left:num <- get *editor, left:offset
   right:num <- get *editor, right:offset
   row:num, column:num <- render screen, editor
-  screen-height:num <- screen-height screen
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-line-until screen, right
   row <- add row, 1
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
   row <- add row, 1
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-screen-from screen, row, left, left, right
 ]
 
diff --git a/edit/004-programming-environment.mu b/edit/004-programming-environment.mu
index ba626cb4..597e6900 100644
--- a/edit/004-programming-environment.mu
+++ b/edit/004-programming-environment.mu
@@ -96,6 +96,7 @@ def event-loop screen:&:screen, console:&:console, env:&:environment, resources:
     }
     # if it's not global and not a touch event, send to appropriate editor
     {
+      hide-screen screen
       sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
       {
         break-if sandbox-in-focus?
@@ -115,6 +116,7 @@ def event-loop screen:&:screen, console:&:console, env:&:environment, resources:
         screen <- render-all screen, env, render
       }
       screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+      show-screen screen
     }
     loop
   }
@@ -393,6 +395,7 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   local-scope
   load-ingredients
   trace 10, [app], [render all]
+  hide-screen screen
   # top menu
   trace 11, [app], [render top menu]
   width:num <- screen-width screen
@@ -416,6 +419,8 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   current-sandbox:&:editor <- get *env, current-sandbox:offset
   sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset
   screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+  #
+  show-screen screen
 ]
 
 def render-recipes screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [
@@ -427,19 +432,12 @@ def render-recipes screen:&:screen, env:&:environment, {render-editor: (recipe (
   left:num <- get *recipes, left:offset
   right:num <- get *recipes, right:offset
   row:num, column:num, screen <- call render-editor, screen, recipes
-  screen-height:num <- screen-height screen
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-line-until screen, right
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   <render-recipe-components-end>
   # draw dotted line after recipes
   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   clear-screen-from screen, row, left, left, right
 ]
 
@@ -451,18 +449,11 @@ def render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (rec
   left:num <- get *current-sandbox, left:offset
   right:num <- get *current-sandbox, right:offset
   row:num, column:num, screen, current-sandbox <- call render-editor, screen, current-sandbox
-  screen-height:num <- screen-height screen
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-line-until screen, right
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   # draw solid line after code (you'll see why in later layers)
   draw-horizontal screen, row, left, right
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   clear-screen-from screen, row, left, left, right
 ]
 
@@ -483,6 +474,18 @@ def update-cursor screen:&:screen, recipes:&:editor, current-sandbox:&:editor, s
   screen <- move-cursor screen, cursor-row, cursor-column
 ]
 
+# ctrl-l - redraw screen (just in case it printed junk somehow)
+
+after <global-type> [
+  {
+    redraw-screen?:bool <- equal c, 12/ctrl-l
+    break-unless redraw-screen?
+    screen <- render-all screen, env:&:environment, render
+    sync-screen screen
+    loop +next-event
+  }
+]
+
 # ctrl-n - switch focus
 # todo: test this
 
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index fed2c806..5f08554d 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -245,26 +245,19 @@ def! render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (re
   row:num, column:num <- copy 1, 0
   left:num <- get *current-sandbox, left:offset
   right:num <- get *current-sandbox, right:offset
-  screen-height:num <- screen-height screen
   # render sandbox editor
   render-from:num <- get *env, render-from:offset
   {
     render-current-sandbox?:bool <- equal render-from, -1
     break-unless render-current-sandbox?
     row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox
-    space-left?:bool <- lesser-than row, screen-height
-    return-unless space-left?
     clear-screen-from screen, row, column, left, right
     row <- add row, 1
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
   }
   # render sandboxes
   draw-horizontal screen, row, left, right
   sandbox:&:sandbox <- get *env, sandbox:offset
   row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   clear-rest-of-screen screen, row, left, right
 ]
 
@@ -280,22 +273,16 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
     break-if hidden?
     # render sandbox menu
     row <- add row, 1
-    space-left?:bool <- lesser-than row, screen-height
-    return-unless space-left?
     screen <- move-cursor screen, row, left
     screen <- render-sandbox-menu screen, idx, left, right
     # save menu row so we can detect clicks to it later
     *sandbox <- put *sandbox, starting-row-on-screen:offset, row
     # render sandbox contents
     row <- add row, 1
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
     screen <- move-cursor screen, row, left
     sandbox-data:text <- get *sandbox, data:offset
     row, screen <- render-code screen, sandbox-data, left, right, row
     *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
     # render sandbox warnings, screen or response, in that order
     sandbox-response:text <- get *sandbox, response:offset
     <render-sandbox-results>
@@ -311,8 +298,8 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
       row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row
     }
     +render-sandbox-end
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
+    at-bottom?:bool <- greater-or-equal row, screen-height
+    return-if at-bottom?
     # draw solid line after sandbox
     draw-horizontal screen, row, left, right
   }
@@ -324,8 +311,6 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
     <end-render-sandbox-reset-hidden>
   }
   # draw next sandbox
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
   next-idx:num <- add idx, 1
   row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx
@@ -426,8 +411,6 @@ def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num
     column <- add column, 1
     loop
   }
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   was-at-left?:bool <- equal column, left
   clear-line-until screen, right
   {
@@ -508,8 +491,6 @@ def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num
     column <- add column, 1
     loop
   }
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   was-at-left?:bool <- equal column, left
   clear-line-until screen, right
   {
@@ -990,8 +971,10 @@ after <global-keypress> [
       render-from <- add render-from, 1
       *env <- put *env, render-from:offset, render-from
     }
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
@@ -1020,8 +1003,10 @@ after <global-keypress> [
     break-if at-beginning?
     render-from <- subtract render-from, 1
     *env <- put *env, render-from:offset, render-from
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/edit/006-sandbox-copy.mu b/edit/006-sandbox-copy.mu
index d3f82e88..9df5e625 100644
--- a/edit/006-sandbox-copy.mu
+++ b/edit/006-sandbox-copy.mu
@@ -128,8 +128,10 @@ after <global-touch> [
     break-unless copy?
     copy?, env <- try-copy-sandbox click-row, env
     break-unless copy?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/edit/007-sandbox-delete.mu b/edit/007-sandbox-delete.mu
index 5167b038..4fa3c37d 100644
--- a/edit/007-sandbox-delete.mu
+++ b/edit/007-sandbox-delete.mu
@@ -72,8 +72,10 @@ after <global-touch> [
     break-unless delete?
     delete?, env <- try-delete-sandbox click-row, env
     break-unless delete?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/edit/008-sandbox-edit.mu b/edit/008-sandbox-edit.mu
index 2d591ad6..dd5c1bb9 100644
--- a/edit/008-sandbox-edit.mu
+++ b/edit/008-sandbox-edit.mu
@@ -111,8 +111,10 @@ after <global-touch> [
     break-unless edit?
     edit?, env <- try-edit-sandbox click-row, env
     break-unless edit?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/edit/009-sandbox-test.mu b/edit/009-sandbox-test.mu
index badd795b..023015ed 100644
--- a/edit/009-sandbox-test.mu
+++ b/edit/009-sandbox-test.mu
@@ -130,8 +130,10 @@ after <global-touch> [
     # toggle its expected-response, and save session
     sandbox <- toggle-expected-response sandbox
     save-sandboxes env, resources
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/edit/010-sandbox-trace.mu b/edit/010-sandbox-trace.mu
index 8088577a..66a321d6 100644
--- a/edit/010-sandbox-trace.mu
+++ b/edit/010-sandbox-trace.mu
@@ -200,8 +200,10 @@ after <global-touch> [
     x:bool <- get *sandbox, display-trace?:offset
     x <- not x
     *sandbox <- put *sandbox, display-trace?:offset, x
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/sandbox/001-editor.mu b/sandbox/001-editor.mu
index a6dde85b..d81278ae 100644
--- a/sandbox/001-editor.mu
+++ b/sandbox/001-editor.mu
@@ -6,8 +6,10 @@ def main text:text [
   local-scope
   load-ingredients
   open-console
+  hide-screen 0/screen
   e:&:editor <- new-editor text, 0/left, 5/right
   render 0/screen, e
+  show-screen 0/screen
   wait-for-event 0/console
   close-console
 ]
diff --git a/sandbox/002-typing.mu b/sandbox/002-typing.mu
index d5120f2f..4f704f0d 100644
--- a/sandbox/002-typing.mu
+++ b/sandbox/002-typing.mu
@@ -266,17 +266,10 @@ def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:
   left:num <- get *editor, left:offset
   right:num <- get *editor, right:offset
   row:num, column:num <- render screen, editor
-  screen-height:num <- screen-height screen
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-line-until screen, right
   row <- add row, 1
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
   row <- add row, 1
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-screen-from screen, row, left, left, right
 ]
 
diff --git a/sandbox/004-programming-environment.mu b/sandbox/004-programming-environment.mu
index 2dca181d..917ea957 100644
--- a/sandbox/004-programming-environment.mu
+++ b/sandbox/004-programming-environment.mu
@@ -79,6 +79,7 @@ def event-loop screen:&:screen, console:&:console, env:&:environment, resources:
     }
     # not global and not a touch event
     {
+      hide-screen screen
       render?:bool <- handle-keyboard-event screen, current-sandbox, e:event
       break-unless render?
       # try to batch up rendering if there are more events queued up
@@ -89,6 +90,9 @@ def event-loop screen:&:screen, console:&:console, env:&:environment, resources:
         break-unless render-all-on-no-more-events?
         screen <- render-all screen, env, render
       }
+      +finish-event
+      screen <- update-cursor screen, current-sandbox, env
+      show-screen screen
     }
     loop
   }
@@ -194,6 +198,7 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   local-scope
   load-ingredients
   trace 10, [app], [render all]
+  hide-screen screen
   # top menu
   trace 11, [app], [render top menu]
   width:num <- screen-width screen
@@ -209,6 +214,8 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   #
   current-sandbox:&:editor <- get *env, current-sandbox:offset
   screen <- update-cursor screen, current-sandbox, env
+  #
+  show-screen screen
 ]
 
 # replaced in a later layer
@@ -219,18 +226,11 @@ def render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (rec
   left:num <- get *current-sandbox, left:offset
   right:num <- get *current-sandbox, right:offset
   row:num, column:num, screen, current-sandbox <- call render-editor, screen, current-sandbox
-  screen-height:num <- screen-height screen
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   clear-line-until screen, right
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   # draw solid line after code (you'll see why in later layers)
   draw-horizontal screen, row, left, right
   row <- add row, 1
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   clear-screen-from screen, row, left, left, right
 ]
 
@@ -242,3 +242,15 @@ def update-cursor screen:&:screen, current-sandbox:&:editor, env:&:environment -
   cursor-column:num <- get *current-sandbox, cursor-column:offset
   screen <- move-cursor screen, cursor-row, cursor-column
 ]
+
+# ctrl-l - redraw screen (just in case it printed junk somehow)
+
+after <global-type> [
+  {
+    redraw-screen?:bool <- equal c, 12/ctrl-l
+    break-unless redraw-screen?
+    screen <- render-all screen, env:&:environment, render
+    sync-screen screen
+    loop +next-event
+  }
+]
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index 06262d72..e9500797 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -234,26 +234,19 @@ def! render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (re
   row:num, column:num <- copy 1, 0
   left:num <- get *current-sandbox, left:offset
   right:num <- get *current-sandbox, right:offset
-  screen-height:num <- screen-height screen
   # render sandbox editor
   render-from:num <- get *env, render-from:offset
   {
     render-current-sandbox?:bool <- equal render-from, -1
     break-unless render-current-sandbox?
     row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox
-    space-left?:bool <- lesser-than row, screen-height
-    return-unless space-left?
     clear-screen-from screen, row, column, left, right
     row <- add row, 1
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
   }
   # render sandboxes
   draw-horizontal screen, row, left, right
   sandbox:&:sandbox <- get *env, sandbox:offset
   row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from, 0, env
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   clear-rest-of-screen screen, row, left, right
 ]
 
@@ -270,22 +263,16 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
     break-if hidden?
     # render sandbox menu
     row <- add row, 1
-    space-left?:bool <- lesser-than row, screen-height
-    return-unless space-left?
     screen <- move-cursor screen, row, left
     screen <- render-sandbox-menu screen, idx, left, right
     # save menu row so we can detect clicks to it later
     *sandbox <- put *sandbox, starting-row-on-screen:offset, row
     # render sandbox contents
     row <- add row, 1
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
     screen <- move-cursor screen, row, left
     sandbox-data:text <- get *sandbox, data:offset
     row, screen <- render-code screen, sandbox-data, left, right, row
     *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
     # render sandbox warnings, screen or response, in that order
     sandbox-response:text <- get *sandbox, response:offset
     <render-sandbox-results>
@@ -301,8 +288,8 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
       row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row
     }
     +render-sandbox-end
-    space-left? <- lesser-than row, screen-height
-    return-unless space-left?
+    at-bottom?:bool <- greater-or-equal row, screen-height
+    return-if at-bottom?
     # draw solid line after sandbox
     draw-horizontal screen, row, left, right
   }
@@ -314,8 +301,6 @@ def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, ro
     <end-render-sandbox-reset-hidden>
   }
   # draw next sandbox
-  space-left? <- lesser-than row, screen-height
-  return-unless space-left?
   next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset
   next-idx:num <- add idx, 1
   row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx, env
@@ -416,8 +401,6 @@ def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num
     column <- add column, 1
     loop
   }
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   was-at-left?:bool <- equal column, left
   clear-line-until screen, right
   {
@@ -483,8 +466,6 @@ def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num
     column <- add column, 1
     loop
   }
-  space-left?:bool <- lesser-than row, screen-height
-  return-unless space-left?
   was-at-left?:bool <- equal column, left
   clear-line-until screen, right
   {
@@ -812,9 +793,10 @@ after <global-keypress> [
       render-from <- add render-from, 1
       *env <- put *env, render-from:offset, render-from
     }
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
-    screen <- update-cursor screen, current-sandbox, env
-    loop +next-event
+    show-screen screen
+    jump +finish-event
   }
 ]
 
@@ -840,9 +822,10 @@ after <global-keypress> [
     break-if at-beginning?
     render-from <- subtract render-from, 1
     *env <- put *env, render-from:offset, render-from
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
-    screen <- update-cursor screen, current-sandbox, env
-    loop +next-event
+    show-screen screen
+    jump +finish-event
   }
 ]
 
diff --git a/sandbox/006-sandbox-copy.mu b/sandbox/006-sandbox-copy.mu
index 4835f02e..995f4c7c 100644
--- a/sandbox/006-sandbox-copy.mu
+++ b/sandbox/006-sandbox-copy.mu
@@ -140,8 +140,10 @@ after <global-touch> [
     break-unless copy?
     copy?, env <- try-copy-sandbox click-row, env
     break-unless copy?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, current-sandbox, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/sandbox/007-sandbox-delete.mu b/sandbox/007-sandbox-delete.mu
index 107c861c..ddfbf692 100644
--- a/sandbox/007-sandbox-delete.mu
+++ b/sandbox/007-sandbox-delete.mu
@@ -69,8 +69,10 @@ after <global-touch> [
     break-unless delete?
     delete?, env <- try-delete-sandbox click-row, env
     break-unless delete?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, current-sandbox, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/sandbox/008-sandbox-edit.mu b/sandbox/008-sandbox-edit.mu
index ec4fd578..cb19ebc4 100644
--- a/sandbox/008-sandbox-edit.mu
+++ b/sandbox/008-sandbox-edit.mu
@@ -111,8 +111,10 @@ after <global-touch> [
     break-unless edit?
     edit?, env <- try-edit-sandbox click-row, env
     break-unless edit?
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, current-sandbox, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/sandbox/009-sandbox-test.mu b/sandbox/009-sandbox-test.mu
index 1c24bcb8..d7b8ed62 100644
--- a/sandbox/009-sandbox-test.mu
+++ b/sandbox/009-sandbox-test.mu
@@ -132,8 +132,10 @@ after <global-touch> [
     # toggle its expected-response, and save session
     sandbox <- toggle-expected-response sandbox
     save-sandboxes env, resources
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, current-sandbox, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/sandbox/010-sandbox-trace.mu b/sandbox/010-sandbox-trace.mu
index 27f2915a..f81d4151 100644
--- a/sandbox/010-sandbox-trace.mu
+++ b/sandbox/010-sandbox-trace.mu
@@ -190,8 +190,10 @@ after <global-touch> [
     x:bool <- get *sandbox, display-trace?:offset
     x <- not x
     *sandbox <- put *sandbox, display-trace?:offset, x
+    hide-screen screen
     screen <- render-sandbox-side screen, env, render
     screen <- update-cursor screen, current-sandbox, env
+    show-screen screen
     loop +next-event
   }
 ]
diff --git a/termbox/termbox.c b/termbox/termbox.c
index 6a22f109..36c9496a 100644
--- a/termbox/termbox.c
+++ b/termbox/termbox.c
@@ -23,10 +23,20 @@ extern int wcwidth (wchar_t);
 #include "output.inl"
 #include "input.inl"
 
+struct cellbuf {
+  int width;
+  int height;
+  struct tb_cell *cells;
+};
+
+#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
+#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
 #define LAST_COORD_INIT -1
 
 static struct termios orig_tios;
 
+static struct cellbuf back_buffer;
+static struct cellbuf front_buffer;
 static struct bytebuffer output_buffer;
 static struct bytebuffer input_buffer;
 
@@ -47,6 +57,11 @@ static uint16_t foreground = TB_WHITE;
 static void write_cursor(int x, int y);
 static void write_sgr(uint16_t fg, uint16_t bg);
 
+static void cellbuf_init(struct cellbuf *buf, int width, int height);
+static void cellbuf_resize(struct cellbuf *buf, int width, int height);
+static void cellbuf_clear(struct cellbuf *buf);
+static void cellbuf_free(struct cellbuf *buf);
+
 static void update_size(void);
 static void update_term_size(void);
 static void send_attr(uint16_t fg, uint16_t bg);
@@ -104,9 +119,14 @@ int tb_init(void)
   bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
   bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
   bytebuffer_puts(&output_buffer, funcs[T_ENTER_BRACKETED_PASTE]);
-  bytebuffer_flush(&output_buffer, inout);
+  send_clear();
 
   update_term_size();
+  cellbuf_init(&back_buffer, termw, termh);
+  cellbuf_init(&front_buffer, termw, termh);
+  cellbuf_clear(&back_buffer);
+  cellbuf_clear(&front_buffer);
+
   return 0;
 }
 
@@ -126,6 +146,8 @@ void tb_shutdown(void)
   close(winch_fds[0]);
   close(winch_fds[1]);
 
+  cellbuf_free(&back_buffer);
+  cellbuf_free(&front_buffer);
   bytebuffer_free(&output_buffer);
   bytebuffer_free(&input_buffer);
   termw = termh = -1;
@@ -136,21 +158,88 @@ int tb_is_active(void)
   return termw != -1;
 }
 
+static void tb_repaint(bool force) {
+  int x,y,w,i;
+  struct tb_cell *back, *front;
+
+  assert(termw != -1);
+
+  /* invalidate cursor position */
+  lastx = LAST_COORD_INIT;
+  lasty = LAST_COORD_INIT;
+
+  if (buffer_size_change_request) {
+    update_size();
+    buffer_size_change_request = 0;
+  }
+
+  for (y = 0; y < front_buffer.height; ++y) {
+    for (x = 0; x < front_buffer.width; ) {
+      back = &CELL(&back_buffer, x, y);
+      front = &CELL(&front_buffer, x, y);
+      w = wcwidth(back->ch);
+      if (w < 1) w = 1;
+      if (!force && memcmp(back, front, sizeof(struct tb_cell)) == 0) {
+        x += w;
+        continue;
+      }
+      memcpy(front, back, sizeof(struct tb_cell));
+      send_attr(back->fg, back->bg);
+      if (w > 1 && x >= front_buffer.width - (w - 1)) {
+        // Not enough room for wide ch, so send spaces
+        for (i = x; i < front_buffer.width; ++i) {
+          send_char(i, y, ' ');
+        }
+      } else {
+        send_char(x, y, back->ch);
+        for (i = 1; i < w; ++i) {
+          front = &CELL(&front_buffer, x + i, y);
+          front->ch = 0;
+          front->fg = back->fg;
+          front->bg = back->bg;
+        }
+      }
+      x += w;
+    }
+  }
+  if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+    write_cursor(cursor_x, cursor_y);
+  bytebuffer_flush(&output_buffer, inout);
+}
+
+void tb_present(void)
+{
+  tb_repaint(false);
+}
+
+void tb_sync(void)
+{
+  tb_repaint(true);
+}
+
 void tb_set_cursor(int cx, int cy)
 {
   assert(termw != -1);
   cursor_x = cx;
   cursor_y = cy;
-  write_cursor(cursor_x, cursor_y);
-  bytebuffer_flush(&output_buffer, inout);
+  if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+    write_cursor(cursor_x, cursor_y);
 }
 
 void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg)
 {
   assert(termw != -1);
-  send_attr(fg, bg);
-  send_char(x, y, ch);
-  bytebuffer_flush(&output_buffer, inout);
+  if ((unsigned)x >= (unsigned)back_buffer.width)
+    return;
+  if ((unsigned)y >= (unsigned)back_buffer.height)
+    return;
+  struct tb_cell c = {ch, fg, bg};
+  CELL(&back_buffer, x, y) = c;
+}
+
+struct tb_cell *tb_cell_buffer()
+{
+  return back_buffer.cells;
 }
 
 int tb_poll_event(struct tb_event *event)
@@ -187,7 +276,7 @@ void tb_clear(void)
     update_size();
     buffer_size_change_request = 0;
   }
-  send_clear();
+  cellbuf_clear(&back_buffer);
 }
 
 void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
@@ -236,6 +325,56 @@ static void write_sgr(uint16_t fg, uint16_t bg) {
   WRITE_LITERAL("m");
 }
 
+static void cellbuf_init(struct cellbuf *buf, int width, int height)
+{
+  buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
+  assert(buf->cells);
+  buf->width = width;
+  buf->height = height;
+}
+
+static void cellbuf_resize(struct cellbuf *buf, int width, int height)
+{
+  if (buf->width == width && buf->height == height)
+    return;
+
+  int oldw = buf->width;
+  int oldh = buf->height;
+  struct tb_cell *oldcells = buf->cells;
+
+  cellbuf_init(buf, width, height);
+  cellbuf_clear(buf);
+
+  int minw = (width < oldw) ? width : oldw;
+  int minh = (height < oldh) ? height : oldh;
+  int i;
+
+  for (i = 0; i < minh; ++i) {
+    struct tb_cell *csrc = oldcells + (i * oldw);
+    struct tb_cell *cdst = buf->cells + (i * width);
+    memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
+  }
+
+  free(oldcells);
+}
+
+static void cellbuf_clear(struct cellbuf *buf)
+{
+  int i;
+  int ncells = buf->width * buf->height;
+
+  for (i = 0; i < ncells; ++i) {
+    buf->cells[i].ch = ' ';
+    buf->cells[i].fg = foreground;
+    buf->cells[i].bg = background;
+  }
+}
+
+static void cellbuf_free(struct cellbuf *buf)
+{
+  free(buf->cells);
+}
+
 static void get_term_size(int *w, int *h)
 {
   struct winsize sz;
@@ -329,6 +468,9 @@ static void sigwinch_handler(int xxx)
 static void update_size(void)
 {
   update_term_size();
+  cellbuf_resize(&back_buffer, termw, termh);
+  cellbuf_resize(&front_buffer, termw, termh);
+  cellbuf_clear(&front_buffer);
   send_clear();
 }
 
diff --git a/termbox/termbox.h b/termbox/termbox.h
index 97e3f524..c6cda6e1 100644
--- a/termbox/termbox.h
+++ b/termbox/termbox.h
@@ -9,12 +9,18 @@ extern "C" {
 /*** 1. Controlling the screen. */
 
 /* The screen is a 2D array of cells. */
+struct tb_cell {
+  uint32_t ch;  /* unicode character */
+  uint16_t fg;  /* foreground color (0-255) and attributes */
+  uint16_t bg;  /* background color (0-255) and attributes */
+};
 
-/* Names for some colors. */
+/* Names for some colors in tb_cell.fg and tb_cell.bg. */
 #define TB_BLACK 232
 #define TB_WHITE 255
 
-/* Some attributes of screen cells that can be combined with colors using bitwise-OR. */
+/* Colors in tb_cell can be combined using bitwise-OR with multiple
+ * of the following attributes. */
 #define TB_BOLD      0x0100
 #define TB_UNDERLINE 0x0200
 #define TB_REVERSE   0x0400
@@ -38,14 +44,34 @@ int tb_is_active(void);
 int tb_width(void);
 int tb_height(void);
 
-/* Clear the screen. */
+/* Update the screen with internal state. Most methods below modify just the
+ * internal state of the screen. Changes won't be visible until you call
+ * tb_present(). */
+void tb_present(void);
+
+/* Variant of tb_present() that always refreshes the entire screen. */
+void tb_sync(void);
+
+/* Returns a pointer to the internal screen state: a 1D array of cells in
+ * raster order. You'll need to call tb_width() and tb_height() for the
+ * array's dimensions. The array stays valid until tb_clear() or tb_present()
+ * are called. */
+struct tb_cell *tb_cell_buffer();
+
+/* Clear the internal screen state using either TB_DEFAULT or the
+ * color/attributes set by tb_set_clear_attributes(). */
 void tb_clear(void);
 void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
 
-/* Move the cursor. Upper-left character is (0, 0). */
+/* Move the cursor. Upper-left character is (0, 0).
+ */
 void tb_set_cursor(int cx, int cy);
+/* To hide the cursor, call tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR).
+ * Cursor starts out hidden. */
+#define TB_HIDE_CURSOR -1
 
-/* Modify a specific cell of the screen. */
+/* Modify a specific cell of the screen. Don't forget to call tb_present() to
+ * commit your changes. */
 void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg);
 
 /*** 2. Controlling keyboard events. */