about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-05-11 18:00:11 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-05-11 18:00:11 -0700
commit1156971774b307bec29fab34a523eb39a7904174 (patch)
tree1b7cc1829fa5f491ac9bbfc58d757bc69aed68fc
parentbf3c3d30246c4e955f418f7b34cd29b1c2e8bec4 (diff)
downloadmu-1156971774b307bec29fab34a523eb39a7904174.tar.gz
2953 - use pgup/pgdn to scroll through sandboxes
In the process I've also simplified the sandbox/ app. Since it's
impossible for sandbox editors to span multiple pages, we can drop all
scroll support altogether.
-rw-r--r--edit/005-sandbox.mu85
-rw-r--r--edit/006-sandbox-edit.mu10
-rw-r--r--edit/007-sandbox-delete.mu12
-rw-r--r--sandbox/002-typing.mu2
-rw-r--r--sandbox/003-shortcuts.mu1344
-rw-r--r--sandbox/005-sandbox.mu71
-rw-r--r--sandbox/006-sandbox-edit.mu10
-rw-r--r--sandbox/007-sandbox-delete.mu12
-rw-r--r--sandbox/011-editor-undo.mu209
9 files changed, 127 insertions, 1628 deletions
diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu
index 80dd4120..9ae94ee3 100644
--- a/edit/005-sandbox.mu
+++ b/edit/005-sandbox.mu
@@ -556,27 +556,19 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
   assume-console [
     # create a sandbox
     press F4
-    # switch to sandbox editor and type in 2 lines
-    press ctrl-n
-    type [abc
-]
-  ]
-  run [
-    event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
-    4:character/cursor <- copy 9251/␣
-    print screen:address:screen, 4:character/cursor
   ]
+  event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
   screen-should-contain [
     .                              .  # minor: F4 clears menu tooltip in very narrow screens
-    .               ┊abc           .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣             .
-    .               ┊━━━━━━━━━━━━━━.
+    .               ┊              .
+    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.
     .               ┊0            x.
     .               ┊add 2, 2      .
   ]
-  # hit 'down' at bottom of sandbox editor
+  # switch to sandbox window and hit 'page-down'
   assume-console [
-    press down-arrow
+    press ctrl-n
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -594,36 +586,31 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
     .               ┊━━━━━━━━━━━━━━.
     .               ┊              .
   ]
-  # hit 'up'
+  # hit 'page-up'
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
     4:character/cursor <- copy 9251/␣
     print screen:address:screen, 4:character/cursor
   ]
-  # sandbox editor displays again
+  # sandbox editor displays again, cursor is in editor
   screen-should-contain [
     .                              .  # minor: F4 clears menu tooltip in very narrow screens
-    .               ┊abc           .
-    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣             .
-    .               ┊━━━━━━━━━━━━━━.
+    .               ┊␣             .
+    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━.
     .               ┊0            x.
     .               ┊add 2, 2      .
   ]
 ]
 
-# down on sandbox side updates render-from when sandbox editor has cursor at bottom
+# page-down on sandbox side updates render-from to scroll sandboxes
 after <global-keypress> [
   {
     break-unless sandbox-in-focus?
-    down?:boolean <- equal k, 65516/down-arrow
-    break-unless down?
-    sandbox-bottom:number <- get *current-sandbox, bottom:offset
-    sandbox-cursor:number <- get *current-sandbox, cursor-row:offset
-    sandbox-cursor-on-last-line?:boolean <- equal sandbox-bottom, sandbox-cursor
-    break-unless sandbox-cursor-on-last-line?
+    page-down?:boolean <- equal k, 65518/page-down
+    break-unless page-down?
     sandbox:address:sandbox-data <- get *env, sandbox:offset
     break-unless sandbox
     # slide down if possible
@@ -656,12 +643,12 @@ after <update-cursor-special-cases> [
   }
 ]
 
-# 'up' on sandbox side is like 'down': updates render-from when necessary
+# 'page-up' on sandbox side is like 'page-down': updates render-from when necessary
 after <global-keypress> [
   {
     break-unless sandbox-in-focus?
-    up?:boolean <- equal k, 65517/up-arrow
-    break-unless up?
+    page-up?:boolean <- equal k, 65519/page-up
+    break-unless page-up?
     render-from:number <- get *env, render-from:offset
     at-beginning?:boolean <- equal render-from, -1
     break-if at-beginning?
@@ -760,9 +747,9 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊add 2, 2      .
     .               ┊4             .
   ]
-  # hit 'down'
+  # hit 'page-down'
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -783,9 +770,9 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊4             .
     .               ┊━━━━━━━━━━━━━━.
   ]
-  # hit 'down' again
+  # hit 'page-down' again
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -801,9 +788,9 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊              .
     .               ┊              .
   ]
-  # hit 'down' again
+  # hit 'page-down' again
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -819,9 +806,9 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊              .
     .               ┊              .
   ]
-  # hit 'up'
+  # hit 'page-up'
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -839,9 +826,9 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊4             .
     .               ┊━━━━━━━━━━━━━━.
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -861,12 +848,14 @@ scenario scrolling-through-multiple-sandboxes [
     .               ┊add 2, 2      .
     .               ┊4             .
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
+    4:character/cursor <- copy 9251/␣
+    print screen:address:screen, 4:character/cursor
   ]
   # no change
   screen-should-contain [
@@ -908,9 +897,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .               ┊━━━━━━━━━━━━━━.
     .               ┊              .
   ]
-  # hit 'down' and 'up' a couple of times. sandbox index should be stable
+  # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -926,9 +915,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .               ┊━━━━━━━━━━━━━━.
     .               ┊              .
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
@@ -944,9 +933,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .               ┊━━━━━━━━━━━━━━.
     .               ┊              .
   ]
