about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--080display.cc74
-rw-r--r--081print.mu304
-rw-r--r--100trace_browser.cc10
-rw-r--r--edit/001-editor.mu2
-rw-r--r--edit/002-typing.mu5
-rw-r--r--edit/004-programming-environment.mu8
-rw-r--r--edit/005-sandbox.mu4
-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.mu5
-rw-r--r--sandbox/004-programming-environment.mu8
-rw-r--r--sandbox/005-sandbox.mu4
-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.c187
-rw-r--r--termbox/termbox.h27
-rw-r--r--termbox/x.cc12
24 files changed, 277 insertions, 395 deletions
diff --git a/080display.cc b/080display.cc
index 26b17926..b639c2a7 100644
--- a/080display.cc
+++ b/080display.cc
@@ -7,7 +7,6 @@
 :(before "End Globals")
 int Display_row = 0;
 int Display_column = 0;
-bool Autodisplay = true;
 
 :(before "End Includes")
 #define CHECK_SCREEN \
@@ -38,6 +37,7 @@ case OPEN_CONSOLE: {
 :(before "End Primitive Recipe Implementations")
 case OPEN_CONSOLE: {
   tb_init();
+  std::setvbuf(stdout, NULL, _IONBF, 0);  // disable buffering in cout
   Display_row = Display_column = 0;
   int width = tb_width();
   int height = tb_height();
@@ -128,29 +128,24 @@ case PRINT_CHARACTER_TO_DISPLAY: {
     if (bg_color == 0) bg_color = TB_BLACK;
   }
   tb_change_cell(Display_column, Display_row, c, color, bg_color);
-  if (c == '\n' || c == '\r') {
-    if (Display_row < height-1) {
-      Display_column = 0;
-      ++Display_row;
-      tb_set_cursor(Display_column, Display_row);
-      if (Autodisplay) tb_present();
-    }
-    break;
+  // track row and column, mimicking what happens on screen
+  if (c == '\n') {
+    if (Display_row < height-1) ++Display_row;  // otherwise we scroll and Display_row remains unchanged
   }
-  if (c == '\b') {
-    if (Display_column > 0) {
-      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;
+  else if (c == '\r') {
+    Display_column = 0;
   }
-  if (Display_column < width-1) {
+  else if (c == '\b') {
+    if (Display_column > 0) --Display_column;
+  }
+  else {
     ++Display_column;
-    tb_set_cursor(Display_column, Display_row);
+    if (Display_column >= width) {
+      Display_column = 0;
+      if (Display_row < height-1) ++Display_row;
+    }
   }
-  if (Autodisplay) tb_present();
+  tb_set_cursor(Display_column, Display_row);
   break;
 }
 
@@ -197,7 +192,6 @@ 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;
 }
 
@@ -217,7 +211,6 @@ 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;
 }
@@ -236,7 +229,6 @@ case MOVE_CURSOR_UP_ON_DISPLAY: {
   if (Display_row > 0) {
     --Display_row;
     tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -257,7 +249,6 @@ 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;
 }
@@ -276,7 +267,6 @@ case MOVE_CURSOR_LEFT_ON_DISPLAY: {
   if (Display_column > 0) {
     --Display_column;
     tb_set_cursor(Display_column, Display_row);
-    if (Autodisplay) tb_present();
   }
   break;
 }
@@ -292,7 +282,6 @@ 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")
@@ -327,37 +316,6 @@ case DISPLAY_HEIGHT: {
   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")
@@ -481,7 +439,6 @@ 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;
 }
 
@@ -507,6 +464,5 @@ 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 b778948e..2c2e09ae 100644
--- a/081print.mu
+++ b/081print.mu
@@ -6,7 +6,9 @@ container screen [
   num-columns:num
   cursor-row:num
   cursor-column:num
-  data:&:@:screen-cell
+  data:&:@:screen-cell  # capacity num-rows*num-columns
+  top-idx:num  # index inside data that corresponds to top-left of screen
+               # modified on scroll, wrapping around to the top of data
 ]
 
 container screen-cell [
@@ -18,9 +20,13 @@ def new-fake-screen w:num, h:num -> result:&:screen [
   local-scope
   load-ingredients
   result <- new screen:type
+  non-zero-width?:bool <- greater-than w, 0
+  assert non-zero-width?, [screen can't have zero width]
+  non-zero-height?:bool <- greater-than h, 0
+  assert non-zero-height?, [screen can't have zero height]
   bufsize:num <- multiply w, h
   data:&:@:screen-cell <- new screen-cell:type, bufsize
-  *result <- merge h/num-rows, w/num-columns, 0/cursor-row, 0/cursor-column, data
+  *result <- merge h/num-rows, w/num-columns, 0/cursor-row, 0/cursor-column, data, 0/top-idx
   result <- clear-screen result
 ]
 
@@ -48,6 +54,7 @@ def clear-screen screen:&:screen -> screen:&:screen [
   # reset cursor
   *screen <- put *screen, cursor-row:offset, 0
   *screen <- put *screen, cursor-column:offset, 0
+  *screen <- put *screen, top-idx:offset, 0
 ]
 
 def fake-screen-is-empty? screen:&:screen -> result:bool [
@@ -97,71 +104,152 @@ def print screen:&:screen, c:char -> screen:&:screen [
   # (handle special cases exactly like in the real screen)
   width:num <- get *screen, num-columns:offset
   height:num <- get *screen, num-rows:offset
-  # if cursor is out of bounds, silently exit
+  capacity:num <- multiply width, height
   row:num <- get *screen, cursor-row:offset
-  row <- round row
-  legal?:bool <- greater-or-equal row, 0
-  return-unless legal?
-  legal? <- lesser-than row, height
-  return-unless legal?
   column:num <- get *screen, cursor-column:offset
+  buf:&:@:screen-cell <- get *screen, data:offset
+  # some potentially slow sanity checks for preconditions {
+  # eliminate fractions from column and row
+  row <- round row
   column <- round column
-  legal? <- greater-or-equal column, 0
-  return-unless legal?
-  legal? <- lesser-than column, width
-  return-unless legal?
+  # if cursor is past left margin (error), reset to left margin
+  {
+    too-far-left?:bool <- lesser-than column, 0
+    break-unless too-far-left?
+    column <- copy 0
+    *screen <- put *screen, cursor-column:offset, column
+  }
+  # if cursor is at right margin, wrap
+  {
+    at-right?:bool <- equal column, width
+    break-unless at-right?
+    column <- copy 0
+    *screen <- put *screen, cursor-column:offset, column
+    row <- add row, 1
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  # if cursor is past right margin (error), reset to right margin
+  {
+    too-far-right?:bool <- greater-than column, width
+    break-unless too-far-right?
+    column <- subtract width, 1
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  # if row is above top margin (error), reset to top margin
+  {
+    too-far-up?:bool <- lesser-than row, 0
+    break-unless too-far-up?
+    row <- copy 0
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  # if row is at bottom margin, scroll
+  {
+    at-bottom?:bool <- equal row, height
+    break-unless at-bottom?
+    scroll-fake-screen screen
+    row <- subtract height, 1
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  # if row is below bottom margin (error), reset to bottom margin
+  {
+    too-far-down?:bool <- greater-than row, height
+    break-unless too-far-down?
+    row <- subtract height, 1
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  # }
 #?     $print [print-character (], row, [, ], column, [): ], c, 10/newline
   # special-case: newline
   {
     newline?:bool <- equal c, 10/newline
     break-unless newline?
-    {
-      # unless cursor is already at bottom
-      bottom:num <- subtract height, 1
-      at-bottom?:bool <- greater-or-equal row, bottom
-      break-if at-bottom?
-      # move it to the next row
-      column <- copy 0
-      *screen <- put *screen, cursor-column:offset, column
-      row <- add row, 1
-      *screen <- put *screen, cursor-row:offset, row
-    }
+    cursor-down-on-fake-screen screen  # doesn't modify column
+    return
+  }
+  # special-case: linefeed
+  {
+    linefeed?:bool <- equal c, 13/linefeed
+    break-unless linefeed?
+    *screen <- put *screen, cursor-column:offset, 0
     return
   }
-  # save character in fake screen
-  index:num <- multiply row, width
-  index <- add index, column
-  buf:&:@:screen-cell <- get *screen, data:offset
-  len:num <- length *buf
   # special-case: backspace
+  # moves cursor left but does not erase
   {
-    backspace?:bool <- equal c, 8
+    backspace?:bool <- equal c, 8/backspace
     break-unless backspace?
     {
-      # unless cursor is already at left margin
-      at-left?:bool <- lesser-or-equal column, 0
-      break-if at-left?
-      # clear previous location
+      break-unless column
       column <- subtract column, 1
       *screen <- put *screen, cursor-column:offset, column
-      index <- subtract index, 1
-      cursor:screen-cell <- merge 32/space, 7/white
-      *buf <- put-index *buf, index, cursor
     }
     return
   }
+  # save character in fake screen
+  top-idx:num <- get *screen, top-idx:offset
+  index:num <- data-index row, column, width, height, top-idx
   cursor:screen-cell <- merge c, color
   *buf <- put-index *buf, index, cursor
-  # increment column unless it's already all the way to the right
+  # move cursor to next character
+  # (but don't bother making it valid; we'll do that before the next print)
+  column <- add column, 1
+  *screen <- put *screen, cursor-column:offset, column
+]
+
+def cursor-down-on-fake-screen screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  row:num <- get *screen, cursor-row:offset
+  height:num <- get *screen, num-rows:offset
+  bottom:num <- subtract height, 1
+  at-bottom?:bool <- greater-or-equal row, bottom
   {
-    right:num <- subtract width, 1
-    at-right?:bool <- greater-or-equal column, right
-    break-if at-right?
-    column <- add column, 1
-    *screen <- put *screen, cursor-column:offset, column
+    break-if at-bottom?
+    row <- add row, 1
+    *screen <- put *screen, cursor-row:offset, row
+  }
+  {
+    break-unless at-bottom?
+    scroll-fake-screen screen  # does not modify row
   }
 ]
 
+def scroll-fake-screen screen:&:screen -> screen:&:screen [
+  local-scope
+  load-ingredients
+  width:num <- get *screen, num-columns:offset
+  height:num <- get *screen, num-rows:offset
+  buf:&:@:screen-cell <- get *screen, data:offset
+  # clear top line and 'rotate' it to the bottom
+  top-idx:num <- get *screen, top-idx:offset  # 0 <= top-idx < len(buf)
+  next-top-idx:num <- add top-idx, width  # 0 <= next-top-idx <= len(buf)
+  empty-cell:screen-cell <- merge 0, 0
+  {
+    done?:bool <- greater-or-equal top-idx, next-top-idx
+    break-if done?
+    put-index *buf, top-idx, empty-cell
+    top-idx <- add top-idx, 1
+    # no modulo; top-idx is always a multiple of width,
+    # so it can never wrap around inside this loop
+    loop
+  }
+  # top-idx now same as next-top-idx; wrap around if necessary
+  capacity:num <- multiply width, height
+  _, top-idx <- divide-with-remainder, top-idx, capacity
+  *screen <- put *screen, top-idx:offset, top-idx
+]
+
+# translate from screen (row, column) coordinates to an index into data
+# while accounting for scrolling (sliding top-idx)
+def data-index row:num, column:num, width:num, height:num, top-idx:num -> result:num [
+  local-scope
+  load-ingredients
+  result <- multiply width, row
+  result <- add result, column, top-idx
+  capacity:num <- multiply width, height
+  _, result <- divide-with-remainder result, capacity
+]
+
 scenario print-character-at-top-left [
   local-scope
   fake-screen:&:screen <- new-fake-screen 3/width, 2/height
@@ -232,7 +320,7 @@ scenario print-backspace-character [
   memory-should-contain [
     10 <- 0  # cursor column
     11 <- 6  # width*height
-    12 <- 32  # space, not 'a'
+    12 <- 97  # still 'a'
     13 <- 7  # white
     # rest of screen is empty
     14 <- 0
@@ -255,7 +343,7 @@ scenario print-extra-backspace-character [
   memory-should-contain [
     1 <- 0  # cursor column
     3 <- 6  # width*height
-    4 <- 32  # space, not 'a'
+    4 <- 97  # still 'a'
     5 <- 7  # white
     # rest of screen is empty
     6 <- 0
@@ -271,22 +359,26 @@ scenario print-character-at-right-margin [
   b:char <- copy 98/b
   fake-screen <- print fake-screen, b
   run [
-    # cursor now at right margin
+    # cursor now at next row
     c:char <- copy 99/c
     fake-screen <- print fake-screen, c
-    10:num/raw <- get *fake-screen, cursor-column:offset
+    10:num/raw <- get *fake-screen, cursor-row:offset
+    11:num/raw <- get *fake-screen, cursor-column:offset
     cell:&:@:screen-cell <- get *fake-screen, data:offset
-    11:@:screen-cell/raw <- copy *cell
+    12:@:screen-cell/raw <- copy *cell
   ]
   memory-should-contain [
-    10 <- 1  # cursor column
-    11 <- 4  # width*height
-    12 <- 97  # 'a'
-    13 <- 7  # white
-    14 <- 99  # 'c' over 'b'
-    15 <- 7  # white
-    # rest of screen is empty
-    16 <- 0
+    10 <- 1  # cursor row
+    11 <- 1  # cursor column
+    12 <- 4  # width*height
+    13 <- 97  # 'a'
+    14 <- 7  # white
+    15 <- 98  # 'b'
+    16 <- 7  # white
+    17 <- 99  # 'c'
+    18 <- 7  # white
+    19 <- 0  # ' '
+    20 <- 7  # white
   ]
 ]
 
@@ -305,7 +397,7 @@ scenario print-newline-character [
   ]
   memory-should-contain [
     10 <- 1  # cursor row
-    11 <- 0  # cursor column
+    11 <- 1  # cursor column
     12 <- 6  # width*height
     13 <- 97  # 'a'
     14 <- 7  # white
@@ -336,39 +428,81 @@ scenario print-newline-at-bottom-line [
 scenario print-character-at-bottom-right [
   local-scope
   fake-screen:&:screen <- new-fake-screen 2/width, 2/height
-  newline:char <- copy 10/newline
-  fake-screen <- print fake-screen, newline
   a:char <- copy 97/a
   fake-screen <- print fake-screen, a
   b:char <- copy 98/b
   fake-screen <- print fake-screen, b
   c:char <- copy 99/c
   fake-screen <- print fake-screen, c
-  fake-screen <- print fake-screen, newline
   run [
     # cursor now at bottom right
     d:char <- copy 100/d
     fake-screen <- print fake-screen, d
     10:num/raw <- get *fake-screen, cursor-row:offset
     11:num/raw <- get *fake-screen, cursor-column:offset
+    12:num/raw <- get *fake-screen, top-idx:offset
     cell:&:@:screen-cell <- get *fake-screen, data:offset
     20:@:screen-cell/raw <- copy *cell
   ]
+  # cursor column overflows the screen but is not wrapped yet
   memory-should-contain [
     10 <- 1  # cursor row
-    11 <- 1  # cursor column
-    20 <- 4  # width*height
-    21 <- 0  # unused
+    11 <- 2  # cursor column -- outside screen
+    12 <- 0  # top-idx -- not yet scrolled
+    20 <- 4  # screen size (width*height)
+    21 <- 97  # 'a'
     22 <- 7  # white
-    23 <- 0  # unused
+    23 <- 98  # 'b'
     24 <- 7  # white
-    25 <- 97 # 'a'
+    25 <- 99 # 'c'
     26 <- 7  # white
-    27 <- 100  # 'd' over 'b' and 'c' and newline
+    27 <- 100  # 'd'
     28 <- 7  # white
-    # rest of screen is empty
-    29 <- 0
   ]
+  run [
+    e:char <- copy 101/e
+    print fake-screen, e
+    10:num/raw <- get *fake-screen, cursor-row:offset
+    11:num/raw <- get *fake-screen, cursor-column:offset
+    12:num/raw <- get *fake-screen, top-idx:offset
+    cell:&:@:screen-cell <- get *fake-screen, data:offset
+    20:@:screen-cell/raw <- copy *cell
+  ]
+  memory-should-contain [
+    # text scrolls by 1, we lose the top line
+    10 <- 1  # cursor row
+    11 <- 1  # cursor column -- wrapped
+    12 <- 2  # top-idx -- scrolled
+    20 <- 4  # screen size (width*height)
+    # screen now checked in rotated order
+    25 <- 99 # 'c'
+    26 <- 7  # white
+    27 <- 100  # 'd'
+    28 <- 7  # white
+    # screen wraps; bottom line is cleared of old contents
+    21 <- 101  # 'e'
+    22 <- 7  # white
+    23 <- 0  # unused
+    24 <- 0  # no color
+  ]
+]
+
+# even though our screen supports scrolling, some apps may want to avoid
+# scrolling
+# these helpers help check for scrolling at development time
+def save-top-idx screen:&:screen -> result:num [
+  local-scope
+  load-ingredients
+  return-unless screen, 0  # check is only for fake screens
+  result <- get *screen, top-idx:offset
+]
+def assert-no-scroll screen:&:screen, old-top-idx:num [
+  local-scope
+  load-ingredients
+  return-unless screen
+  new-top-idx:num <- get *screen, top-idx:offset
+  no-scroll?:bool <- equal old-top-idx, new-top-idx
+  assert no-scroll?, [render should never use screen's scrolling capabilities]
 ]
 
 def clear-line screen:&:screen -> screen:&:screen [
@@ -398,10 +532,14 @@ def clear-line screen:&:screen -> screen:&:screen [
   *screen <- put *screen, cursor-column:offset, original-column
 ]
 
+# only for non-scrolling apps
 def clear-line-until screen:&:screen, right:num/inclusive -> screen:&:screen [
   local-scope
   load-ingredients
-  _, column:num <- cursor-position screen
+  row:num, column:num <- cursor-position screen
+  height:num <- screen-height screen
+  past-bottom?:bool <- greater-or-equal row, height
+  return-if past-bottom?
   space:char <- copy 32/space
   bg-color:num, bg-color-found?:bool <- next-ingredient
   {
@@ -597,22 +735,6 @@ def screen-height screen:&:screen -> height:num [
   height <- display-height
 ]
 
-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
@@ -640,24 +762,32 @@ def print screen:&:screen, s:text -> screen:&:screen [
   }
 ]
 
-scenario print-text-stops-at-right-margin [
+scenario print-text-wraps-past-right-margin [
   local-scope
   fake-screen:&:screen <- new-fake-screen 3/width, 2/height
   run [
     fake-screen <- print fake-screen, [abcd]
+    5:num/raw <- get *fake-screen, cursor-row:offset
+    6:num/raw <- get *fake-screen, cursor-column:offset
+    7:num/raw <- get *fake-screen, top-idx:offset
     cell:&:@:screen-cell <- get *fake-screen, data:offset
     10:@:screen-cell/raw <- copy *cell
   ]
   memory-should-contain [
+    5 <- 1  # cursor-row
+    6 <- 1  # cursor-column
+    7 <- 0  # top-idx
     10 <- 6  # width*height
     11 <- 97  # 'a'
     12 <- 7  # white
     13 <- 98  # 'b'
     14 <- 7  # white
-    15 <- 100  # 'd' overwrites 'c'
+    15 <- 99  # 'c'
     16 <- 7  # white
+    17 <- 100  # 'd'
+    18 <- 7  # white
     # rest of screen is empty
-    17 <- 0
+    19 <- 0
   ]
 ]
 
diff --git a/100trace_browser.cc b/100trace_browser.cc
index 42a4d848..ffa66b41 100644
--- a/100trace_browser.cc
+++ b/100trace_browser.cc
@@ -276,7 +276,6 @@ 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();
@@ -294,25 +293,21 @@ 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) {
@@ -330,7 +325,6 @@ 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) {
@@ -339,7 +333,6 @@ 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);
@@ -349,7 +342,6 @@ 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
@@ -360,7 +352,6 @@ 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();
     }
   }
 }
@@ -444,7 +435,6 @@ 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/edit/001-editor.mu b/edit/001-editor.mu
index d81278ae..a6dde85b 100644
--- a/edit/001-editor.mu
+++ b/edit/001-editor.mu
@@ -6,10 +6,8 @@ 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 8c48f147..16693429 100644
--- a/edit/002-typing.mu
+++ b/edit/002-typing.mu
@@ -264,6 +264,7 @@ def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool
 def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:editor [
   local-scope
   load-ingredients
+  old-top-idx:num <- save-top-idx screen
   left:num <- get *editor, left:offset
   right:num <- get *editor, right:offset
   row:num, column:num <- render screen, editor
@@ -272,6 +273,7 @@ def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:
   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
   row <- add row, 1
   clear-screen-from screen, row, left, left, right
+  assert-no-scroll screen, old-top-idx
 ]
 
 scenario editor-handles-empty-event-queue [
@@ -1057,6 +1059,9 @@ after <handle-special-key> [
 def draw-horizontal screen:&:screen, row:num, x:num, right:num -> screen:&:screen [
   local-scope
   load-ingredients
+  height:num <- screen-height screen
+  past-bottom?:bool <- greater-or-equal row, height
+  return-if past-bottom?
   style:char, style-found?:bool <- next-ingredient
   {
     break-if style-found?
diff --git a/edit/004-programming-environment.mu b/edit/004-programming-environment.mu
index 0d4e907d..606cdfa2 100644
--- a/edit/004-programming-environment.mu
+++ b/edit/004-programming-environment.mu
@@ -96,7 +96,6 @@ 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?
@@ -117,7 +116,6 @@ 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
   }
@@ -396,7 +394,7 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   local-scope
   load-ingredients
   trace 10, [app], [render all]
-  hide-screen screen
+  old-top-idx:num <- save-top-idx screen
   # top menu
   trace 11, [app], [render top menu]
   width:num <- screen-width screen
@@ -414,14 +412,14 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   #
   screen <- render-recipes screen, env, render-editor
   screen <- render-sandbox-side screen, env, render-editor
-  <render-components-end>
+  <render-components-end>  # no early returns permitted
   #
   recipes:&:editor <- get *env, recipes:offset
   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
+  assert-no-scroll screen, old-top-idx
 ]
 
 def render-recipes screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index 5f08554d..2e16e05b 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -971,10 +971,8 @@ 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
   }
 ]
@@ -1003,10 +1001,8 @@ 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 9df5e625..d3f82e88 100644
--- a/edit/006-sandbox-copy.mu
+++ b/edit/006-sandbox-copy.mu
@@ -128,10 +128,8 @@ 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 4fa3c37d..5167b038 100644
--- a/edit/007-sandbox-delete.mu
+++ b/edit/007-sandbox-delete.mu
@@ -72,10 +72,8 @@ 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 dd5c1bb9..2d591ad6 100644
--- a/edit/008-sandbox-edit.mu
+++ b/edit/008-sandbox-edit.mu
@@ -111,10 +111,8 @@ 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 023015ed..badd795b 100644
--- a/edit/009-sandbox-test.mu
+++ b/edit/009-sandbox-test.mu
@@ -130,10 +130,8 @@ 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 66a321d6..8088577a 100644
--- a/edit/010-sandbox-trace.mu
+++ b/edit/010-sandbox-trace.mu
@@ -200,10 +200,8 @@ 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 d81278ae..a6dde85b 100644
--- a/sandbox/001-editor.mu
+++ b/sandbox/001-editor.mu
@@ -6,10 +6,8 @@ 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 8c48f147..16693429 100644
--- a/sandbox/002-typing.mu
+++ b/sandbox/002-typing.mu
@@ -264,6 +264,7 @@ def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool
 def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:editor [
   local-scope
   load-ingredients
+  old-top-idx:num <- save-top-idx screen
   left:num <- get *editor, left:offset
   right:num <- get *editor, right:offset
   row:num, column:num <- render screen, editor
@@ -272,6 +273,7 @@ def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:
   draw-horizontal screen, row, left, right, 9480/horizontal-dotted
   row <- add row, 1
   clear-screen-from screen, row, left, left, right
+  assert-no-scroll screen, old-top-idx
 ]
 
 scenario editor-handles-empty-event-queue [
@@ -1057,6 +1059,9 @@ after <handle-special-key> [
 def draw-horizontal screen:&:screen, row:num, x:num, right:num -> screen:&:screen [
   local-scope
   load-ingredients
+  height:num <- screen-height screen
+  past-bottom?:bool <- greater-or-equal row, height
+  return-if past-bottom?
   style:char, style-found?:bool <- next-ingredient
   {
     break-if style-found?
diff --git a/sandbox/004-programming-environment.mu b/sandbox/004-programming-environment.mu
index 0f1280e3..53f9f846 100644
--- a/sandbox/004-programming-environment.mu
+++ b/sandbox/004-programming-environment.mu
@@ -79,7 +79,6 @@ 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
@@ -93,7 +92,6 @@ def event-loop screen:&:screen, console:&:console, env:&:environment, resources:
       }
       +finish-event
       screen <- update-cursor screen, current-sandbox, env
-      show-screen screen
     }
     loop
   }
@@ -199,7 +197,7 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   local-scope
   load-ingredients
   trace 10, [app], [render all]
-  hide-screen screen
+  old-top-idx:num <- save-top-idx screen
   # top menu
   trace 11, [app], [render top menu]
   width:num <- screen-width screen
@@ -211,12 +209,12 @@ def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (addr
   print screen, [ run (F4) ], 255/white, 161/reddish
   #
   screen <- render-sandbox-side screen, env, render-editor
-  <render-components-end>
+  <render-components-end>  # no early returns permitted
   #
   current-sandbox:&:editor <- get *env, current-sandbox:offset
   screen <- update-cursor screen, current-sandbox, env
   #
-  show-screen screen
+  assert-no-scroll screen, old-top-idx
 ]
 
 # replaced in a later layer
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index e9500797..a7df7a54 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -793,9 +793,7 @@ 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
-    show-screen screen
     jump +finish-event
   }
 ]
@@ -822,9 +820,7 @@ 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
-    show-screen screen
     jump +finish-event
   }
 ]
diff --git a/sandbox/006-sandbox-copy.mu b/sandbox/006-sandbox-copy.mu
index 995f4c7c..4835f02e 100644
--- a/sandbox/006-sandbox-copy.mu
+++ b/sandbox/006-sandbox-copy.mu
@@ -140,10 +140,8 @@ 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 ddfbf692..107c861c 100644
--- a/sandbox/007-sandbox-delete.mu
+++ b/sandbox/007-sandbox-delete.mu
@@ -69,10 +69,8 @@ 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 cb19ebc4..ec4fd578 100644
--- a/sandbox/008-sandbox-edit.mu
+++ b/sandbox/008-sandbox-edit.mu
@@ -111,10 +111,8 @@ 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 d7b8ed62..1c24bcb8 100644
--- a/sandbox/009-sandbox-test.mu
+++ b/sandbox/009-sandbox-test.mu
@@ -132,10 +132,8 @@ 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 f81d4151..27f2915a 100644
--- a/sandbox/010-sandbox-trace.mu
+++ b/sandbox/010-sandbox-trace.mu
@@ -190,10 +190,8 @@ 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 1ae275ce..0ee4b283 100644
--- a/termbox/termbox.c
+++ b/termbox/termbox.c
@@ -23,19 +23,10 @@ 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 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;
 
@@ -45,22 +36,12 @@ static int termh = -1;
 static int inout;
 static int winch_fds[2];
 
-static int lastx = LAST_COORD_INIT;
-static int lasty = LAST_COORD_INIT;
 static int cursor_x = 0;
 static int cursor_y = 0;
 
 static uint16_t background = TB_BLACK;
 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);
@@ -121,11 +102,6 @@ int tb_init(void)
   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;
 }
 
@@ -145,8 +121,6 @@ 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;
@@ -157,76 +131,12 @@ int tb_is_active(void)
   return termw != -1;
 }
 
-void tb_present() {
-  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 (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;
-    }
-  }
-  write_cursor(cursor_x, cursor_y);
-  bytebuffer_flush(&output_buffer, inout);
-}
-
-void tb_set_cursor(int cx, int cy)
-{
-  assert(termw != -1);
-  cursor_x = cx;
-  cursor_y = cy;
-  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);
-  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;
+  send_attr(fg, bg);
+  send_char(x, y, ch);
+  bytebuffer_flush(&output_buffer, inout);
 }
 
 int tb_poll_event(struct tb_event *event)
@@ -263,7 +173,7 @@ void tb_clear(void)
     update_size();
     buffer_size_change_request = 0;
   }
-  cellbuf_clear(&back_buffer);
+  send_clear();
 }
 
 void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
@@ -293,73 +203,14 @@ static int convertnum(uint32_t num, char* buf) {
 #define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
 #define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
 
-static void write_cursor(int x, int y) {
+void tb_set_cursor(int x, int y) {
   char buf[32];
   WRITE_LITERAL("\033[");
   WRITE_INT(y+1);
   WRITE_LITERAL(";");
   WRITE_INT(x+1);
   WRITE_LITERAL("H");
-}
-
-static void write_sgr(uint16_t fg, uint16_t bg) {
-  char buf[32];
-  WRITE_LITERAL("\033[38;5;");
-  WRITE_INT(fg);
-  WRITE_LITERAL("m");
-  WRITE_LITERAL("\033[48;5;");
-  WRITE_INT(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);
+  bytebuffer_flush(&output_buffer, inout);
 }
 
 static void get_term_size(int *w, int *h)
@@ -402,7 +253,14 @@ static void send_attr(uint16_t fg, uint16_t bg)
       bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
     if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
       bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
-    write_sgr(fgcol, bgcol);
+    char buf[32];
+    WRITE_LITERAL("\033[38;5;");
+    WRITE_INT(fgcol);
+    WRITE_LITERAL("m");
+    WRITE_LITERAL("\033[48;5;");
+    WRITE_INT(bgcol);
+    WRITE_LITERAL("m");
+    bytebuffer_flush(&output_buffer, inout);
     lastfg = fg;
     lastbg = bg;
   }
@@ -413,9 +271,7 @@ static void send_char(int x, int y, uint32_t c)
   char buf[7];
   int bw = tb_utf8_unicode_to_char(buf, c);
   buf[bw] = '\0';
-  if (x-1 != lastx || y != lasty)
-    write_cursor(x, y);
-  lastx = x; lasty = y;
+  tb_set_cursor(x, y);
   if(!c) buf[0] = ' '; // replace 0 with whitespace
   bytebuffer_puts(&output_buffer, buf);
 }
@@ -432,16 +288,8 @@ static void send_clear(void)
 {
   send_attr(foreground, background);
   bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
-  write_cursor(cursor_x, cursor_y);
+  tb_set_cursor(cursor_x, cursor_y);
   bytebuffer_flush(&output_buffer, inout);
-
-  /* we need to invalidate cursor position too and these two vars are
-   * used only for simple cursor positioning optimization, cursor
-   * actually may be in the correct place, but we simply discard
-   * optimization once and it gives us simple solution for the case when
-   * cursor moved */
-  lastx = LAST_COORD_INIT;
-  lasty = LAST_COORD_INIT;
 }
 
 static void sigwinch_handler(int xxx)
@@ -455,9 +303,6 @@ 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 64c1c4eb..97306142 100644
--- a/termbox/termbox.h
+++ b/termbox/termbox.h
@@ -8,19 +8,12 @@ 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 in tb_cell.fg and tb_cell.bg. */
+/* Names for some foreground/background colors. */
 #define TB_BLACK 232
 #define TB_WHITE 255
 
-/* Colors in tb_cell can be combined using bitwise-OR with multiple
- * of the following attributes. */
+/* Some attributes of screen cells that can be combined with colors using
+ * bitwise-OR. */
 #define TB_BOLD      0x0100
 #define TB_UNDERLINE 0x0200
 #define TB_REVERSE   0x0400
@@ -44,17 +37,6 @@ int tb_is_active(void);
 int tb_width(void);
 int tb_height(void);
 
-/* 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);
-
-/* 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);
@@ -63,8 +45,7 @@ void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
 /* Move the cursor. Upper-left character is (0, 0). */
 void tb_set_cursor(int cx, int cy);
 
-/* Modify a specific cell of the screen. Don't forget to call tb_present() to
- * commit your changes. */
+/* Modify a specific cell of the screen. */
 void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg);
 
 /*** 2. Controlling keyboard events. */
diff --git a/termbox/x.cc b/termbox/x.cc
index f6b04693..1b715746 100644
--- a/termbox/x.cc
+++ b/termbox/x.cc
@@ -1,12 +1,18 @@
 #include<iostream>
+using std::cout;
 #include"termbox.h"
 
 int main() {
   tb_init();
+  std::setvbuf(stdout, NULL, _IONBF, 0);
+  cout << tb_width() << ' ' << tb_height();
   tb_event x;
-  tb_poll_event(&x);
-  std::cout << "a\nb\r\nc\r\n";
-  tb_poll_event(&x);
+  for (int col = 0; col <= tb_width(); ++col) {
+    tb_set_cursor(col, 1);
+    tb_poll_event(&x);
+    cout << "a";
+    tb_poll_event(&x);
+  }
   tb_shutdown();
   return 0;
 }