about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-07-10 02:00:49 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-07-10 02:04:50 -0700
commit7402ce32ee0cd3301677d0037718b175868a56a8 (patch)
treeefe29f138b11d4029846e27113e0fa06b9e0a3f6
parent19323e7afc4b6c4da04c62ecaf05eb3768cacd93 (diff)
downloadmu-7402ce32ee0cd3301677d0037718b175868a56a8.tar.gz
1745 - hoist warning/response strings out of editor-data
Still ugly as hell. Some tests failing, but they're most likely
wrong. We need to test cursor positioning at the level of the
environment and take it away from the responsibilities of individual
editors. Also bring back the line at the bottom of each editor.

The non-test run ('main' in edit.mu) is completely borked. Sluggish as
hell, and I can't seem to switch focus to the sandbox editor.
-rw-r--r--036call_reply.cc18
-rw-r--r--050scenario.cc2
-rw-r--r--edit.mu584
3 files changed, 326 insertions, 278 deletions
diff --git a/036call_reply.cc b/036call_reply.cc
index 71aa4991..54866765 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -41,7 +41,11 @@ case REPLY: {
       vector<string> tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
       assert(SIZE(tmp) == 1);
       long long int ingredient_index = to_integer(tmp.at(0));
-      if (caller_instruction.products.at(i).value != caller_instruction.ingredients.at(ingredient_index).value)
+      if (ingredient_index >= SIZE(caller_instruction.ingredients))
+        raise << current_recipe_name() << ": 'same-as-ingredient' metadata overflows ingredients in: " << caller_instruction.to_string() << '\n';
+//?       cerr << caller_instruction.products.size() << ' ' << i << ' ' << caller_instruction.ingredients.size() << ' ' << ingredient_index << '\n'; //? 1
+//?       cerr << caller_instruction.to_string() << '\n'; //? 1
+      if (!is_dummy(caller_instruction.products.at(i)) && caller_instruction.products.at(i).value != caller_instruction.ingredients.at(ingredient_index).value)
         raise << current_recipe_name() << ": 'same-as-ingredient' result " << caller_instruction.products.at(i).value << " from call to " << callee << " must be location " << caller_instruction.ingredients.at(ingredient_index).value << '\n';
     }
   }
@@ -79,6 +83,18 @@ recipe test1 [
 ]
 +warn: main: 'same-as-ingredient' result 2 from call to test1 must be location 1
 
