about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-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]
   ]
 ]