-  # hit 'down'
+  # hit 'page-down'
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
diff --git a/edit/006-sandbox-edit.mu b/edit/006-sandbox-edit.mu
index 257aed22..6d83cee2 100644
--- a/edit/006-sandbox-edit.mu
+++ b/edit/006-sandbox-edit.mu
@@ -200,8 +200,8 @@ scenario editing-sandbox-after-scrolling-resets-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
   screen-should-contain [
@@ -281,9 +281,9 @@ scenario editing-sandbox-updates-sandbox-count [
   ]
   # now try to scroll past end
   assume-console [
-    press down-arrow
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
diff --git a/edit/007-sandbox-delete.mu b/edit/007-sandbox-delete.mu
index 3df11a4a..4f77f205 100644
--- a/edit/007-sandbox-delete.mu
+++ b/edit/007-sandbox-delete.mu
@@ -153,7 +153,7 @@ scenario deleting-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
   screen-should-contain [
@@ -199,7 +199,7 @@ scenario deleting-top-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
   screen-should-contain [
@@ -245,8 +245,8 @@ scenario deleting-final-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
   screen-should-contain [
@@ -310,8 +310,8 @@ scenario deleting-updates-sandbox-count [
   # delete the second sandbox, then try to scroll down twice
   assume-console [
     left-click 3, 29
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 3:address:programming-environment-data
diff --git a/sandbox/002-typing.mu b/sandbox/002-typing.mu
index 88079641..2f1051c1 100644
--- a/sandbox/002-typing.mu
+++ b/sandbox/002-typing.mu
@@ -704,7 +704,6 @@ after <insert-character-special-case> [
     {
       below-screen?:boolean <- greater-or-equal cursor-row, screen-height
       break-unless below-screen?
-      <scroll-down>
     }
     go-render? <- copy 1/true
     return
@@ -852,7 +851,6 @@ def insert-new-line-and-indent editor:address:editor-data, screen:address:screen
   {
     below-screen?:boolean <- greater-or-equal cursor-row, screen-height  # must be equal, never greater
     break-unless below-screen?
-    <scroll-down>
     go-render? <- copy 1/true
     cursor-row <- subtract cursor-row, 1  # bring back into screen range
     *editor <- put *editor, cursor-row:offset, cursor-row
diff --git a/sandbox/003-shortcuts.mu b/sandbox/003-shortcuts.mu
index 40956cb3..46ab636e 100644
--- a/sandbox/003-shortcuts.mu
+++ b/sandbox/003-shortcuts.mu
@@ -89,21 +89,19 @@ def delete-before-cursor editor:address:editor-data, screen:address:screen -> ed
   prev:address:duplex-list:character <- prev before-cursor
   go-render?, backspaced-cell <- copy 0/no-more-render, 0/nothing-deleted
   return-unless prev
+  go-render? <- copy 1/true
   trace 10, [app], [delete-before-cursor]
   original-row:number <- get *editor, cursor-row:offset
-  editor, scroll?:boolean <- move-cursor-coordinates-left editor
+  editor <- move-cursor-coordinates-left editor
   backspaced-cell:address:duplex-list:character <- copy before-cursor
   data <- remove before-cursor, data  # will also neatly trim next/prev pointers in backspaced-cell/before-cursor
   before-cursor <- copy prev
   *editor <- put *editor, before-cursor:offset, before-cursor
-  go-render? <- copy 1/true
-  return-if scroll?
   screen-width:number <- screen-width screen
   cursor-row:number <- get *editor, cursor-row:offset
   cursor-column:number <- get *editor, cursor-column:offset
   # did we just backspace over a newline?
   same-row?:boolean <- equal cursor-row, original-row
-  go-render? <- copy 1/true
   return-unless same-row?
   left:number <- get *editor, left:offset
   right:number <- get *editor, right:offset
@@ -113,7 +111,6 @@ def delete-before-cursor editor:address:editor-data, screen:address:screen -> ed
   {
     # hit right margin? give up and let caller render
     at-right?:boolean <- greater-or-equal curr-column, right
-    go-render? <- copy 1/true
     return-if at-right?
     break-unless curr
     # newline? done.
@@ -131,7 +128,7 @@ def delete-before-cursor editor:address:editor-data, screen:address:screen -> ed
   go-render? <- copy 0/false
 ]
 
-def move-cursor-coordinates-left editor:address:editor-data -> editor:address:editor-data, go-render?:boolean [
+def move-cursor-coordinates-left editor:address:editor-data -> editor:address:editor-data [
   local-scope
   load-ingredients
   before-cursor:address:duplex-list:character <- get *editor, before-cursor:offset
@@ -145,12 +142,10 @@ def move-cursor-coordinates-left editor:address:editor-data -> editor:address:ed
     trace 10, [app], [decrementing cursor column]
     cursor-column <- subtract cursor-column, 1
     *editor <- put *editor, cursor-column:offset, cursor-column
-    go-render? <- copy 0/false
     return
   }
   # if at left margin, we must move to previous row:
   top-of-screen?:boolean <- equal cursor-row, 1  # exclude menu bar
-  go-render?:boolean <- copy 0/false
   {
     break-if top-of-screen?
     cursor-row <- subtract cursor-row, 1
@@ -158,8 +153,7 @@ def move-cursor-coordinates-left editor:address:editor-data -> editor:address:ed
   }
   {
     break-unless top-of-screen?
-    <scroll-up>
-    go-render? <- copy 1/true
+    # no scroll, so do nothing
   }
   {
     # case 1: if previous character was newline, figure out how long the previous line is
@@ -452,7 +446,6 @@ def move-cursor-coordinates-right editor:address:editor-data, screen-height:numb
     below-screen?:boolean <- greater-or-equal cursor-row, screen-height  # must be equal
     go-render? <- copy 0/false
     return-unless below-screen?
-    <scroll-down>
     cursor-row <- subtract cursor-row, 1  # bring back into screen range
     *editor <- put *editor, cursor-row:offset, cursor-row
     go-render? <- copy 1/true
@@ -476,7 +469,6 @@ def move-cursor-coordinates-right editor:address:editor-data, screen-height:numb
     *editor <- put *editor, cursor-column:offset, cursor-column
     below-screen?:boolean <- greater-or-equal cursor-row, screen-height  # must be equal
     return-unless below-screen?, editor/same-as-ingredient:0, 0/no-more-render
-    <scroll-down>
     cursor-row <- subtract cursor-row, 1  # bring back into screen range
     *editor <- put *editor, cursor-row:offset, cursor-row
     go-render? <- copy 1/true
@@ -707,7 +699,7 @@ after <handle-special-key> [
     go-render? <- copy 0/false
     return-unless prev
     <move-cursor-begin>
-    editor, go-render? <- move-cursor-coordinates-left editor
+    editor <- move-cursor-coordinates-left editor
     before-cursor <- copy prev
     *editor <- put *editor, before-cursor:offset, before-cursor
     undo-coalesce-tag:number <- copy 1/left-arrow
@@ -1033,10 +1025,9 @@ def move-to-previous-line editor:address:editor-data -> editor:address:editor-da
     return
   }
   {
-    # if cursor already at top, scroll up
+    # if cursor already at top, do nothing
     break-unless already-at-top?
-    <scroll-up>
-    go-render? <- copy 1/true
+    go-render? <- copy 0/true
     return
   }
 ]
@@ -1196,14 +1187,14 @@ after <handle-special-key> [
     move-to-next-line?:boolean <- equal k, 65516/down-arrow
     break-unless move-to-next-line?
     <move-cursor-begin>
-    editor, go-render? <- move-to-next-line editor, screen-height
+    editor <- move-to-next-line editor, screen-height
     undo-coalesce-tag:number <- copy 4/down-arrow
     <move-cursor-end>
     return
   }
 ]
 
-def move-to-next-line editor:address:editor-data, screen-height:number -> editor:address:editor-data, go-render?:boolean [
+def move-to-next-line editor:address:editor-data, screen-height:number -> editor:address:editor-data [
   local-scope
   load-ingredients
   cursor-row:number <- get *editor, cursor-row:offset
@@ -1213,50 +1204,36 @@ def move-to-next-line editor:address:editor-data, screen-height:number -> editor
   right:number <- get *editor, right:offset
   last-line:number <- subtract screen-height, 1
   already-at-bottom?:boolean <- greater-or-equal cursor-row, last-line
+  # if cursor not at bottom
+  return-if already-at-bottom?
+  # scan to start of next line, then to right column or until end of line
+  max:number <- subtract right, left
+  next-line:address:duplex-list:character <- before-start-of-next-line before-cursor, max
+  # already at end of buffer? do nothing
+  no-motion?:boolean <- equal next-line, before-cursor
+  return-if no-motion?
+  cursor-row <- add cursor-row, 1
+  *editor <- put *editor, cursor-row:offset, cursor-row
+  before-cursor <- copy next-line
+  *editor <- put *editor, before-cursor:offset, before-cursor
+  target-column:number <- copy cursor-column
+  cursor-column <- copy left
+  *editor <- put *editor, cursor-column:offset, cursor-column
   {
-    # if cursor not at bottom, move it
-    break-if already-at-bottom?
-    # scan to start of next line, then to right column or until end of line
-    max:number <- subtract right, left
-    next-line:address:duplex-list:character <- before-start-of-next-line before-cursor, max
-    {
-      # already at end of buffer? try to scroll up (so we can see more
-      # warnings or sandboxes below)
-      no-motion?:boolean <- equal next-line, before-cursor
-      break-unless no-motion?
-      scroll?:boolean <- greater-than cursor-row, 1
-      break-if scroll?, +try-to-scroll:label
-      go-render? <- copy 0/false
-      return
-    }
-    cursor-row <- add cursor-row, 1
-    *editor <- put *editor, cursor-row:offset, cursor-row
-    before-cursor <- copy next-line
+    done?:boolean <- greater-or-equal cursor-column, target-column
+    break-if done?
+    curr:address:duplex-list:character <- next before-cursor
+    break-unless curr
+    currc:character <- get *curr, value:offset
+    at-newline?:boolean <- equal currc, 10/newline
+    break-if at-newline?
+    #
+    before-cursor <- copy curr
     *editor <- put *editor, before-cursor:offset, before-cursor
-    target-column:number <- copy cursor-column
-    cursor-column <- copy left
+    cursor-column <- add cursor-column, 1
     *editor <- put *editor, cursor-column:offset, cursor-column
-    {
-      done?:boolean <- greater-or-equal cursor-column, target-column
-      break-if done?
-      curr:address:duplex-list:character <- next before-cursor
-      break-unless curr
-      currc:character <- get *curr, value:offset
-      at-newline?:boolean <- equal currc, 10/newline
-      break-if at-newline?
-      #
-      before-cursor <- copy curr
-      *editor <- put *editor, before-cursor:offset, before-cursor
-      cursor-column <- add cursor-column, 1
-      *editor <- put *editor, cursor-column:offset, cursor-column
-      loop
-    }
-    go-render? <- copy 0/false
-    return
+    loop
   }
-  +try-to-scroll
-  <scroll-down>
-  go-render? <- copy 1/true
 ]
 
 scenario editor-adjusts-column-at-next-line [
@@ -1921,54 +1898,6 @@ scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
   ]
 ]
 
-# cursor-down can scroll if necessary
-
-scenario editor-can-scroll-down-using-arrow-keys [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with >3 lines
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # position cursor at last line, then try to move further down
-  assume-console [
-    left-click 3, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen slides by one line
-  screen-should-contain [
-    .          .
-    .b         .
-    .c         .
-    .d         .
-  ]
-]
-
-after <scroll-down> [
-  trace 10, [app], [scroll down]
-  top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-  left:number <- get *editor, left:offset
-  right:number <- get *editor, right:offset
-  max:number <- subtract right, left
-  old-top:address:duplex-list:character <- copy top-of-screen
-  top-of-screen <- before-start-of-next-line top-of-screen, max
-  *editor <- put *editor, top-of-screen:offset, top-of-screen
-  no-movement?:boolean <- equal old-top, top-of-screen
-  go-render? <- copy 0/false
-  return-if no-movement?
-]
-
 # takes a pointer into the doubly-linked list, scans ahead at most 'max'
 # positions until the next newline
 # beware: never return null pointer.
@@ -2000,343 +1929,6 @@ def before-start-of-next-line original:address:duplex-list:character, max:number
   return curr
 ]
 
-scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with a long, wrapped line and more than a screen of
-  # other lines
-  1:address:array:character <- new [abcdef
-g
-h
-i]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  screen-should-contain [
-    .          .
-    .abcd↩     .
-    .ef        .
-    .g         .
-  ]
-  # position cursor at last line, then try to move further down
-  assume-console [
-    left-click 3, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .ef        .
-    .g         .
-    .h         .
-  ]
-]
-
-scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # editor starts with a long line wrapping twice
-  1:address:array:character <- new [abcdefghij
-k
-l
-m]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at last line, then try to move further down
-  assume-console [
-    left-click 3, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line containing a wrap icon
-  screen-should-contain [
-    .          .
-    .efgh↩     .
-    .ij        .
-    .k         .
-  ]
-  # scroll down again
-  assume-console [
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .ij        .
-    .k         .
-    .l         .
-  ]
-]
-
-scenario editor-scrolls-down-when-line-wraps [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 5/width, 4/height
-  # editor contains a long line in the third line
-  1:address:array:character <- new [a
-b
-cdef]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at end, type a character
-  assume-console [
-    left-click 3, 4
-    type [g]
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .cdef↩.
-    .g    .
-  ]
-  memory-should-contain [
-    3 <- 3
-    4 <- 1
-  ]
-]
-
-scenario editor-scrolls-down-on-newline [
-  assume-screen 5/width, 4/height
-  # position cursor after last line and type newline
-  1:address:array:character <- new [a
-b
-c]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  assume-console [
-    left-click 3, 4
-    type [
-]
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .c    .
-    .     .
-  ]
-  memory-should-contain [
-    3 <- 3
-    4 <- 0
-  ]
-]
-
-scenario editor-scrolls-down-on-right-arrow [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 5/width, 4/height
-  # editor contains a wrapped line
-  1:address:array:character <- new [a
-b
-cdefgh]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at end of screen and try to move right
-  assume-console [
-    left-click 3, 3
-    press right-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .cdef↩.
-    .gh   .
-  ]
-  memory-should-contain [
-    3 <- 3
-    4 <- 0
-  ]
-]
-
-scenario editor-scrolls-down-on-right-arrow-2 [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 5/width, 4/height
-  # editor contains more lines than can fit on screen
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at end of screen and try to move right
-  assume-console [
-    left-click 3, 3
-    press right-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .c    .
-    .d    .
-  ]
-  memory-should-contain [
-    3 <- 3
-    4 <- 0
-  ]
-]
-
-scenario editor-scrolls-at-end-on-down-arrow [
-  assume-screen 10/width, 5/height
-  1:address:array:character <- new [abc
-de]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  $clear-trace
-  # try to move down past end of text
-  assume-console [
-    left-click 2, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen should scroll, moving cursor to end of text
-  memory-should-contain [
-    3 <- 1
-    4 <- 2
-  ]
-  assume-console [
-    type [0]
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .de0       .
-    .┈┈┈┈┈┈┈┈┈┈.
-    .          .
-  ]
-  # try to move down again
-  $clear-trace
-  assume-console [
-    left-click 2, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen stops scrolling because cursor is already at top
-  memory-should-contain [
-    3 <- 1
-    4 <- 3
-  ]
-  check-trace-count-for-label 0, [print-character]
-  assume-console [
-    type [1]
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .de01      .
-    .┈┈┈┈┈┈┈┈┈┈.
-    .          .
-  ]
-]
-
-scenario editor-combines-page-and-line-scroll [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with a few pages of lines
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f
-g]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # scroll down one page and one line
-  assume-console [
-    press page-down
-    left-click 3, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen scrolls down 3 lines
-  screen-should-contain [
-    .          .
-    .d         .
-    .e         .
-    .f         .
-  ]
-]
-
-# cursor-up can scroll if necessary
-
-scenario editor-can-scroll-up-using-arrow-keys [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with >3 lines
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # position cursor at top of second page, then try to move up
-  assume-console [
-    press page-down
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen slides by one line
-  screen-should-contain [
-    .          .
-    .b         .
-    .c         .
-    .d         .
-  ]
-]
-
-after <scroll-up> [
-  trace 10, [app], [scroll up]
-  top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-  old-top:address:duplex-list:character <- copy top-of-screen
-  top-of-screen <- before-previous-line top-of-screen, editor
-  *editor <- put *editor, top-of-screen:offset, top-of-screen
-  no-movement?:boolean <- equal old-top, top-of-screen
-  go-render? <- copy 0/false
-  return-if no-movement?
-]
-
 # takes a pointer into the doubly-linked list, scans back to before start of
 # previous *wrapped* line
 # beware: never return null pointer
@@ -2380,869 +1972,3 @@ def before-previous-line in:address:duplex-list:character, editor:address:editor
   }
   return curr
 ]
-
-scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with a long, wrapped line and more than a screen of
-  # other lines
-  1:address:array:character <- new [abcdef
-g
-h
-i]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  screen-should-contain [
-    .          .
-    .abcd↩     .
-    .ef        .
-    .g         .
-  ]
-  # position cursor at top of second page, just below wrapped line
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .g         .
-    .h         .
-    .i         .
-  ]
-  # now move up one line
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .ef        .
-    .g         .
-    .h         .
-  ]
-]
-
-scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
-  # screen has 1 line for menu + 4 lines
-  assume-screen 10/width, 5/height
-  # editor starts with a long line wrapping twice, occupying 3 of the 4 lines
-  1:address:array:character <- new [abcdefghij
-k
-l
-m]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at top of second page
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .k         .
-    .l         .
-    .m         .
-    .┈┈┈┈┈     .
-  ]
-  # move up one line
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .ij        .
-    .k         .
-    .l         .
-    .m         .
-  ]
-  # move up a second line
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .efgh↩     .
-    .ij        .
-    .k         .
-    .l         .
-  ]
-  # move up a third line
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .abcd↩     .
-    .efgh↩     .
-    .ij        .
-    .k         .
-  ]
-]
-
-# same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length
-# slightly off, just to prevent over-training
-scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with a long, wrapped line and more than a screen of
-  # other lines
-  1:address:array:character <- new [abcdef
-g
-h
-i]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 6/right
-  screen-should-contain [
-    .          .
-    .abcde↩    .
-    .f         .
-    .g         .
-  ]
-  # position cursor at top of second page, just below wrapped line
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .g         .
-    .h         .
-    .i         .
-  ]
-  # now move up one line
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows partial wrapped line
-  screen-should-contain [
-    .          .
-    .f         .
-    .g         .
-    .h         .
-  ]
-]
-
-# check empty lines
-scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
-  assume-screen 10/width, 4/height
-  # initialize editor with some lines around an empty line
-  1:address:array:character <- new [a
-b
-
-c
-d
-e]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 6/right
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .          .
-    .c         .
-    .d         .
-  ]
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .d         .
-    .e         .
-    .┈┈┈┈┈┈    .
-  ]
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .          .
-    .c         .
-    .d         .
-  ]
-]
-
-scenario editor-scrolls-up-on-left-arrow [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 5/width, 4/height
-  # editor contains >3 lines
-  1:address:array:character <- new [a
-b
-c
-d
-e]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at top of second page
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .     .
-    .c    .
-    .d    .
-    .e    .
-  ]
-  # now try to move left
-  assume-console [
-    press left-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .c    .
-    .d    .
-  ]
-  memory-should-contain [
-    3 <- 1
-    4 <- 1
-  ]
-]
-
-scenario editor-can-scroll-up-to-start-of-file [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with >3 lines
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # position cursor at top of second page, then try to move up to start of
-  # text
-  assume-console [
-    press page-down
-    press up-arrow
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen slides by one line
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # try to move up again
-  assume-console [
-    press up-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen remains unchanged
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-]
-
-# ctrl-f/page-down - render next page if it exists
-
-scenario editor-can-scroll [
-  assume-screen 10/width, 4/height
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows next page
-  screen-should-contain [
-    .          .
-    .c         .
-    .d         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-]
-
-after <handle-special-character> [
-  {
-    page-down?:boolean <- equal c, 6/ctrl-f
-    break-unless page-down?
-    old-top:address:duplex-list:character <- get *editor, top-of-screen:offset
-    <move-cursor-begin>
-    page-down editor
-    undo-coalesce-tag:number <- copy 0/never
-    <move-cursor-end>
-    top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-    no-movement?:boolean <- equal top-of-screen, old-top
-    go-render? <- not no-movement?
-    return
-  }
-]
-
-after <handle-special-key> [
-  {
-    page-down?:boolean <- equal k, 65518/page-down
-    break-unless page-down?
-    old-top:address:duplex-list:character <- get *editor, top-of-screen:offset
-    <move-cursor-begin>
-    page-down editor
-    undo-coalesce-tag:number <- copy 0/never
-    <move-cursor-end>
-    top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-    no-movement?:boolean <- equal top-of-screen, old-top
-    go-render? <- not no-movement?
-    return
-  }
-]
-
-# page-down skips entire wrapped lines, so it can't scroll past lines
-# taking up the entire screen
-def page-down editor:address:editor-data -> editor:address:editor-data [
-  local-scope
-  load-ingredients
-  # if editor contents don't overflow screen, do nothing
-  bottom-of-screen:address:duplex-list:character <- get *editor, bottom-of-screen:offset
-  return-unless bottom-of-screen
-  # if not, position cursor at final character
-  before-cursor:address:duplex-list:character <- get *editor, before-cursor:offset
-  before-cursor:address:duplex-list:character <- prev bottom-of-screen
-  *editor <- put *editor, before-cursor:offset, before-cursor
-  # keep one line in common with previous page
-  {
-    last:character <- get *before-cursor, value:offset
-    newline?:boolean <- equal last, 10/newline
-    break-unless newline?:boolean
-    before-cursor <- prev before-cursor
-    *editor <- put *editor, before-cursor:offset, before-cursor
-  }
-  # move cursor and top-of-screen to start of that line
-  move-to-start-of-line editor
-  before-cursor <- get *editor, before-cursor:offset
-  *editor <- put *editor, top-of-screen:offset, before-cursor
-]
-
-scenario editor-does-not-scroll-past-end [
-  assume-screen 10/width, 4/height
-  1:address:array:character <- new [a
-b]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen remains unmodified
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-]
-
-scenario editor-starts-next-page-at-start-of-wrapped-line [
-  # screen has 1 line for menu + 3 lines for text
-  assume-screen 10/width, 4/height
-  # editor contains a long last line
-  1:address:array:character <- new [a
-b
-cdefgh]
-  # editor screen triggers wrap of last line
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  # some part of last line is not displayed
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .cde↩      .
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows entire wrapped line
-  screen-should-contain [
-    .          .
-    .cde↩      .
-    .fgh       .
-    .┈┈┈┈      .
-  ]
-]
-
-scenario editor-starts-next-page-at-start-of-wrapped-line-2 [
-  # screen has 1 line for menu + 3 lines for text
-  assume-screen 10/width, 4/height
-  # editor contains a very long line that occupies last two lines of screen
-  # and still has something left over
-  1:address:array:character <- new [a
-bcdefgh]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  # some part of last line is not displayed
-  screen-should-contain [
-    .          .
-    .a         .
-    .bcd↩      .
-    .efg↩      .
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows entire wrapped line
-  screen-should-contain [
-    .          .
-    .bcd↩      .
-    .efg↩      .
-    .h         .
-  ]
-]
-
-# ctrl-b/page-up - render previous page if it exists
-
-scenario editor-can-scroll-up [
-  assume-screen 10/width, 4/height
-  1:address:array:character <- new [a
-b
-c
-d]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows next page
-  screen-should-contain [
-    .          .
-    .c         .
-    .d         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-  # scroll back up
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows original page again
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-]
-
-after <handle-special-character> [
-  {
-    page-up?:boolean <- equal c, 2/ctrl-b
-    break-unless page-up?
-    old-top:address:duplex-list:character <- get *editor, top-of-screen:offset
-    <move-cursor-begin>
-    editor <- page-up editor, screen-height
-    undo-coalesce-tag:number <- copy 0/never
-    <move-cursor-end>
-    top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-    no-movement?:boolean <- equal top-of-screen, old-top
-    go-render? <- not no-movement?
-    return
-  }
-]
-
-after <handle-special-key> [
-  {
-    page-up?:boolean <- equal k, 65519/page-up
-    break-unless page-up?
-    old-top:address:duplex-list:character <- get *editor, top-of-screen:offset
-    <move-cursor-begin>
-    editor <- page-up editor, screen-height
-    undo-coalesce-tag:number <- copy 0/never
-    <move-cursor-end>
-    top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-    no-movement?:boolean <- equal top-of-screen, old-top
-    # don't bother re-rendering if nothing changed. todo: test this
-    go-render? <- not no-movement?
-    return
-  }
-]
-
-def page-up editor:address:editor-data, screen-height:number -> editor:address:editor-data [
-  local-scope
-  load-ingredients
-  max:number <- subtract screen-height, 1/menu-bar, 1/overlapping-line
-  count:number <- copy 0
-  top-of-screen:address:duplex-list:character <- get *editor, top-of-screen:offset
-  {
-    done?:boolean <- greater-or-equal count, max
-    break-if done?
-    prev:address:duplex-list:character <- before-previous-line top-of-screen, editor
-    break-unless prev
-    top-of-screen <- copy prev
-    *editor <- put *editor, top-of-screen:offset, top-of-screen
-    count <- add count, 1
-    loop
-  }
-]
-
-scenario editor-can-scroll-up-multiple-pages [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 10/width, 4/height
-  # initialize editor with 8 lines
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f
-g
-h]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-  # scroll down two pages
-  assume-console [
-    press page-down
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows third page
-  screen-should-contain [
-    .          .
-    .e         .
-    .f         .
-    .g         .
-  ]
-  # scroll up
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows second page
-  screen-should-contain [
-    .          .
-    .c         .
-    .d         .
-    .e         .
-  ]
-  # scroll up again
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows original page again
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-  ]
-]
-
-scenario editor-can-scroll-up-wrapped-lines [
-  # screen has 1 line for menu + 5 lines for text
-  assume-screen 10/width, 6/height
-  # editor contains a long line in the first page
-  1:address:array:character <- new [a
-b
-cdefgh
-i
-j
-k
-l
-m
-n
-o]
-  # editor screen triggers wrap of last line
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  # some part of last line is not displayed
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .cde↩      .
-    .fgh       .
-    .i         .
-  ]
-  # scroll down a page and a line
-  assume-console [
-    press page-down
-    left-click 5, 0
-    press down-arrow
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows entire wrapped line
-  screen-should-contain [
-    .          .
-    .j         .
-    .k         .
-    .l         .
-    .m         .
-    .n         .
-  ]
-  # now scroll up one page
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen resets
-  screen-should-contain [
-    .          .
-    .b         .
-    .cde↩      .
-    .fgh       .
-    .i         .
-    .j         .
-  ]
-]
-
-scenario editor-can-scroll-up-wrapped-lines-2 [
-  # screen has 1 line for menu + 3 lines for text
-  assume-screen 10/width, 4/height
-  # editor contains a very long line that occupies last two lines of screen
-  # and still has something left over
-  1:address:array:character <- new [a
-bcdefgh]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  # some part of last line is not displayed
-  screen-should-contain [
-    .          .
-    .a         .
-    .bcd↩      .
-    .efg↩      .
-  ]
-  # scroll down
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen shows entire wrapped line
-  screen-should-contain [
-    .          .
-    .bcd↩      .
-    .efg↩      .
-    .h         .
-  ]
-  # scroll back up
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  # screen resets
-  screen-should-contain [
-    .          .
-    .a         .
-    .bcd↩      .
-    .efg↩      .
-  ]
-]
-
-scenario editor-can-scroll-up-past-nonempty-lines [
-  assume-screen 10/width, 4/height
-  # text with empty line in second screen
-  1:address:array:character <- new [axx
-bxx
-cxx
-dxx
-exx
-fxx
-gxx
-hxx
-]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  screen-should-contain [
-    .          .
-    .axx       .
-    .bxx       .
-    .cxx       .
-  ]
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .cxx       .
-    .dxx       .
-    .exx       .
-  ]
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .exx       .
-    .fxx       .
-    .gxx       .
-  ]
-  # scroll back up past empty line
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .cxx       .
-    .dxx       .
-    .exx       .
-  ]
-]
-
-scenario editor-can-scroll-up-past-empty-lines [
-  assume-screen 10/width, 4/height
-  # text with empty line in second screen
-  1:address:array:character <- new [axy
-bxy
-cxy
-
-dxy
-exy
-fxy
-gxy
-]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 4/right
-  screen-should-contain [
-    .          .
-    .axy       .
-    .bxy       .
-    .cxy       .
-  ]
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .cxy       .
-    .          .
-    .dxy       .
-  ]
-  assume-console [
-    press page-down
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .dxy       .
-    .exy       .
-    .fxy       .
-  ]
-  # scroll back up past empty line
-  assume-console [
-    press page-up
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .          .
-    .cxy       .
-    .          .
-    .dxy       .
-  ]
-]
diff --git a/sandbox/005-sandbox.mu b/sandbox/005-sandbox.mu
index 227a1ee0..01a6422c 100644
--- a/sandbox/005-sandbox.mu
+++ b/sandbox/005-sandbox.mu
@@ -546,17 +546,11 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
   assume-console [
     # create a sandbox
     press F4
-    # type in 2 lines
-    type [abc
-]
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
-  3:character/cursor <- copy 9251/␣
-  print screen:address:screen, 3:character/cursor
   screen-should-contain [
     .                               run (F4)           .
-    .abc                                               .
-    .␣                                                 .
+    .                                                  .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .0                                                x.
     .add 2, 2                                          .
@@ -564,9 +558,9 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down' at bottom of sandbox editor
+  # hit 'page-down'
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -584,9 +578,9 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'up'
+  # hit 'page-up'
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -596,7 +590,6 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
   # sandbox editor displays again
   screen-should-contain [
     .                               run (F4)           .
-    .abc                                               .
     .␣                                                 .
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .0                                                x.
@@ -607,15 +600,11 @@ scenario scrolling-down-past-bottom-of-sandbox-editor [
   ]
 ]
 
-# down on sandbox side updates render-from when sandbox editor has cursor at bottom
+# page-down updates render-from to scroll sandboxes
 after <global-keypress> [
   {
-    down?:boolean <- equal k, 65516/down-arrow
-    break-unless down?
-    sandbox-bottom:number <- get *current-sandbox, bottom:offset
-    sandbox-cursor:number <- get *current-sandbox, cursor-row:offset
-    sandbox-cursor-on-last-line?:boolean <- equal sandbox-bottom, sandbox-cursor
-    break-unless sandbox-cursor-on-last-line?
+    page-down?:boolean <- equal k, 65518/page-down
+    break-unless page-down?
     sandbox:address:sandbox-data <- get *env, sandbox:offset
     break-unless sandbox
     # slide down if possible
@@ -647,11 +636,11 @@ after <update-cursor-special-cases> [
   }
 ]
 
-# 'up' on sandbox side is like 'down': updates first-sandbox-to-render when necessary
+# 'page-up' is like 'page-down': updates first-sandbox-to-render when necessary
 after <global-keypress> [
   {
-    up?:boolean <- equal k, 65517/up-arrow
-    break-unless up?
+    page-up?:boolean <- equal k, 65519/page-up
+    break-unless page-up?
     render-from:number <- get *env, render-from:offset
     at-beginning?:boolean <- equal render-from, -1
     break-if at-beginning?
@@ -715,9 +704,9 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down'
+  # hit 'page-down'
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -739,9 +728,9 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down' again
+  # hit 'page-down' again
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -756,9 +745,9 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down' again
+  # hit 'page-down' again
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -773,9 +762,9 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'up'
+  # hit 'page-up'
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -794,9 +783,9 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -818,12 +807,14 @@ scenario scrolling-through-multiple-sandboxes [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
+    3:character/cursor <- copy 9251/␣
+    print screen:address:screen, 3:character/cursor
   ]
   # no change
   screen-should-contain [
@@ -866,9 +857,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down' and 'up' a couple of times. sandbox index should be stable
+  # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -884,9 +875,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'up' again
+  # hit 'page-up' again
   assume-console [
-    press up-arrow
+    press page-up
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
@@ -902,9 +893,9 @@ scenario scrolling-manages-sandbox-index-correctly [
     .━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
     .                                                  .
   ]
-  # hit 'down'
+  # hit 'page-down'
   assume-console [
-    press down-arrow
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
diff --git a/sandbox/006-sandbox-edit.mu b/sandbox/006-sandbox-edit.mu
index ae23c20b..5f527444 100644
--- a/sandbox/006-sandbox-edit.mu
+++ b/sandbox/006-sandbox-edit.mu
@@ -196,8 +196,8 @@ scenario editing-sandbox-after-scrolling-resets-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
@@ -276,9 +276,9 @@ scenario editing-sandbox-updates-sandbox-count [
   ]
   # now try to scroll past end
   assume-console [
-    press down-arrow
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
diff --git a/sandbox/007-sandbox-delete.mu b/sandbox/007-sandbox-delete.mu
index 1f0f03b4..f8ea3450 100644
--- a/sandbox/007-sandbox-delete.mu
+++ b/sandbox/007-sandbox-delete.mu
@@ -151,7 +151,7 @@ scenario deleting-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
@@ -199,7 +199,7 @@ scenario deleting-top-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
@@ -247,8 +247,8 @@ scenario deleting-final-sandbox-after-scroll [
     press F4
     type [add 1, 1]
     press F4
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
   screen-should-contain [
@@ -311,8 +311,8 @@ scenario deleting-updates-sandbox-count [
   # delete the second sandbox, then try to scroll down twice
   assume-console [
     left-click 3, 29
-    press down-arrow
-    press down-arrow
+    press page-down
+    press page-down
   ]
   run [
     event-loop screen:address:screen, console:address:console, 2:address:programming-environment-data
diff --git a/sandbox/011-editor-undo.mu b/sandbox/011-editor-undo.mu
index 75872cf0..14c156e9 100644
--- a/sandbox/011-editor-undo.mu
+++ b/sandbox/011-editor-undo.mu
@@ -670,7 +670,7 @@ scenario editor-can-redo-typing-and-enter-and-tab [
   ]
 ]
 
-# undo cursor movement and scroll
+# undo cursor movement
 
 scenario editor-can-undo-touch [
   # create an editor with some text
@@ -762,69 +762,6 @@ after <handle-undo> [
   }
 ]
 
-scenario editor-can-undo-scroll [
-  # screen has 1 line for menu + 3 lines
-  assume-screen 5/width, 4/height
-  # editor contains a wrapped line
-  1:address:array:character <- new [a
-b
-cdefgh]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 5/right
-  # position cursor at end of screen and try to move right
-  assume-console [
-    left-click 3, 3
-    press right-arrow
-  ]
-  editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  3:number <- get *2:address:editor-data, cursor-row:offset
-  4:number <- get *2:address:editor-data, cursor-column:offset
-  # screen scrolls
-  screen-should-contain [
-    .     .
-    .b    .
-    .cdef↩.
-    .gh   .
-  ]
-  memory-should-contain [
-    3 <- 3
-    4 <- 0
-  ]
-  # undo
-  assume-console [
-    press ctrl-z
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # cursor moved back
-  memory-should-contain [
-    3 <- 3
-    4 <- 3
-  ]
-  # scroll undone
-  screen-should-contain [
-    .     .
-    .a    .
-    .b    .
-    .cdef↩.
-  ]
-  # cursor should be in the right place
-  assume-console [
-    type [1]
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  ]
-  screen-should-contain [
-    .     .
-    .b    .
-    .cde1↩.
-    .fgh  .
-  ]
-]
-
 scenario editor-can-undo-left-arrow [
   # create an editor with some text
   assume-screen 10/width, 5/height
@@ -963,148 +900,6 @@ ghi]
   ]
 ]
 
-scenario editor-can-undo-ctrl-f [
-  # create an editor with multiple pages of text
-  assume-screen 10/width, 5/height
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  # scroll the page
-  assume-console [
-    press ctrl-f
-  ]
-  editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  # undo
-  assume-console [
-    press ctrl-z
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen should again show page 1
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-    .d         .
-  ]
-]
-
-scenario editor-can-undo-page-down [
-  # create an editor with multiple pages of text
-  assume-screen 10/width, 5/height
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  # scroll the page
-  assume-console [
-    press page-down
-  ]
-  editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  # undo
-  assume-console [
-    press ctrl-z
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen should again show page 1
-  screen-should-contain [
-    .          .
-    .a         .
-    .b         .
-    .c         .
-    .d         .
-  ]
-]
-
-scenario editor-can-undo-ctrl-b [
-  # create an editor with multiple pages of text
-  assume-screen 10/width, 5/height
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  # scroll the page down and up
-  assume-console [
-    press page-down
-    press ctrl-b
-  ]
-  editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  # undo
-  assume-console [
-    press ctrl-z
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen should again show page 2
-  screen-should-contain [
-    .          .
-    .d         .
-    .e         .
-    .f         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-]
-
-scenario editor-can-undo-page-up [
-  # create an editor with multiple pages of text
-  assume-screen 10/width, 5/height
-  1:address:array:character <- new [a
-b
-c
-d
-e
-f]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address:screen, 0/left, 10/right
-  editor-render screen, 2:address:editor-data
-  # scroll the page down and up
-  assume-console [
-    press page-down
-    press page-up
-  ]
-  editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-  # undo
-  assume-console [
-    press ctrl-z
-  ]
-  run [
-    editor-event-loop screen:address:screen, console:address:console, 2:address:editor-data
-    3:number <- get *2:address:editor-data, cursor-row:offset
-    4:number <- get *2:address:editor-data, cursor-column:offset
-  ]
-  # screen should again show page 2
-  screen-should-contain [
-    .          .
-    .d         .
-    .e         .
-    .f         .
-    .┈┈┈┈┈┈┈┈┈┈.
-  ]
-]
-
 scenario editor-can-undo-ctrl-a [
   # create an editor with some text
   assume-screen 10/width, 5/height
@@ -1333,7 +1128,7 @@ ghi]
   ]
 ]
 
-# redo cursor movement and scroll
+# redo cursor movement
 
 scenario editor-redo-touch [
   # create an editor with some text, click on a character, undo