+:(scenario reply_same_as_ingredient_dummy)
+% Hide_warnings = true;
+recipe main [
+  1:number <- copy 0:literal
+  _ <- test1 1:number  # call with different ingredient and product
+]
+recipe test1 [
+  10:address:number <- next-ingredient
+  reply 10:address:number/same-as-ingredient:0
+]
+$warn: 0
+
 :(code)
 string to_string(const vector<double>& in) {
   if (in.empty()) return "[]";
diff --git a/050scenario.cc b/050scenario.cc
index 3b3aa962..a87c1b26 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -132,7 +132,7 @@ const scenario* Current_scenario = NULL;
 void run_mu_scenario(const scenario& s) {
   Current_scenario = &s;
   bool not_already_inside_test = !Trace_stream;
-//?   cerr << s.name << '\n'; //? 6
+//?   cerr << s.name << '\n'; //? 9
   if (not_already_inside_test) {
     Trace_file = s.name;
     Trace_stream = new trace_stream;
diff --git a/edit.mu b/edit.mu
index f21f5ea8..501fdd75 100644
--- a/edit.mu
+++ b/edit.mu
@@ -10,18 +10,28 @@ recipe main [
   reply z:number
 ]]
   initial-sandbox:address:array:character <- new [new-add 2:literal, 3:literal]
-  programming-environment 0:literal/screen, 0:literal/console, initial-recipe:address:array:character, initial-sandbox:address:array:character
+  env:address:programming-environment-data <- new-programming-environment 0:literal/screen, initial-recipe:address:array:character, initial-sandbox:address:array:character
+  render-all 0:literal/address, env:address:programming-environment-data
+  event-loop 0:literal/screen, 0:literal/console, env:address:programming-environment-data
 ]
 
-recipe programming-environment [
+container programming-environment-data [
+  recipes:address:editor-data
+  recipe-warnings:address:array:character
+  current-sandbox:address:editor-data
+  sandbox:sandbox-data
+  focus-in-sandbox?:boolean  # false => focus in recipes; true => focus in current-sandbox
+]
+
+recipe new-programming-environment [
   default-space:address:array:location <- new location:type, 30:literal
   screen:address <- next-ingredient
-  console:address <- next-ingredient
   initial-recipe-contents:address:array:character <- next-ingredient
   initial-sandbox-contents:address:array:character <- next-ingredient
   width:number <- screen-width screen:address
   height:number <- screen-height screen:address
   # top menu
+  result:address:programming-environment-data <- new programming-environment-data:type
   draw-horizontal screen:address, 0:literal, 0:literal/left, width:number, 32:literal/space, 0:literal/black, 238:literal/grey
   button-start:number <- subtract width:number, 20:literal
   button-on-screen?:boolean <- greater-or-equal button-start:number, 0:literal
@@ -33,15 +43,16 @@ recipe programming-environment [
   divider:number, _ <- divide-with-remainder width:number, 2:literal
   draw-vertical screen:address, divider:number, 1:literal/top, height:number, 9482:literal/vertical-dotted
   # recipe editor on the left
-  left-editor:address:editor-data <- new-editor initial-recipe-contents:address:array:character, screen:address, 0:literal/left, divider:number/right
+  recipes:address:address:editor-data <- get-address result:address:programming-environment-data/deref, recipes:offset
+  recipes:address:address:editor-data/deref <- new-editor initial-recipe-contents:address:array:character, screen:address, 0:literal/left, divider:number/right
   # sandbox editor on the right
   new-left:number <- add divider:number, 1:literal
   new-right:number <- add new-left:number, 5:literal
-  right-editor:address:editor-data <- new-editor initial-sandbox-contents:address:array:character, screen:address, new-left:number, width:number
+  current-sandbox:address:address:editor-data <- get-address result:address:programming-environment-data/deref, current-sandbox:offset
+  current-sandbox:address:address:editor-data/deref <- new-editor initial-sandbox-contents:address:array:character, screen:address, new-left:number, width:number
   # initialize cursor
-  update-cursor screen:address, left-editor:address:editor-data, right-editor:address:editor-data, 0:literal/focus-in-recipe
-  # and we're off!
-  event-loop screen:address, console:address, left-editor:address:editor-data, right-editor:address:editor-data
+  update-cursor screen:address, recipes:address:address:editor-data/deref, current-sandbox:address:address:editor-data/deref, 0:literal/focus-in-recipes
+  reply result:address:programming-environment-data
 ]
 
 scenario editor-initially-prints-string-to-screen [
@@ -60,23 +71,12 @@ scenario editor-initially-prints-string-to-screen [
 ## In which we introduce the editor data structure, and show how it displays
 ## text to the screen.
 
-container programming-environment-data [
-  recipes:address:editor-data
-  current-sandbox:address:editor-data
-  focus:boolean  # false => focus in recipes; true => focus in current-sandbox
-]
-
 container editor-data [
   # editable text: doubly linked list of characters (head contains a special sentinel)
   data:address:duplex-list
   # location before cursor inside data
   before-cursor:address:duplex-list
 
-  # string of non-editable text, in either regular grey..
-  response:address:array:character
-  # ..or red
-  warnings:address:array:character
-
   # raw bounds of display area on screen
   # always displays from row 1 and at most until bottom of screen
   left:number
@@ -108,11 +108,6 @@ recipe new-editor [
   x:address:number <- get-address result:address:editor-data/deref, cursor-row:offset
   x:address:number/deref <- copy 1:literal/top
   x:address:number <- get-address result:address:editor-data/deref, cursor-column:offset
-  response:address:address:array:character <- get-address result:address:editor-data/deref, response:offset
-  response:address:address:array:character/deref <- copy 0:literal
-#?   response:address:address:array:character/deref <- new [aaa] #? 1
-  warnings:address:address:array:character <- get-address result:address:editor-data/deref, warnings:offset
-  warnings:address:address:array:character/deref <- copy 0:literal
   x:address:number/deref <- copy left:number
   init:address:address:duplex-list <- get-address result:address:editor-data/deref, data:offset
   init:address:address:duplex-list/deref <- push-duplex 167:literal/§, 0:literal/tail
@@ -139,7 +134,7 @@ recipe new-editor [
   y:address:address:duplex-list <- get-address result:address:editor-data/deref, before-cursor:offset
   y:address:address:duplex-list/deref <- copy init:address:address:duplex-list/deref
   # perform initial rendering to screen
-  result:address:editor-data, screen:address <- render screen:address, result:address:editor-data
+  result:address:editor-data, _, screen:address <- render screen:address, result:address:editor-data
   reply result:address:editor-data, screen:address/same-as-ingredient:0
 ]
 
@@ -152,12 +147,10 @@ scenario editor-initializes-without-data [
   memory-should-contain [
     # 2 (data) <- just the § sentinel
     # 3 (before cursor) <- the § sentinel
-    4 <- 0  # response
-    5 <- 0  # warnings
-    6 <- 2  # left
-    7 <- 4  # right  (inclusive)
-    8 <- 1  # cursor row
-    9 <- 2  # cursor column
+    4 <- 2  # left
+    5 <- 4  # right  (inclusive)
+    6 <- 1  # cursor row
+    7 <- 2  # cursor column
   ]
   screen-should-contain [
     .     .
@@ -170,7 +163,7 @@ recipe render [
   default-space:address:array:location <- new location:type, 40:literal
   screen:address <- next-ingredient
   editor:address:editor-data <- next-ingredient
-  reply-unless editor:address:editor-data, editor:address:editor-data/same-as-ingredient:1, screen:address/same-as-ingredient:0
+  reply-unless editor:address:editor-data, editor:address:editor-data/same-as-ingredient:1, 1:literal/top, screen:address/same-as-ingredient:0
   left:number <- get editor:address:editor-data/deref, left:offset
   screen-height:number <- screen-height screen:address
   right:number <- get editor:address:editor-data/deref, right:offset
@@ -252,12 +245,8 @@ recipe render [
     above-cursor-row?:boolean <- lesser-than row:number, cursor-row:address:number/deref
     before-cursor?:boolean <- or before-cursor-on-same-line?:boolean, above-cursor-row?:boolean
     break-unless before-cursor?:boolean
-#?     $print [pointed after all text
-#? ] #? 1
     cursor-row:address:number/deref <- copy row:number
     cursor-column:address:number/deref <- copy column:number
-#?     $print [render: cursor moved to ], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [ 
-#? ] #? 1
     # line not wrapped but cursor outside bounds? wrap cursor
     {
       too-far-right?:boolean <- greater-than cursor-column:address:number/deref, right:number
@@ -267,63 +256,10 @@ recipe render [
       above-screen-bottom?:boolean <- lesser-than cursor-row:address:number/deref, screen-height:number
       assert above-screen-bottom?:boolean, [unimplemented: wrapping cursor past bottom of screen]
     }
-#?     $print [now ], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [ 
-#? ] #? 1
     before-cursor:address:address:duplex-list/deref <- copy prev:address:duplex-list
-#?     new-prev:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset #? 1
-#?     $print [render Ω: cursor adjusted to after ], new-prev:character, [(], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [)
-#? ] #? 1
   }
-#?   $print [clearing ], row:number, [ ], column:number, [ ], right:number, [ 
-#? ] #? 2
   clear-line-delimited screen:address, column:number, right:number
-  row:number <- add row:number, 1:literal
-  {
-    # print warnings, or response if no warnings
-    warnings:address:array:character <- get editor:address:editor-data/deref, warnings:offset
-    {
-      break-unless warnings:address:array:character
-      row:number <- render-string screen:address, warnings:address:array:character, editor:address:editor-data, 1:literal/red, row:number
-    }
-    {
-      break-if warnings:address:array:character
-      response:address:array:character <- get editor:address:editor-data/deref, response:offset
-      break-unless response:address:array:character
-      row:number <- render-string screen:address, response:address:array:character, editor:address:editor-data, 245:literal/grey, row:number
-    }
-  }
-  {
-    # draw a line after
-    # hack: not for tests
-    break-if screen:address
-    {
-      break-if left:number  # hacky
-      # left side, recipe editor
-      draw-horizontal screen:address, row:number, left:number, right:number, 9480:literal/horizontal-dotted
-    }
-    {
-      break-unless left:number
-      # right side, sandbox editor
-      draw-horizontal screen:address, row:number, left:number, right:number, 9473:literal/horizontal-double
-    }
-    row:number <- add row:number, 1:literal
-  }
-  {
-    # clear one more line just in case we just backspaced out of it
-    done?:boolean <- greater-or-equal row:number, screen-height:number
-    break-if done?:boolean
-    draw-horizontal screen:address, row:number, left:number, right:number, 32:literal/space
-  }
-  # update cursor
-  {
-    cursor-inside-right-margin?:boolean <- lesser-or-equal cursor-column:address:number/deref, right:number
-    assert cursor-inside-right-margin?:boolean, [cursor outside right margin]
-    cursor-inside-left-margin?:boolean <- greater-or-equal cursor-column:address:number/deref, left:number
-    assert cursor-inside-left-margin?:boolean, [cursor outside left margin]
-    move-cursor screen:address, cursor-row:address:number/deref, cursor-column:address:number/deref
-  }
-  show-screen screen:address
-  reply editor:address:editor-data/same-as-ingredient:1, screen:address/same-as-ingredient:0
+  reply editor:address:editor-data/same-as-ingredient:1, row:number, screen:address/same-as-ingredient:0
 ]
 
 # row:number <- render-string s:address:array:character, editor:address:editor-data, color:number, row:number
@@ -336,6 +272,7 @@ recipe render-string [
   editor:address:editor-data <- next-ingredient
   color:number <- next-ingredient
   row:number <- next-ingredient
+  row:number <- add row:number, 1:literal
   reply-unless s:address:array:character, row:number
   left:number <- get editor:address:editor-data/deref, left:offset
   right:number <- get editor:address:editor-data/deref, right:offset
@@ -516,16 +453,14 @@ scenario editor-initializes-empty-text [
 
 ## handling events from the keyboard, mouse, touch screen, ...
 
-# takes two editors, sends each event from the console to each editor
 recipe event-loop [
   default-space:address:array:location <- new location:type, 30:literal
   screen:address <- next-ingredient
   console:address <- next-ingredient
-  recipes:address:editor-data <- next-ingredient
-  current-sandbox:address:editor-data <- next-ingredient
-  sandbox-in-focus?:boolean <- next-ingredient
-#?   $print [sandbox in focus? ], sandbox-in-focus?:boolean, [ 
-#? ] #? 1
+  env:address:programming-environment-data <- next-ingredient
+  recipes:address:editor-data <- get env:address:programming-environment-data/deref, recipes:offset
+  current-sandbox:address:editor-data <- get env:address:programming-environment-data/deref, current-sandbox:offset
+  sandbox-in-focus?:boolean <- get env:address:programming-environment-data/deref, focus-in-sandbox?:offset
   {
     # looping over each (keyboard or touch) event as it occurs
     +next-event
@@ -533,8 +468,6 @@ recipe event-loop [
     loop-unless found?:boolean
     break-if quit?:boolean  # only in tests
     trace [app], [next-event]
-#?     $print [--- new event
-#? ] #? 1
     # check for global events that will trigger regardless of which editor has focus
     {
       k:address:number <- maybe-convert e:event, keycode:variant
@@ -543,9 +476,8 @@ recipe event-loop [
       {
         do-run?:boolean <- equal k:address:number/deref, 65526:literal/F10
         break-unless do-run?:boolean
-#?         trace [app], [run] #? 1
-        run-sandboxes recipes:address:editor-data, current-sandbox:address:editor-data
-        render-all screen:address, recipes:address:editor-data, current-sandbox:address:editor-data, sandbox-in-focus?:boolean
+        run-sandboxes env:address:programming-environment-data
+        render-all screen:address, env:address:programming-environment-data
         loop +next-event:label  # done with this event; no need to send to editors
       }
     }
@@ -553,24 +485,59 @@ recipe event-loop [
     {
       t:address:touch-event <- maybe-convert e:event, touch:variant
       break-unless t:address:touch-event
-#?       trace [app], [touch] #? 1
-      _ <- move-cursor-in-editor recipes:address:editor-data, t:address:touch-event/deref
-      sandbox-in-focus?:boolean <- move-cursor-in-editor current-sandbox:address:editor-data, t:address:touch-event/deref
-      render-all screen:address, recipes:address:editor-data, current-sandbox:address:editor-data, sandbox-in-focus?:boolean
+      _ <- move-cursor-in-editor screen:address, recipes:address:editor-data, t:address:touch-event/deref
+      sandbox-in-focus?:boolean <- move-cursor-in-editor screen:address, current-sandbox:address:editor-data, t:address:touch-event/deref
+      render-all screen:address, env:address:programming-environment-data
       loop +next-event:label
     }
     # if it's not global, send to appropriate editor
     {
       {
         break-if sandbox-in-focus?:boolean
-#?         $print [event in recipes
-#? ] #? 1
         handle-event screen:address, console:address, recipes:address:editor-data, e:event
       }
       {
         break-unless sandbox-in-focus?:boolean
-#?         $print [event in current-sandbox: ], sandbox-in-focus?:boolean, [ 
-#? ] #? 1
+        handle-event screen:address, console:address, current-sandbox:address:editor-data, e:event
+      }
+    }
+    render-all screen:address, env:address:programming-environment-data
+    loop
+  }
+]
+
+# takes two editors, sends each event from the console to each editor
+recipe editor-event-loop [
+  default-space:address:array:location <- new location:type, 30:literal
+  screen:address <- next-ingredient
+  console:address <- next-ingredient
+  recipes:address:editor-data <- next-ingredient
+  current-sandbox:address:editor-data <- next-ingredient
+  sandbox-in-focus?:boolean <- next-ingredient
+  {
+    # looping over each (keyboard or touch) event as it occurs
+    +next-event
+    e:event, console:address, found?:boolean, quit?:boolean <- read-event console:address
+    loop-unless found?:boolean
+    break-if quit?:boolean  # only in tests
+    trace [app], [next-event]
+    # 'touch' event - send to both editors
+    {
+      t:address:touch-event <- maybe-convert e:event, touch:variant
+      break-unless t:address:touch-event
+      _ <- move-cursor-in-editor screen:address, recipes:address:editor-data, t:address:touch-event/deref
+      sandbox-in-focus?:boolean <- move-cursor-in-editor screen:address, current-sandbox:address:editor-data, t:address:touch-event/deref
+      update-cursor screen:address, recipes:address:editor-data, current-sandbox:address:editor-data, sandbox-in-focus?:boolean
+      loop +next-event:label
+    }
+    # other events - send to appropriate editor
+    {
+      {
+        break-if sandbox-in-focus?:boolean
+        handle-event screen:address, console:address, recipes:address:editor-data, e:event
+      }
+      {
+        break-unless sandbox-in-focus?:boolean
         handle-event screen:address, console:address, current-sandbox:address:editor-data, e:event
       }
     }
@@ -727,6 +694,7 @@ recipe handle-event [
 # todo: ignores menu bar (for now just displays shortcuts)
 recipe move-cursor-in-editor [
   default-space:address:array:location <- new location:type, 30:literal
+  screen:address <- next-ingredient
   editor:address:editor-data <- next-ingredient
   t:touch-event <- next-ingredient
   reply-unless editor:address:editor-data, 0:literal/false
@@ -742,6 +710,7 @@ recipe move-cursor-in-editor [
   cursor-row:address:number/deref <- get t:touch-event, row:offset
   cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
   cursor-column:address:number/deref <- get t:touch-event, column:offset
+  editor:address:editor-data <- render screen:address, editor:address:editor-data
   # gain focus
   reply 1:literal/true
 ]
@@ -840,12 +809,63 @@ recipe previous-line-length [
 recipe render-all [
   default-space:address:array:location <- new location:type, 40:literal
   screen:address <- next-ingredient
-  recipes:address:editor-data <- next-ingredient
-  current-sandbox:address:editor-data <- next-ingredient
-  sandbox-in-focus?:boolean <- next-ingredient
-  render screen:address, recipes:address:editor-data
-  render screen:address, current-sandbox:address:editor-data
+  env:address:programming-environment-data <- next-ingredient
+  recipes:address:editor-data <- get env:address:programming-environment-data/deref, recipes:offset
+  current-sandbox:address:editor-data <- get env:address:programming-environment-data/deref, current-sandbox:offset
+  sandbox-in-focus?:boolean <- get env:address:programming-environment-data/deref, focus-in-sandbox?:offset
+  recipes:address:editor-data, row:number <- render screen:address, recipes:address:editor-data
+  # print warnings, or response if no warnings
+  recipe-warnings:address:array:character <- get env:address:programming-environment-data/deref, recipe-warnings:offset
+  {
+    break-unless recipe-warnings:address:array:character
+    row:number <- render-string screen:address, recipe-warnings:address:array:character, recipes:address:editor-data, 1:literal/red, row:number
+  }
+  left:number <- get current-sandbox:address:editor-data/deref, left:offset
+  _, row:number <- render screen:address, current-sandbox:address:editor-data
+  sandbox:address:sandbox-data <- get-address env:address:programming-environment-data/deref, sandbox:offset
+  sandbox-response:address:array:character <- get sandbox:address:sandbox-data/deref, response:offset
+  sandbox-warnings:address:array:character <- get sandbox:address:sandbox-data/deref, warnings:offset
+  {
+    break-unless sandbox-warnings:address:array:character
+    row:number <- render-string screen:address, sandbox-warnings:address:array:character, current-sandbox:address:editor-data, 1:literal/red, row:number
+  }
+  {
+    break-if sandbox-warnings:address:array:character
+    row:number <- render-string screen:address, sandbox-response:address:array:character, current-sandbox:address:editor-data, 245:literal/grey, row:number
+  }
+#?   {
+#?     # draw a line after
+#?     # hack: not for tests
+#?     break-if screen:address
+#?     {
+#?       break-if left:number  # hacky
+#?       # left side, recipe editor
+#?       draw-horizontal screen:address, row:number, left:number, right:number, 9480:literal/horizontal-dotted
+#?     }
+#?     {
+#?       break-unless left:number
+#?       # right side, sandbox editor
+#?       draw-horizontal screen:address, row:number, left:number, right:number, 9473:literal/horizontal-double
+#?     }
+#?     row:number <- add row:number, 1:literal
+#?   }
+#?   row:number <- add row:number, 1:literal
+#?   {
+#?     # clear one more line just in case we just backspaced out of it
+#?     done?:boolean <- greater-or-equal row:number, screen-height:number
+#?     break-if done?:boolean
+#?     draw-horizontal screen:address, row:number, left:number, right:number, 32:literal/space
+#?   }
+#?   # update cursor
+#?   {
+#?     cursor-inside-right-margin?:boolean <- lesser-or-equal cursor-column:address:number/deref, right:number
+#?     assert cursor-inside-right-margin?:boolean, [cursor outside right margin]
+#?     cursor-inside-left-margin?:boolean <- greater-or-equal cursor-column:address:number/deref, left:number
+#?     assert cursor-inside-left-margin?:boolean, [cursor outside left margin]
+#?     move-cursor screen:address, cursor-row:address:number/deref, cursor-column:address:number/deref
+#?   }
   update-cursor screen:address, recipes:address:editor-data, current-sandbox:address:editor-data, sandbox-in-focus?:boolean
+  show-screen screen:address
 ]
 
 recipe update-cursor [
@@ -875,7 +895,7 @@ scenario editor-handles-empty-event-queue [
   2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 10:literal/right
   assume-console []
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -892,7 +912,7 @@ scenario editor-handles-mouse-clicks [
     left-click 1, 1  # on the 'b'
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -915,7 +935,7 @@ scenario editor-handles-mouse-clicks-outside-text [
     left-click 1, 7  # last line, to the right of text
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -934,7 +954,7 @@ def]
     left-click 1, 7  # interior line, to the right of text
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -953,7 +973,7 @@ def]
     left-click 3, 7  # below text
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -973,7 +993,7 @@ scenario editor-handles-mouse-clicks-outside-column [
     left-click 3, 8
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -996,7 +1016,7 @@ scenario editor-inserts-characters-into-empty-editor [
     type [abc]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1015,7 +1035,7 @@ scenario editor-inserts-characters-at-cursor [
     type [d]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1033,7 +1053,7 @@ scenario editor-inserts-characters-at-cursor-2 [
     type [d]  # should append
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1051,7 +1071,7 @@ scenario editor-inserts-characters-at-cursor-3 [
     type [d]  # should append
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1070,7 +1090,7 @@ d]
     type [e]  # should append
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1090,7 +1110,7 @@ d]
     type [ef]  # should append multiple characters in order
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1109,7 +1129,7 @@ scenario editor-wraps-line-on-insert [
     type [e]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   # no wrap yet
   screen-should-contain [
@@ -1123,7 +1143,7 @@ scenario editor-wraps-line-on-insert [
     type [f]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   # now wrap
   screen-should-contain [
@@ -1142,7 +1162,7 @@ scenario editor-moves-cursor-after-inserting-characters [
     type [01]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1160,7 +1180,7 @@ scenario editor-wraps-cursor-after-inserting-characters [
     type [f]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1185,7 +1205,7 @@ scenario editor-wraps-cursor-after-inserting-characters-2 [
     type [f]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1210,7 +1230,7 @@ scenario editor-moves-cursor-down-after-inserting-newline [
 1]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1229,7 +1249,7 @@ scenario editor-moves-cursor-down-after-inserting-newline-2 [
 1]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1256,7 +1276,7 @@ scenario editor-clears-previous-line-completely-after-inserting-newline [
     .          .
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   # line should be fully cleared
   screen-should-contain [
@@ -1279,7 +1299,7 @@ scenario editor-handles-backspace-key [
   3:event/backspace <- merge 0:literal/text, 8:literal/backspace, 0:literal/dummy, 0:literal/dummy
   replace-in-console 171:literal/«, 3:event/backspace
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     4:number <- get 2:address:editor-data/deref, cursor-row:offset
     5:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1307,7 +1327,7 @@ d]
   3:event/backspace <- merge 0:literal/text, 8:literal/backspace, 0:literal/dummy, 0:literal/dummy
   replace-in-console 171:literal/«, 3:event/backspace
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1325,7 +1345,7 @@ scenario editor-moves-cursor-right-with-key [
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1347,7 +1367,7 @@ d]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1370,7 +1390,7 @@ d]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1389,7 +1409,7 @@ scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
     press 65514  # right arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1416,7 +1436,7 @@ scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
     press 65514  # right arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1429,7 +1449,7 @@ scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
     press 65514
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1448,7 +1468,7 @@ scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
     press 65514  # right arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1475,7 +1495,7 @@ d]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1495,7 +1515,7 @@ scenario editor-moves-cursor-left-with-key [
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1516,7 +1536,7 @@ d]
     press 65515  # left arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1543,7 +1563,7 @@ g]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1567,7 +1587,7 @@ g]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1592,7 +1612,7 @@ d]
     type [0]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
   ]
   screen-should-contain [
     .          .
@@ -1620,7 +1640,7 @@ scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
     press 65515  # left arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1640,7 +1660,7 @@ def]
     press 65517  # up arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1660,7 +1680,7 @@ def]
     press 65516  # down arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1681,7 +1701,7 @@ def]
     press 65517  # up arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1701,7 +1721,7 @@ de]
     press 65516  # down arrow
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data
+    editor-event-loop screen:address, console:address, 2:address:editor-data
     3:number <- get 2:address:editor-data/deref, cursor-row:offset
     4:number <- get 2:address:editor-data/deref, cursor-column:offset
   ]
@@ -1712,88 +1732,84 @@ de]
 ]
 
 scenario point-at-multiple-editors [
-  assume-screen 10:literal/width, 5:literal/height
-  # initialize recipe editor covering left half of screen
+  assume-screen 30:literal/width, 5:literal/height
+  # initialize both halves of screen
   1:address:array:character <- new [abc]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 5:literal/right
-  # initialize sandbox editor covering right half of screen
-  3:address:array:character <- new [def]
-  4:address:editor-data <- new-editor 3:address:array:character, screen:address, 5:literal/left, 10:literal/right
+  2:address:array:character <- new [def]
+  3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
   # focus on both sides
   assume-console [
     left-click 1, 1
-    left-click 1, 8
+    left-click 1, 17
   ]
   # check cursor column in each
   run [
-    event-loop screen:address, console:address, 2:address:editor-data, 4:address:editor-data
-    6:number <- get 2:address:editor-data/deref, cursor-column:offset
-    7:number <- get 4:address:editor-data/deref, cursor-column:offset
+    event-loop screen:address, console:address, 3:address:programming-environment-data
+    4:address:editor-data <- get 3:address:programming-environment-data/deref, recipes:offset
+    5:number <- get 4:address:editor-data/deref, cursor-column:offset
+    6:address:editor-data <- get 3:address:programming-environment-data/deref, current-sandbox:offset
+    7:number <- get 6:address:editor-data/deref, cursor-column:offset
   ]
   memory-should-contain [
-    6 <- 1
-    7 <- 8
+    5 <- 1
+    7 <- 17
   ]
 ]
 
 scenario editors-chain-to-cover-multiple-columns [
-  assume-screen 10:literal/width, 5:literal/height
-  # initialize recipe editor covering left half of screen
+  assume-screen 30:literal/width, 5:literal/height
+  # initialize both halves of screen
   1:address:array:character <- new [abc]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 5:literal/right
-  # initialize sandbox editor covering right half of screen
-  3:address:array:character <- new [def]
-  4:address:editor-data <- new-editor 3:address:array:character, screen:address, 5:literal/left, 10:literal/right
+  2:address:array:character <- new [def]
+  3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
   # type one letter in each of them
   assume-console [
     left-click 1, 1
     type [0]
-    left-click 1, 6
+    left-click 1, 17
     type [1]
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data, 4:address:editor-data
-    5:number <- get 2:address:editor-data/deref, cursor-column:offset
-    6:number <- get 4:address:editor-data/deref, cursor-column:offset
+    event-loop screen:address, console:address, 3:address:programming-environment-data
+    4:address:editor-data <- get 3:address:programming-environment-data/deref, recipes:offset
+    5:number <- get 4:address:editor-data/deref, cursor-column:offset
+    6:address:editor-data <- get 3:address:programming-environment-data/deref, current-sandbox:offset
+    7:number <- get 6:address:editor-data/deref, cursor-column:offset
   ]
   screen-should-contain [
-    .          .
-    .a0bc d1ef .
-    .          .
+    .           run (F10)          .
+    .a0bc           ┊d1ef          .
+    .               ┊              .
   ]
   memory-should-contain [
     5 <- 2  # cursor column of recipe editor
-    6 <- 7  # cursor column of sandbox editor
+    7 <- 18  # cursor column of sandbox editor
   ]
   # show the cursor at the right window
   run [
     screen:address <- print-character screen:address, 9251:literal/␣
   ]
   screen-should-contain [
-    .          .
-    .a0bc d1␣f .
-    .          .
+    .           run (F10)          .
+    .a0bc           ┊d1␣f          .
+    .               ┊              .
   ]
 ]
 
 scenario multiple-editors-cover-only-their-own-areas [
-  assume-screen 10:literal/width, 5:literal/height
+  assume-screen 60:literal/width, 10:literal/height
   run [
-    # draw a divider
-    draw-vertical screen:address, 5:literal/divider, 1:literal/top, 5:literal/height
-    # initialize editors on both sides of it
     1:address:array:character <- new [abc]
-    2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 5:literal/right
-    3:address:array:character <- new [def]
-    4:address:editor-data <- new-editor 3:address:array:character, screen:address, 6:literal/left, 10:literal/right
+    2:address:array:character <- new [def]
+    3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
   ]
   # divider isn't messed up
   screen-should-contain [
-    .          .
-    .abc  │def .
-    .     │    .
-    .     │    .
-    .     │    .
+    .                                         run (F10)          .
+    .abc                           ┊def                          .
+    .                              ┊                             .
+    .                              ┊                             .
+    .                              ┊                             .
   ]
 ]
 
@@ -1804,7 +1820,8 @@ scenario editor-in-focus-keeps-cursor [
   # initialize programming environment and highlight cursor
   assume-console []
   run [
-    programming-environment screen:address, console:address, 1:address:array:character, 2:address:array:character
+    3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
+    event-loop screen:address, console:address, 3:address:programming-environment-data
     screen:address <- print-character screen:address, 9251:literal/␣
   ]
   # is cursor at the right place?
@@ -1818,7 +1835,8 @@ scenario editor-in-focus-keeps-cursor [
     type [z]
   ]
   run [
-    programming-environment screen:address, console:address, 1:address:array:character, 2:address:array:character
+    3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
+    event-loop screen:address, console:address, 3:address:programming-environment-data
     screen:address <- print-character screen:address, 9251:literal/␣
   ]
   # cursor should still be right
@@ -1831,130 +1849,144 @@ scenario editor-in-focus-keeps-cursor [
 
 ## Running code from the editors
 
-recipe editor-contents [
-  default-space:address:array:location <- new location:type, 30:literal
-  editor:address:editor-data <- next-ingredient
-  buf:address:buffer <- new-buffer 80:literal
-  curr:address:duplex-list <- get editor:address:editor-data/deref, data:offset
-  # skip § sentinel
-  assert curr:address:duplex-list, [editor without data is illegal; must have at least a sentinel]
-  curr:address:duplex-list <- next-duplex curr:address:duplex-list
-  {
-    break-unless curr:address:duplex-list
-    c:character <- get curr:address:duplex-list/deref, value:offset
-    buffer-append buf:address:buffer, c:character
-    curr:address:duplex-list <- next-duplex curr:address:duplex-list
-    loop
-  }
-  result:address:array:character <- buffer-to-array buf:address:buffer
-  reply result:address:array:character
-]
-
-scenario editor-provides-edited-contents [
-  assume-screen 10:literal/width, 5:literal/height
-  1:address:array:character <- new [abc]
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 10:literal/right
-  assume-console [
-    left-click 1, 2
-    type [def]
-  ]
-  run [
-    event-loop screen:address, console:address, 2:address:editor-data
-    3:address:array:character <- editor-contents 2:address:editor-data
-    4:array:character <- copy 3:address:array:character/deref
-  ]
-  memory-should-contain [
-    4:string <- [abdefc]
-  ]
+container sandbox-data [
+  data:address:array:character
+  response:address:array:character
+  warnings:address:array:character
 ]
 
 scenario run-and-show-results [
-  assume-screen 60:literal/width, 5:literal/height
+  assume-screen 120:literal/width, 10:literal/height
   # recipe editor is empty
   1:address:array:character <- new []
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 5:literal/right
   # sandbox editor contains an instruction without storing outputs
-  3:address:array:character <- new [divide-with-remainder 11:literal, 3:literal]
-  4:address:editor-data <- new-editor 3:address:array:character, screen:address, 5:literal/left, 60:literal/right
+  2:address:array:character <- new [divide-with-remainder 11:literal, 3:literal]
+  3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
   # run the code in the editors
   assume-console [
     press 65526  # F10
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data, 4:address:editor-data
+    event-loop screen:address, console:address, 3:address:programming-environment-data
   ]
   # check that screen prints the results
   screen-should-contain [
-    .                                                            .
-    .     divide-with-remainder 11:literal, 3:literal            .
-    .     3                                                      .
-    .     2                                                      .
-    .                                                            .
+    .                                                                                                     run (F10)          .
+    .                                                            ┊divide-with-remainder 11:literal, 3:literal                .
+    .                                                            ┊3                                                          .
+    .                                                            ┊2                                                          .
+    .                                                            ┊                                                           .
   ]
   screen-should-contain-in-color 7:literal/white, [
-    .                                                            .
-    .     divide-with-remainder 11:literal, 3:literal            .
-    .                                                            .
-    .                                                            .
-    .                                                            .
+    .                                                                                                                        .
+    .                                                             divide-with-remainder 11:literal, 3:literal                .
+    .                                                                                                                        .
+    .                                                                                                                        .
+    .                                                                                                                        .
   ]
-  screen-should-contain-in-color 245:literal/grey, [
-    .                                                            .
-    .                                                            .
-    .     3                                                      .
-    .     2                                                      .
-    .                                                            .
+  screen-should-contain-in-color, 245:literal/grey, [
+    .                                                                                                                        .
+    .                                                            ┊                                                           .
+    .                                                            ┊3                                                          .
+    .                                                            ┊2                                                          .
+    .                                                            ┊                                                           .
   ]
+  $close-trace  # todo: try removing after we fix sluggishness
 ]
 
 recipe run-sandboxes [
   default-space:address:array:location <- new location:type, 30:literal
-  recipes:address:editor-data <- next-ingredient
-  current-sandbox:address:editor-data <- next-ingredient
+  env:address:programming-environment-data <- next-ingredient
+  recipes:address:editor-data <- get env:address:programming-environment-data/deref, recipes:offset
+  current-sandbox:address:editor-data <- get env:address:programming-environment-data/deref, current-sandbox:offset
   # load code from recipe editor, save any warnings
   in:address:array:character <- editor-contents recipes:address:editor-data
-  warnings:address:address:array:character <- get-address recipes:address:editor-data/deref, warnings:offset
-  warnings:address:address:array:character/deref <- reload in:address:array:character
+  recipe-warnings:address:address:array:character <- get-address env:address:programming-environment-data/deref, recipe-warnings:offset
+  recipe-warnings:address:address:array:character/deref <- reload in:address:array:character
   # run contents of right editor (sandbox), save any warnings or output
   in:address:array:character <- editor-contents current-sandbox:address:editor-data
-  response:address:address:array:character <- get-address current-sandbox:address:editor-data/deref, response:offset
-  warnings:address:address:array:character <- get-address current-sandbox:address:editor-data/deref, warnings:offset
+  sandbox:address:sandbox-data <- get-address env:address:programming-environment-data/deref, sandbox:offset
+  response:address:address:array:character <- get-address sandbox:address:sandbox-data/deref, response:offset
+  warnings:address:address:array:character <- get-address sandbox:address:sandbox-data/deref, warnings:offset
   response:address:address:array:character/deref, warnings:address:address:array:character/deref <- run-interactive in:address:array:character
 ]
 
 scenario run-instruction-and-print-warnings [
-  assume-screen 60:literal/width, 5:literal/height
+  assume-screen 120:literal/width, 10:literal/height
   # left editor is empty
   1:address:array:character <- new []
-  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 5:literal/right
   # right editor contains an illegal instruction
-  3:address:array:character <- new [get 1234:number, foo:offset]
-  4:address:editor-data <- new-editor 3:address:array:character, screen:address, 5:literal/left, 60:literal/right
+  2:address:array:character <- new [get 1234:number, foo:offset]
+  3:address:programming-environment-data <- new-programming-environment screen:address, 1:address:array:character, 2:address:array:character
   # run the code in the editors
   assume-console [
     press 65526  # F10
   ]
   run [
-    event-loop screen:address, console:address, 2:address:editor-data, 4:address:editor-data
+    event-loop screen:address, console:address, 3:address:programming-environment-data
   ]
   # check that screen prints error message in red
   screen-should-contain [
-    .                                                            .
-    .     get 1234:number, foo:offset                            .
-    .     unknown element foo in container number                .
-    .                                                            .
+    .                                                                                                     run (F10)          .
+    .                                                            ┊get 1234:number, foo:offset                                .
+    .                                                            ┊unknown element foo in container number                    .
+    .                                                            ┊                                                           .
   ]
   screen-should-contain-in-color 7:literal/white, [
-    .                                                            .
-    .     get 1234:number, foo:offset                            .
-    .                                                            .
-    .                                                            .
+    .                                                                                                                        .
+    .                                                             get 1234:number, foo:offset                                .
+    .                                                                                                                        .
+    .                                                                                                                        .
   ]
   screen-should-contain-in-color, 1:literal/red, [
-    .                                                            .
-    .                                                            .
-    .     unknown element foo in container number                .
-    .                                                            .
+    .                                                                                                                        .
+    .                                                                                                                        .
+    .                                                             unknown element foo in container number                    .
+    .                                                                                                                        .
+  ]
+  screen-should-contain-in-color, 245:literal/grey, [
+    .                                                                                                                        .
+    .                                                            ┊                                                           .
+    .                                                            ┊                                                           .
+    .                                                            ┊                                                           .
+  ]
+  $close-trace  # todo: try removing after we fix sluggishness
+]
+
+recipe editor-contents [
+  default-space:address:array:location <- new location:type, 30:literal
+  editor:address:editor-data <- next-ingredient
+  buf:address:buffer <- new-buffer 80:literal
+  curr:address:duplex-list <- get editor:address:editor-data/deref, data:offset
+  # skip § sentinel
+  assert curr:address:duplex-list, [editor without data is illegal; must have at least a sentinel]
+  curr:address:duplex-list <- next-duplex curr:address:duplex-list
+  {
+    break-unless curr:address:duplex-list
+    c:character <- get curr:address:duplex-list/deref, value:offset
+    buffer-append buf:address:buffer, c:character
+    curr:address:duplex-list <- next-duplex curr:address:duplex-list
+    loop
+  }
+  result:address:array:character <- buffer-to-array buf:address:buffer
+  reply result:address:array:character
+]
+
+scenario editor-provides-edited-contents [
+  assume-screen 10:literal/width, 5:literal/height
+  1:address:array:character <- new [abc]
+  2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/left, 10:literal/right
+  assume-console [
+    left-click 1, 2
+    type [def]
+  ]
+  run [
+    editor-event-loop screen:address, console:address, 2:address:editor-data
+    3:address:array:character <- editor-contents 2:address:editor-data
+    4:array:character <- copy 3:address:array:character/deref
+  ]
+  memory-should-contain [
+    4:string <- [abdefc]
   ]
 ]