From fbe0f91376a4f1ed98e95b35d1b9e7629a743c30 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 7 Feb 2017 00:12:50 -0800 Subject: 3745 Stop trying to create a new layer showing how we minimize prints. Stephen's suggestion is to create a data structure that encapsulates instructions to `insert-at-cursor` for either just printing a character to screen or rendering everything. Let's try that at some point. --- edit/003-shortcuts.mu | 3299 +++++++++++++++++++++++++++++++++++ edit/004-programming-environment.mu | 642 +++++++ edit/004-shortcuts.mu | 3299 ----------------------------------- edit/005-programming-environment.mu | 642 ------- edit/005-sandbox.mu | 1213 +++++++++++++ edit/006-sandbox-copy.mu | 279 +++ edit/006-sandbox.mu | 1213 ------------- edit/007-sandbox-copy.mu | 279 --- edit/007-sandbox-delete.mu | 343 ++++ edit/008-sandbox-delete.mu | 343 ---- edit/008-sandbox-edit.mu | 324 ++++ edit/009-sandbox-edit.mu | 324 ---- edit/009-sandbox-test.mu | 207 +++ edit/010-sandbox-test.mu | 207 --- edit/010-sandbox-trace.mu | 253 +++ edit/011-errors.mu | 742 ++++++++ edit/011-sandbox-trace.mu | 253 --- edit/012-editor-undo.mu | 2109 ++++++++++++++++++++++ edit/012-errors.mu | 742 -------- edit/013-editor-undo.mu | 2109 ---------------------- 20 files changed, 9411 insertions(+), 9411 deletions(-) create mode 100644 edit/003-shortcuts.mu create mode 100644 edit/004-programming-environment.mu delete mode 100644 edit/004-shortcuts.mu delete mode 100644 edit/005-programming-environment.mu create mode 100644 edit/005-sandbox.mu create mode 100644 edit/006-sandbox-copy.mu delete mode 100644 edit/006-sandbox.mu delete mode 100644 edit/007-sandbox-copy.mu create mode 100644 edit/007-sandbox-delete.mu delete mode 100644 edit/008-sandbox-delete.mu create mode 100644 edit/008-sandbox-edit.mu delete mode 100644 edit/009-sandbox-edit.mu create mode 100644 edit/009-sandbox-test.mu delete mode 100644 edit/010-sandbox-test.mu create mode 100644 edit/010-sandbox-trace.mu create mode 100644 edit/011-errors.mu delete mode 100644 edit/011-sandbox-trace.mu create mode 100644 edit/012-editor-undo.mu delete mode 100644 edit/012-errors.mu delete mode 100644 edit/013-editor-undo.mu (limited to 'edit') diff --git a/edit/003-shortcuts.mu b/edit/003-shortcuts.mu new file mode 100644 index 00000000..941d316d --- /dev/null +++ b/edit/003-shortcuts.mu @@ -0,0 +1,3299 @@ +## special shortcuts for manipulating the editor +# Some keys on the keyboard generate unicode characters, others generate +# terminfo key codes. We need to modify different places in the two cases. + +# tab - insert two spaces + +scenario editor-inserts-two-spaces-on-tab [ + local-scope + assume-screen 10/width, 5/height + # just one character in final line + s:text <- new [ab +cd] + e:&:editor <- new-editor s, 0/left, 5/right + assume-console [ + press tab + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + . ab . + .cd . + ] +] + +after [ + { + tab?:bool <- equal c, 9/tab + break-unless tab? + + insert-at-cursor editor, 32/space, screen + insert-at-cursor editor, 32/space, screen + + return 1/go-render + } +] + +# backspace - delete character before cursor + +scenario editor-handles-backspace-key [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 1, 1 + press backspace + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + screen-should-contain [ + . . + .bc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 4 <- 1 + 5 <- 0 + ] + check-trace-count-for-label 3, [print-character] # length of original line to overwrite +] + +after [ + { + delete-previous-character?:bool <- equal c, 8/backspace + break-unless delete-previous-character? + + go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen + + return + } +] + +# return values: +# go-render? - whether caller needs to update the screen +# backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc. +def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [ + local-scope + load-ingredients + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + data:&:duplex-list:char <- get *editor, data:offset + # if at start of text (before-cursor at § sentinel), return + prev:&:duplex-list:char <- prev before-cursor + return-unless prev, 0/no-more-render, 0/nothing-deleted + trace 10, [app], [delete-before-cursor] + original-row:num <- get *editor, cursor-row:offset + scroll?:bool <- move-cursor-coordinates-left editor + backspaced-cell:&:duplex-list:char <- copy before-cursor + data <- remove before-cursor, data # will also neatly trim next/prev pointers in backspaced-cell/before-cursor + before-cursor <- copy prev + *editor <- put *editor, before-cursor:offset, before-cursor + return-if scroll?, 1/go-render + screen-width:num <- screen-width screen + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + # did we just backspace over a newline? + same-row?:bool <- equal cursor-row, original-row + return-unless same-row?, 1/go-render + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + curr:&:duplex-list:char <- next before-cursor + screen <- move-cursor screen, cursor-row, cursor-column + curr-column:num <- copy cursor-column + { + # hit right margin? give up and let caller render + at-right?:bool <- greater-or-equal curr-column, right + return-if at-right?, 1/go-render + break-unless curr + # newline? done. + currc:char <- get *curr, value:offset + at-newline?:bool <- equal currc, 10/newline + break-if at-newline? + screen <- print screen, currc + curr-column <- add curr-column, 1 + curr <- next curr + loop + } + # we're guaranteed not to be at the right margin + space:char <- copy 32/space + screen <- print screen, space + go-render? <- copy 0/false +] + +def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [ + local-scope + load-ingredients + go-render?:bool <- copy 0/false + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + left:num <- get *editor, left:offset + # if not at left margin, move one character left + { + at-left-margin?:bool <- equal cursor-column, left + break-if at-left-margin? + trace 10, [app], [decrementing cursor column] + cursor-column <- subtract cursor-column, 1 + *editor <- put *editor, cursor-column:offset, cursor-column + return + } + # if at left margin, we must move to previous row: + top-of-screen?:bool <- equal cursor-row, 1 # exclude menu bar + { + break-if top-of-screen? + cursor-row <- subtract cursor-row, 1 + *editor <- put *editor, cursor-row:offset, cursor-row + } + { + break-unless top-of-screen? + + go-render? <- copy 1/true + } + { + # case 1: if previous character was newline, figure out how long the previous line is + previous-character:char <- get *before-cursor, value:offset + previous-character-is-newline?:bool <- equal previous-character, 10/newline + break-unless previous-character-is-newline? + # compute length of previous line + trace 10, [app], [switching to previous line] + d:&:duplex-list:char <- get *editor, data:offset + end-of-line:num <- previous-line-length before-cursor, d + right:num <- get *editor, right:offset + width:num <- subtract right, left + wrap?:bool <- greater-than end-of-line, width + { + break-unless wrap? + _, column-offset:num <- divide-with-remainder end-of-line, width + cursor-column <- add left, column-offset + *editor <- put *editor, cursor-column:offset, cursor-column + } + { + break-if wrap? + cursor-column <- add left, end-of-line + *editor <- put *editor, cursor-column:offset, cursor-column + } + return + } + # case 2: if previous-character was not newline, we're just at a wrapped line + trace 10, [app], [wrapping to previous line] + right:num <- get *editor, right:offset + cursor-column <- subtract right, 1 # leave room for wrap icon + *editor <- put *editor, cursor-column:offset, cursor-column +] + +# takes a pointer 'curr' into the doubly-linked list and its sentinel, counts +# the length of the previous line before the 'curr' pointer. +def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [ + local-scope + load-ingredients + result:num <- copy 0 + return-unless curr + at-start?:bool <- equal curr, start + return-if at-start? + { + curr <- prev curr + break-unless curr + at-start?:bool <- equal curr, start + break-if at-start? + c:char <- get *curr, value:offset + at-newline?:bool <- equal c, 10/newline + break-if at-newline? + result <- add result, 1 + loop + } +] + +scenario editor-clears-last-line-on-backspace [ + local-scope + assume-screen 10/width, 5/height + # just one character in final line + s:text <- new [ab +cd] + e:&:editor <- new-editor s, 0/left, 10/right + assume-console [ + left-click 2, 0 # cursor at only character in final line + press backspace + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + screen-should-contain [ + . . + .abcd . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 4 <- 1 + 5 <- 2 + ] +] + +scenario editor-joins-and-wraps-lines-on-backspace [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with two long-ish but non-wrapping lines + s:text <- new [abc def +ghi jkl] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # position the cursor at the start of the second and hit backspace + assume-console [ + left-click 2, 0 + press backspace + ] + run [ + editor-event-loop screen, console, e + ] + # resulting single line should wrap correctly + screen-should-contain [ + . . + .abc defgh↩. + .i jkl . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-wraps-long-lines-on-backspace [ + local-scope + assume-screen 10/width, 5/height + # initialize editor in part of the screen with a long line + e:&:editor <- new-editor [abc def ghij], 0/left, 8/right + editor-render screen, e + # confirm that it wraps + screen-should-contain [ + . . + .abc def↩ . + . ghij . + .┈┈┈┈┈┈┈┈ . + ] + $clear-trace + # position the cursor somewhere in the middle of the top screen line and hit backspace + assume-console [ + left-click 1, 4 + press backspace + ] + run [ + editor-event-loop screen, console, e + ] + # resulting single line should wrap correctly and not overflow its bounds + screen-should-contain [ + . . + .abcdef ↩ . + .ghij . + .┈┈┈┈┈┈┈┈ . + . . + ] +] + +# delete - delete character at cursor + +scenario editor-handles-delete-key [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + press delete + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .bc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 3, [print-character] # length of original line to overwrite + $clear-trace + assume-console [ + press delete + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .c . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 2, [print-character] # new length to overwrite +] + +after [ + { + delete-next-character?:bool <- equal k, 65522/delete + break-unless delete-next-character? + + go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen + + return + } +] + +def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [ + local-scope + load-ingredients + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + data:&:duplex-list:char <- get *editor, data:offset + deleted-cell:&:duplex-list:char <- next before-cursor + return-unless deleted-cell, 0/don't-render + currc:char <- get *deleted-cell, value:offset + data <- remove deleted-cell, data + deleted-newline?:bool <- equal currc, 10/newline + return-if deleted-newline?, 1/go-render + # wasn't a newline? render rest of line + curr:&:duplex-list:char <- next before-cursor # refresh after remove above + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + screen <- move-cursor screen, cursor-row, cursor-column + curr-column:num <- copy cursor-column + screen-width:num <- screen-width screen + { + # hit right margin? give up and let caller render + at-right?:bool <- greater-or-equal curr-column, screen-width + return-if at-right?, 1/go-render + break-unless curr + # newline? done. + currc:char <- get *curr, value:offset + at-newline?:bool <- equal currc, 10/newline + break-if at-newline? + screen <- print screen, currc + curr-column <- add curr-column, 1 + curr <- next curr + loop + } + # we're guaranteed not to be at the right margin + space:char <- copy 32/space + screen <- print screen, space + go-render? <- copy 0/false +] + +# right arrow + +scenario editor-moves-cursor-right-with-key [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + press right-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .a0bc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 3, [print-character] # 0 and following characters +] + +after [ + { + move-to-next-character?:bool <- equal k, 65514/right-arrow + break-unless move-to-next-character? + # if not at end of text + next-cursor:&:duplex-list:char <- next before-cursor + break-unless next-cursor + # scan to next character + + before-cursor <- copy next-cursor + *editor <- put *editor, before-cursor:offset, before-cursor + go-render?:bool <- move-cursor-coordinates-right editor, screen-height + screen <- move-cursor screen, cursor-row, cursor-column + undo-coalesce-tag:num <- copy 2/right-arrow + + return + } +] + +def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [ + local-scope + load-ingredients + before-cursor:&:duplex-list:char <- get *editor before-cursor:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + # if crossed a newline, move cursor to start of next row + { + old-cursor-character:char <- get *before-cursor, value:offset + was-at-newline?:bool <- equal old-cursor-character, 10/newline + break-unless was-at-newline? + cursor-row <- add cursor-row, 1 + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- copy left + *editor <- put *editor, cursor-column:offset, cursor-column + below-screen?:bool <- greater-or-equal cursor-row, screen-height # must be equal + return-unless below-screen?, 0/don't-render + + cursor-row <- subtract cursor-row, 1 # bring back into screen range + *editor <- put *editor, cursor-row:offset, cursor-row + return 1/go-render + } + # if the line wraps, move cursor to start of next row + { + # if we're at the column just before the wrap indicator + wrap-column:num <- subtract right, 1 + at-wrap?:bool <- equal cursor-column, wrap-column + break-unless at-wrap? + # and if next character isn't newline + next:&:duplex-list:char <- next before-cursor + break-unless next + next-character:char <- get *next, value:offset + newline?:bool <- equal next-character, 10/newline + break-if newline? + cursor-row <- add cursor-row, 1 + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- copy left + *editor <- put *editor, cursor-column:offset, cursor-column + below-screen?:bool <- greater-or-equal cursor-row, screen-height # must be equal + return-unless below-screen?, 0/no-more-render + + cursor-row <- subtract cursor-row, 1 # bring back into screen range + *editor <- put *editor, cursor-row:offset, cursor-row + return 1/go-render + } + # otherwise move cursor one character right + cursor-column <- add cursor-column, 1 + *editor <- put *editor, cursor-column:offset, cursor-column + go-render? <- copy 0/false +] + +scenario editor-moves-cursor-to-next-line-with-right-arrow [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # type right-arrow a few times to get to start of second line + assume-console [ + press right-arrow + press right-arrow + press right-arrow + press right-arrow # next line + ] + run [ + editor-event-loop screen, console, e + ] + check-trace-count-for-label 0, [print-character] + # type something and ensure it goes where it should + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .0d . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 2, [print-character] # new length of second line +] + +scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +d] + e:&:editor <- new-editor s, 1/left, 10/right + editor-render screen, e + assume-console [ + press right-arrow + press right-arrow + press right-arrow + press right-arrow # next line + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + . abc . + . 0d . + . ┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abcdef], 0/left, 5/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 1, 3 + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + screen-should-contain [ + . . + .abcd↩ . + .ef . + .┈┈┈┈┈ . + . . + ] + memory-should-contain [ + 3 <- 2 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [ + local-scope + assume-screen 10/width, 5/height + # line just barely wrapping + e:&:editor <- new-editor [abcde], 0/left, 5/right + editor-render screen, e + $clear-trace + # position cursor at last character before wrap and hit right-arrow + assume-console [ + left-click 1, 3 + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 2 + 4 <- 0 + ] + # now hit right arrow again + assume-console [ + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abcdef], 1/left, 6/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 1, 4 + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + screen-should-contain [ + . . + . abcd↩ . + . ef . + . ┈┈┈┈┈ . + . . + ] + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # move to end of line, press right-arrow, type a character + assume-console [ + left-click 1, 3 + press right-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + # new character should be in next line + screen-should-contain [ + . . + .abc . + .0d . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 2, [print-character] +] + +# todo: ctrl-right: next word-end + +# left arrow + +scenario editor-moves-cursor-left-with-key [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 1, 2 + press left-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .a0bc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 3, [print-character] +] + +after [ + { + move-to-previous-character?:bool <- equal k, 65515/left-arrow + break-unless move-to-previous-character? + trace 10, [app], [left arrow] + # if not at start of text (before-cursor at § sentinel) + prev:&:duplex-list:char <- prev before-cursor + return-unless prev, 0/don't-render + + go-render? <- move-cursor-coordinates-left editor + before-cursor <- copy prev + *editor <- put *editor, before-cursor:offset, before-cursor + undo-coalesce-tag:num <- copy 1/left-arrow + + return + } +] + +scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with two lines + s:text <- new [abc +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # position cursor at start of second line (so there's no previous newline) + assume-console [ + left-click 2, 0 + press left-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with three lines + s:text <- new [abc +def +g] + e:&:editor <- new-editor s:text, 0/left, 10/right + editor-render screen, e + $clear-trace + # position cursor further down (so there's a newline before the character at + # the cursor) + assume-console [ + left-click 3, 0 + press left-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def0 . + .g . + .┈┈┈┈┈┈┈┈┈┈. + ] + check-trace-count-for-label 1, [print-character] # just the '0' +] + +scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +def +g] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # position cursor at start of text, press left-arrow, then type a character + assume-console [ + left-click 1, 0 + press left-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + # left-arrow should have had no effect + screen-should-contain [ + . . + .0abc . + .def . + .g . + .┈┈┈┈┈┈┈┈┈┈. + ] + check-trace-count-for-label 4, [print-character] # length of first line +] + +scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with text containing an empty line + s:text <- new [abc + +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e:&:editor + $clear-trace + # position cursor right after empty line + assume-console [ + left-click 3, 0 + press left-arrow + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .0 . + .d . + .┈┈┈┈┈┈┈┈┈┈. + ] + check-trace-count-for-label 1, [print-character] # just the '0' +] + +scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with a wrapping line + e:&:editor <- new-editor [abcdef], 0/left, 5/right + editor-render screen, e + $clear-trace + screen-should-contain [ + . . + .abcd↩ . + .ef . + .┈┈┈┈┈ . + . . + ] + # position cursor right after empty line + assume-console [ + left-click 2, 0 + press left-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 # previous row + 4 <- 3 # right margin except wrap icon + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with a wrapping line followed by a second line + s:text <- new [abcdef +g] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + $clear-trace + screen-should-contain [ + . . + .abcd↩ . + .ef . + .g . + .┈┈┈┈┈ . + ] + # position cursor right after empty line + assume-console [ + left-click 3, 0 + press left-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 2 # previous row + 4 <- 2 # end of wrapped line + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [ + local-scope + assume-screen 10/width, 5/height + # initialize editor with a line on the verge of wrapping, followed by a second line + s:text <- new [abcd +e] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + $clear-trace + screen-should-contain [ + . . + .abcd . + .e . + .┈┈┈┈┈ . + . . + ] + # position cursor right after empty line + assume-console [ + left-click 2, 0 + press left-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 # previous row + 4 <- 4 # end of wrapped line + ] + check-trace-count-for-label 0, [print-character] +] + +# todo: ctrl-left: previous word-start + +# up arrow + +scenario editor-moves-to-previous-line-with-up-arrow [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +def] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 2, 1 + press up-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .a0bc . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + { + move-to-previous-line?:bool <- equal k, 65517/up-arrow + break-unless move-to-previous-line? + + go-render? <- move-to-previous-line editor + undo-coalesce-tag:num <- copy 3/up-arrow + + return + } +] + +def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [ + local-scope + load-ingredients + go-render?:bool <- copy 0/false + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + already-at-top?:bool <- lesser-or-equal cursor-row, 1/top + { + # if cursor not at top, move it + break-if already-at-top? + # if not at newline, move to start of line (previous newline) + # then scan back another line + # if either step fails, give up without modifying cursor or coordinates + curr:&:duplex-list:char <- copy before-cursor + { + old:&:duplex-list:char <- copy curr + c2:char <- get *curr, value:offset + at-newline?:bool <- equal c2, 10/newline + break-if at-newline? + curr:&:duplex-list:char <- before-previous-line curr, editor + no-motion?:bool <- equal curr, old + return-if no-motion? + } + { + old <- copy curr + curr <- before-previous-line curr, editor + no-motion?:bool <- equal curr, old + return-if no-motion? + } + before-cursor <- copy curr + *editor <- put *editor, before-cursor:offset, before-cursor + cursor-row <- subtract cursor-row, 1 + *editor <- put *editor, cursor-row:offset, cursor-row + # scan ahead to right column or until end of line + target-column:num <- copy cursor-column + cursor-column <- copy left + *editor <- put *editor, cursor-column:offset, cursor-column + { + done?:bool <- greater-or-equal cursor-column, target-column + break-if done? + curr:&:duplex-list:char <- next before-cursor + break-unless curr + currc:char <- get *curr, value:offset + at-newline?:bool <- equal currc, 10/newline + break-if at-newline? + # + before-cursor <- copy curr + *editor <- put *editor, before-cursor:offset, before-cursor + cursor-column <- add cursor-column, 1 + *editor <- put *editor, cursor-column:offset, cursor-column + loop + } + return + } + { + # if cursor already at top, scroll up + break-unless already-at-top? + + return 1/go-render + } +] + +scenario editor-adjusts-column-at-previous-line [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [ab +def] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 2, 3 + press up-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .ab0 . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-adjusts-column-at-empty-line [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [ +def] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 2, 3 + press up-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .0 . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-moves-to-previous-line-from-left-margin [ + local-scope + assume-screen 10/width, 5/height + # start out with three lines + s:text <- new [abc +def +ghi] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # click on the third line and hit up-arrow, so you end up just after a newline + assume-console [ + left-click 3, 0 + press up-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 2 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .0def . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +# down arrow + +scenario editor-moves-to-next-line-with-down-arrow [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +def] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # cursor starts out at (1, 0) + assume-console [ + press down-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # ..and ends at (2, 0) + memory-should-contain [ + 3 <- 2 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .0def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + { + move-to-next-line?:bool <- equal k, 65516/down-arrow + break-unless move-to-next-line? + + go-render? <- move-to-next-line editor, screen-height + undo-coalesce-tag:num <- copy 4/down-arrow + + return + } +] + +def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [ + local-scope + load-ingredients + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + last-line:num <- subtract screen-height, 1 + already-at-bottom?:bool <- greater-or-equal cursor-row, last-line + { + # if cursor not at bottom, move it + break-if already-at-bottom? + # scan to start of next line, then to right column or until end of line + max:num <- subtract right, left + next-line:&:duplex-list:char <- before-start-of-next-line before-cursor, max + { + # already at end of buffer? try to scroll up (so we can see more + # warnings or sandboxes below) + no-motion?:bool <- equal next-line, before-cursor + break-unless no-motion? + scroll?:bool <- greater-than cursor-row, 1 + break-if scroll?, +try-to-scroll + return 0/don't-render + } + cursor-row <- add cursor-row, 1 + *editor <- put *editor, cursor-row:offset, cursor-row + before-cursor <- copy next-line + *editor <- put *editor, before-cursor:offset, before-cursor + target-column:num <- copy cursor-column + cursor-column <- copy left + *editor <- put *editor, cursor-column:offset, cursor-column + { + done?:bool <- greater-or-equal cursor-column, target-column + break-if done? + curr:&:duplex-list:char <- next before-cursor + break-unless curr + currc:char <- get *curr, value:offset + at-newline?:bool <- equal currc, 10/newline + break-if at-newline? + # + before-cursor <- copy curr + *editor <- put *editor, before-cursor:offset, before-cursor + cursor-column <- add cursor-column, 1 + *editor <- put *editor, cursor-column:offset, cursor-column + loop + } + return 0/don't-render + } + +try-to-scroll + + go-render? <- copy 1/true +] + +scenario editor-adjusts-column-at-next-line [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +de] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + assume-console [ + left-click 1, 3 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 3 <- 2 + 4 <- 2 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .de0 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# ctrl-a/home - move cursor to start of line + +scenario editor-moves-to-start-of-line-with-ctrl-a [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on second line, press ctrl-a + assume-console [ + left-click 2, 3 + press ctrl-a + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to start of line + memory-should-contain [ + 4 <- 2 + 5 <- 0 + ] + check-trace-count-for-label 0, [print-character] +] + +after [ + { + move-to-start-of-line?:bool <- equal c, 1/ctrl-a + break-unless move-to-start-of-line? + + move-to-start-of-line editor + undo-coalesce-tag:num <- copy 0/never + + return 0/don't-render + } +] + +after [ + { + move-to-start-of-line?:bool <- equal k, 65521/home + break-unless move-to-start-of-line? + + move-to-start-of-line editor + undo-coalesce-tag:num <- copy 0/never + + return 0/don't-render + } +] + +def move-to-start-of-line editor:&:editor -> editor:&:editor [ + local-scope + load-ingredients + # update cursor column + left:num <- get *editor, left:offset + cursor-column:num <- copy left + *editor <- put *editor, cursor-column:offset, cursor-column + # update before-cursor + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + init:&:duplex-list:char <- get *editor, data:offset + # while not at start of line, move + { + at-start-of-text?:bool <- equal before-cursor, init + break-if at-start-of-text? + prev:char <- get *before-cursor, value:offset + at-start-of-line?:bool <- equal prev, 10/newline + break-if at-start-of-line? + before-cursor <- prev before-cursor + *editor <- put *editor, before-cursor:offset, before-cursor + assert before-cursor, [move-to-start-of-line tried to move before start of text] + loop + } +] + +scenario editor-moves-to-start-of-line-with-ctrl-a-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on first line (no newline before), press ctrl-a + assume-console [ + left-click 1, 3 + press ctrl-a + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to start of line + memory-should-contain [ + 4 <- 1 + 5 <- 0 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-to-start-of-line-with-home [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + $clear-trace + # start on second line, press 'home' + assume-console [ + left-click 2, 3 + press home + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to start of line + memory-should-contain [ + 3 <- 2 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-to-start-of-line-with-home-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on first line (no newline before), press 'home' + assume-console [ + left-click 1, 3 + press home + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to start of line + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + check-trace-count-for-label 0, [print-character] +] + +# ctrl-e/end - move cursor to end of line + +scenario editor-moves-to-end-of-line-with-ctrl-e [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on first line, press ctrl-e + assume-console [ + left-click 1, 1 + press ctrl-e + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to end of line + memory-should-contain [ + 4 <- 1 + 5 <- 3 + ] + check-trace-count-for-label 0, [print-character] + # editor inserts future characters at cursor + assume-console [ + type [z] + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + memory-should-contain [ + 4 <- 1 + 5 <- 4 + ] + screen-should-contain [ + . . + .123z . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + check-trace-count-for-label 1, [print-character] +] + +after [ + { + move-to-end-of-line?:bool <- equal c, 5/ctrl-e + break-unless move-to-end-of-line? + + move-to-end-of-line editor + undo-coalesce-tag:num <- copy 0/never + + return 0/don't-render + } +] + +after [ + { + move-to-end-of-line?:bool <- equal k, 65520/end + break-unless move-to-end-of-line? + + move-to-end-of-line editor + undo-coalesce-tag:num <- copy 0/never + + return 0/don't-render + } +] + +def move-to-end-of-line editor:&:editor -> editor:&:editor [ + local-scope + load-ingredients + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + cursor-column:num <- get *editor, cursor-column:offset + # while not at start of line, move + { + next:&:duplex-list:char <- next before-cursor + break-unless next # end of text + nextc:char <- get *next, value:offset + at-end-of-line?:bool <- equal nextc, 10/newline + break-if at-end-of-line? + before-cursor <- copy next + *editor <- put *editor, before-cursor:offset, before-cursor + cursor-column <- add cursor-column, 1 + *editor <- put *editor, cursor-column:offset, cursor-column + loop + } +] + +scenario editor-moves-to-end-of-line-with-ctrl-e-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on second line (no newline after), press ctrl-e + assume-console [ + left-click 2, 1 + press ctrl-e + ] + run [ + editor-event-loop screen, console, e + 4:num/raw <- get *e, cursor-row:offset + 5:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to end of line + memory-should-contain [ + 4 <- 2 + 5 <- 3 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-to-end-of-line-with-end [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on first line, press 'end' + assume-console [ + left-click 1, 1 + press end + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to end of line + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + check-trace-count-for-label 0, [print-character] +] + +scenario editor-moves-to-end-of-line-with-end-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # start on second line (no newline after), press 'end' + assume-console [ + left-click 2, 1 + press end + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # cursor moves to end of line + memory-should-contain [ + 3 <- 2 + 4 <- 3 + ] + check-trace-count-for-label 0, [print-character] +] + +# ctrl-u - delete text from start of line until (but not at) cursor + +scenario editor-deletes-to-start-of-line-with-ctrl-u [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start on second line, press ctrl-u + assume-console [ + left-click 2, 2 + press ctrl-u + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to start of line + screen-should-contain [ + . . + .123 . + .6 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + { + delete-to-start-of-line?:bool <- equal c, 21/ctrl-u + break-unless delete-to-start-of-line? + + deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor + + return 1/go-render + } +] + +def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [ + local-scope + load-ingredients + # compute range to delete + init:&:duplex-list:char <- get *editor, data:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + start:&:duplex-list:char <- copy before-cursor + end:&:duplex-list:char <- next before-cursor + { + at-start-of-text?:bool <- equal start, init + break-if at-start-of-text? + curr:char <- get *start, value:offset + at-start-of-line?:bool <- equal curr, 10/newline + break-if at-start-of-line? + start <- prev start + assert start, [delete-to-start-of-line tried to move before start of text] + loop + } + # snip it out + result:&:duplex-list:char <- next start + remove-between start, end + # adjust cursor + before-cursor <- copy start + *editor <- put *editor, before-cursor:offset, before-cursor + left:num <- get *editor, left:offset + *editor <- put *editor, cursor-column:offset, left +] + +scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start on first line (no newline before), press ctrl-u + assume-console [ + left-click 1, 2 + press ctrl-u + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to start of line + screen-should-contain [ + . . + .3 . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start past end of line, press ctrl-u + assume-console [ + left-click 1, 3 + press ctrl-u + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to start of line + screen-should-contain [ + . . + . . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-start-of-final-line-with-ctrl-u [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start past end of final line, press ctrl-u + assume-console [ + left-click 2, 3 + press ctrl-u + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to start of line + screen-should-contain [ + . . + .123 . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# ctrl-k - delete text from cursor to end of line (but not the newline) + +scenario editor-deletes-to-end-of-line-with-ctrl-k [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start on first line, press ctrl-k + assume-console [ + left-click 1, 1 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to end of line + screen-should-contain [ + . . + .1 . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + { + delete-to-end-of-line?:bool <- equal c, 11/ctrl-k + break-unless delete-to-end-of-line? + + deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor + + return 1/go-render + } +] + +def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [ + local-scope + load-ingredients + # compute range to delete + start:&:duplex-list:char <- get *editor, before-cursor:offset + end:&:duplex-list:char <- next start + { + at-end-of-text?:bool <- equal end, 0/null + break-if at-end-of-text? + curr:char <- get *end, value:offset + at-end-of-line?:bool <- equal curr, 10/newline + break-if at-end-of-line? + end <- next end + loop + } + # snip it out + result <- next start + remove-between start, end +] + +scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start on second line (no newline after), press ctrl-k + assume-console [ + left-click 2, 1 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes to end of line + screen-should-contain [ + . . + .123 . + .4 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start at end of line + assume-console [ + left-click 1, 2 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes just last character + screen-should-contain [ + . . + .12 . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start past end of line + assume-console [ + left-click 1, 3 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes nothing + screen-should-contain [ + . . + .123 . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start at end of text + assume-console [ + left-click 2, 2 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes just the final character + screen-should-contain [ + . . + .123 . + .45 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [123 +456] + e:&:editor <- new-editor s, 0/left, 10/right + # start past end of text + assume-console [ + left-click 2, 3 + press ctrl-k + ] + run [ + editor-event-loop screen, console, e + ] + # cursor deletes nothing + screen-should-contain [ + . . + .123 . + .456 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# cursor-down can scroll if necessary + +scenario editor-can-scroll-down-using-arrow-keys [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with >3 lines + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # position cursor at last line, then try to move further down + assume-console [ + left-click 3, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen slides by one line + screen-should-contain [ + . . + .b . + .c . + .d . + ] +] + +after [ + trace 10, [app], [scroll down] + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + max:num <- subtract right, left + old-top:&:duplex-list:char <- copy top-of-screen + top-of-screen <- before-start-of-next-line top-of-screen, max + *editor <- put *editor, top-of-screen:offset, top-of-screen + no-movement?:bool <- equal old-top, top-of-screen + return-if no-movement?, 0/don't-render +] + +# takes a pointer into the doubly-linked list, scans ahead at most 'max' +# positions until the next newline +# beware: never return null pointer. +def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [ + local-scope + load-ingredients + count:num <- copy 0 + curr:&:duplex-list:char <- copy original + # skip the initial newline if it exists + { + c:char <- get *curr, value:offset + at-newline?:bool <- equal c, 10/newline + break-unless at-newline? + curr <- next curr + count <- add count, 1 + } + { + return-unless curr, original + done?:bool <- greater-or-equal count, max + break-if done? + c:char <- get *curr, value:offset + at-newline?:bool <- equal c, 10/newline + break-if at-newline? + curr <- next curr + count <- add count, 1 + loop + } + return-unless curr, original + return curr +] + +scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with a long, wrapped line and more than a screen of + # other lines + s:text <- new [abcdef +g +h +i] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + screen-should-contain [ + . . + .abcd↩ . + .ef . + .g . + ] + # position cursor at last line, then try to move further down + assume-console [ + left-click 3, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .ef . + .g . + .h . + ] +] + +scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # editor starts with a long line wrapping twice + s:text <- new [abcdefghij +k +l +m] + e:&:editor <- new-editor s, 0/left, 5/right + # position cursor at last line, then try to move further down + assume-console [ + left-click 3, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line containing a wrap icon + screen-should-contain [ + . . + .efgh↩ . + .ij . + .k . + ] + # scroll down again + assume-console [ + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .ij . + .k . + .l . + ] +] + +scenario editor-scrolls-down-when-line-wraps [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 5/width, 4/height + # editor contains a long line in the third line + s:text <- new [a +b +cdef] + e:&:editor <- new-editor s, 0/left, 5/right + # position cursor at end, type a character + assume-console [ + left-click 3, 4 + type [g] + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen scrolls + screen-should-contain [ + . . + .b . + .cdef↩. + .g . + ] + memory-should-contain [ + 3 <- 3 + 4 <- 1 + ] +] + +scenario editor-scrolls-down-on-newline [ + local-scope + assume-screen 5/width, 4/height + # position cursor after last line and type newline + s:text <- new [a +b +c] + e:&:editor <- new-editor s, 0/left, 5/right + assume-console [ + left-click 3, 4 + type [ +] + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen scrolls + screen-should-contain [ + . . + .b . + .c . + . . + ] + memory-should-contain [ + 3 <- 3 + 4 <- 0 + ] +] + +scenario editor-scrolls-down-on-right-arrow [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 5/width, 4/height + # editor contains a wrapped line + s:text <- new [a +b +cdefgh] + e:&:editor <- new-editor s, 0/left, 5/right + # position cursor at end of screen and try to move right + assume-console [ + left-click 3, 3 + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen scrolls + screen-should-contain [ + . . + .b . + .cdef↩. + .gh . + ] + memory-should-contain [ + 3 <- 3 + 4 <- 0 + ] +] + +scenario editor-scrolls-down-on-right-arrow-2 [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 5/width, 4/height + # editor contains more lines than can fit on screen + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 5/right + # position cursor at end of screen and try to move right + assume-console [ + left-click 3, 3 + press right-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen scrolls + screen-should-contain [ + . . + .b . + .c . + .d . + ] + memory-should-contain [ + 3 <- 3 + 4 <- 0 + ] +] + +scenario editor-scrolls-at-end-on-down-arrow [ + local-scope + assume-screen 10/width, 5/height + s:text <- new [abc +de] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + $clear-trace + # try to move down past end of text + assume-console [ + left-click 2, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen should scroll, moving cursor to end of text + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + assume-console [ + type [0] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .de0 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # try to move down again + $clear-trace + assume-console [ + left-click 2, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen stops scrolling because cursor is already at top + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + check-trace-count-for-label 0, [print-character] + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .de01 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-combines-page-and-line-scroll [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with a few pages of lines + s:text <- new [a +b +c +d +e +f +g] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + # scroll down one page and one line + assume-console [ + press page-down + left-click 3, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen scrolls down 3 lines + screen-should-contain [ + . . + .d . + .e . + .f . + ] +] + +# cursor-up can scroll if necessary + +scenario editor-can-scroll-up-using-arrow-keys [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with >3 lines + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # position cursor at top of second page, then try to move up + assume-console [ + press page-down + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen slides by one line + screen-should-contain [ + . . + .b . + .c . + .d . + ] +] + +after [ + trace 10, [app], [scroll up] + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + old-top:&:duplex-list:char <- copy top-of-screen + top-of-screen <- before-previous-line top-of-screen, editor + *editor <- put *editor, top-of-screen:offset, top-of-screen + no-movement?:bool <- equal old-top, top-of-screen + return-if no-movement?, 0/don't-render +] + +# takes a pointer into the doubly-linked list, scans back to before start of +# previous *wrapped* line +# beware: never return null pointer +def before-previous-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [ + local-scope + load-ingredients + curr:&:duplex-list:char <- copy in + c:char <- get *curr, value:offset + # compute max, number of characters to skip + # 1 + len%(width-1) + # except rotate second term to vary from 1 to width-1 rather than 0 to width-2 + left:num <- get *editor, left:offset + right:num <- get *editor, right:offset + max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon + sentinel:&:duplex-list:char <- get *editor, data:offset + len:num <- previous-line-length curr, sentinel + { + break-if len + # empty line; just skip this newline + prev:&:duplex-list:char <- prev curr + return-unless prev, curr + return prev + } + _, max:num <- divide-with-remainder len, max-line-length + # remainder 0 => scan one width-worth + { + break-if max + max <- copy max-line-length + } + max <- add max, 1 + count:num <- copy 0 + # skip 'max' characters + { + done?:bool <- greater-or-equal count, max + break-if done? + prev:&:duplex-list:char <- prev curr + break-unless prev + curr <- copy prev + count <- add count, 1 + loop + } + return curr +] + +scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with a long, wrapped line and more than a screen of + # other lines + s:text <- new [abcdef +g +h +i] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + screen-should-contain [ + . . + .abcd↩ . + .ef . + .g . + ] + # position cursor at top of second page, just below wrapped line + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .g . + .h . + .i . + ] + # now move up one line + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .ef . + .g . + .h . + ] +] + +scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [ + local-scope + # screen has 1 line for menu + 4 lines + assume-screen 10/width, 5/height + # editor starts with a long line wrapping twice, occupying 3 of the 4 lines + s:text <- new [abcdefghij +k +l +m] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + # position cursor at top of second page + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .k . + .l . + .m . + .┈┈┈┈┈ . + ] + # move up one line + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .ij . + .k . + .l . + .m . + ] + # move up a second line + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .efgh↩ . + .ij . + .k . + .l . + ] + # move up a third line + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .abcd↩ . + .efgh↩ . + .ij . + .k . + ] +] + +# same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length +# slightly off, just to prevent over-training +scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with a long, wrapped line and more than a screen of + # other lines + s:text <- new [abcdef +g +h +i] + e:&:editor <- new-editor s, 0/left, 6/right + editor-render screen, e + screen-should-contain [ + . . + .abcde↩ . + .f . + .g . + ] + # position cursor at top of second page, just below wrapped line + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .g . + .h . + .i . + ] + # now move up one line + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows partial wrapped line + screen-should-contain [ + . . + .f . + .g . + .h . + ] +] + +# check empty lines +scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [ + local-scope + assume-screen 10/width, 4/height + # initialize editor with some lines around an empty line + s:text <- new [a +b + +c +d +e] + e:&:editor <- new-editor s, 0/left, 6/right + editor-render screen, e + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + . . + .c . + .d . + ] + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .d . + .e . + .┈┈┈┈┈┈ . + ] + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + . . + .c . + .d . + ] +] + +scenario editor-scrolls-up-on-left-arrow [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 5/width, 4/height + # editor contains >3 lines + s:text <- new [a +b +c +d +e] + e:&:editor <- new-editor s, 0/left, 5/right + editor-render screen, e + # position cursor at top of second page + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .c . + .d . + .e . + ] + # now try to move left + assume-console [ + press left-arrow + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen scrolls + screen-should-contain [ + . . + .b . + .c . + .d . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] +] + +scenario editor-can-scroll-up-to-start-of-file [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with >3 lines + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # position cursor at top of second page, then try to move up to start of + # text + assume-console [ + press page-down + press up-arrow + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen slides by one line + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # try to move up again + assume-console [ + press up-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen remains unchanged + screen-should-contain [ + . . + .a . + .b . + .c . + ] +] + +# ctrl-f/page-down - render next page if it exists + +scenario editor-can-scroll [ + local-scope + assume-screen 10/width, 4/height + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows next page + screen-should-contain [ + . . + .c . + .d . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +after [ + { + page-down?:bool <- equal c, 6/ctrl-f + break-unless page-down? + old-top:&:duplex-list:char <- get *editor, top-of-screen:offset + + page-down editor + undo-coalesce-tag:num <- copy 0/never + + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + movement?:bool <- not-equal top-of-screen, old-top + return movement?/go-render + } +] + +after [ + { + page-down?:bool <- equal k, 65518/page-down + break-unless page-down? + old-top:&:duplex-list:char <- get *editor, top-of-screen:offset + + page-down editor + undo-coalesce-tag:num <- copy 0/never + + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + movement?:bool <- not-equal top-of-screen, old-top + return movement?/go-render + } +] + +# page-down skips entire wrapped lines, so it can't scroll past lines +# taking up the entire screen +def page-down editor:&:editor -> editor:&:editor [ + local-scope + load-ingredients + # if editor contents don't overflow screen, do nothing + bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset + return-unless bottom-of-screen + # if not, position cursor at final character + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + before-cursor:&:duplex-list:char <- prev bottom-of-screen + *editor <- put *editor, before-cursor:offset, before-cursor + # keep one line in common with previous page + { + last:char <- get *before-cursor, value:offset + newline?:bool <- equal last, 10/newline + break-unless newline?:bool + before-cursor <- prev before-cursor + *editor <- put *editor, before-cursor:offset, before-cursor + } + # move cursor and top-of-screen to start of that line + move-to-start-of-line editor + before-cursor <- get *editor, before-cursor:offset + *editor <- put *editor, top-of-screen:offset, before-cursor +] + +scenario editor-does-not-scroll-past-end [ + local-scope + assume-screen 10/width, 4/height + s:text <- new [a +b] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .┈┈┈┈┈┈┈┈┈┈. + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen remains unmodified + screen-should-contain [ + . . + .a . + .b . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-starts-next-page-at-start-of-wrapped-line [ + local-scope + # screen has 1 line for menu + 3 lines for text + assume-screen 10/width, 4/height + # editor contains a long last line + s:text <- new [a +b +cdefgh] + # editor screen triggers wrap of last line + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + # some part of last line is not displayed + screen-should-contain [ + . . + .a . + .b . + .cde↩ . + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows entire wrapped line + screen-should-contain [ + . . + .cde↩ . + .fgh . + .┈┈┈┈ . + ] +] + +scenario editor-starts-next-page-at-start-of-wrapped-line-2 [ + local-scope + # screen has 1 line for menu + 3 lines for text + assume-screen 10/width, 4/height + # editor contains a very long line that occupies last two lines of screen + # and still has something left over + s:text <- new [a +bcdefgh] + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + # some part of last line is not displayed + screen-should-contain [ + . . + .a . + .bcd↩ . + .efg↩ . + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows entire wrapped line + screen-should-contain [ + . . + .bcd↩ . + .efg↩ . + .h . + ] +] + +# ctrl-b/page-up - render previous page if it exists + +scenario editor-can-scroll-up [ + local-scope + assume-screen 10/width, 4/height + s:text <- new [a +b +c +d] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows next page + screen-should-contain [ + . . + .c . + .d . + .┈┈┈┈┈┈┈┈┈┈. + ] + # scroll back up + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows original page again + screen-should-contain [ + . . + .a . + .b . + .c . + ] +] + +after [ + { + page-up?:bool <- equal c, 2/ctrl-b + break-unless page-up? + old-top:&:duplex-list:char <- get *editor, top-of-screen:offset + + editor <- page-up editor, screen-height + undo-coalesce-tag:num <- copy 0/never + + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + movement?:bool <- not-equal top-of-screen, old-top + return movement?/go-render + } +] + +after [ + { + page-up?:bool <- equal k, 65519/page-up + break-unless page-up? + old-top:&:duplex-list:char <- get *editor, top-of-screen:offset + + editor <- page-up editor, screen-height + undo-coalesce-tag:num <- copy 0/never + + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + movement?:bool <- not-equal top-of-screen, old-top + # don't bother re-rendering if nothing changed. todo: test this + return movement?/go-render + } +] + +def page-up editor:&:editor, screen-height:num -> editor:&:editor [ + local-scope + load-ingredients + max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line + count:num <- copy 0 + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + { + done?:bool <- greater-or-equal count, max + break-if done? + prev:&:duplex-list:char <- before-previous-line top-of-screen, editor + break-unless prev + top-of-screen <- copy prev + *editor <- put *editor, top-of-screen:offset, top-of-screen + count <- add count, 1 + loop + } +] + +scenario editor-can-scroll-up-multiple-pages [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 10/width, 4/height + # initialize editor with 8 lines + s:text <- new [a +b +c +d +e +f +g +h] + e:&:editor <- new-editor s, 0/left, 10/right + editor-render screen, e + screen-should-contain [ + . . + .a . + .b . + .c . + ] + # scroll down two pages + assume-console [ + press page-down + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows third page + screen-should-contain [ + . . + .e . + .f . + .g . + ] + # scroll up + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows second page + screen-should-contain [ + . . + .c . + .d . + .e . + ] + # scroll up again + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows original page again + screen-should-contain [ + . . + .a . + .b . + .c . + ] +] + +scenario editor-can-scroll-up-wrapped-lines [ + local-scope + # screen has 1 line for menu + 5 lines for text + assume-screen 10/width, 6/height + # editor contains a long line in the first page + s:text <- new [a +b +cdefgh +i +j +k +l +m +n +o] + # editor screen triggers wrap of last line + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + # some part of last line is not displayed + screen-should-contain [ + . . + .a . + .b . + .cde↩ . + .fgh . + .i . + ] + # scroll down a page and a line + assume-console [ + press page-down + left-click 5, 0 + press down-arrow + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows entire wrapped line + screen-should-contain [ + . . + .j . + .k . + .l . + .m . + .n . + ] + # now scroll up one page + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + # screen resets + screen-should-contain [ + . . + .b . + .cde↩ . + .fgh . + .i . + .j . + ] +] + +scenario editor-can-scroll-up-wrapped-lines-2 [ + local-scope + # screen has 1 line for menu + 3 lines for text + assume-screen 10/width, 4/height + # editor contains a very long line that occupies last two lines of screen + # and still has something left over + s:text <- new [a +bcdefgh] + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + # some part of last line is not displayed + screen-should-contain [ + . . + .a . + .bcd↩ . + .efg↩ . + ] + # scroll down + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + # screen shows entire wrapped line + screen-should-contain [ + . . + .bcd↩ . + .efg↩ . + .h . + ] + # scroll back up + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + # screen resets + screen-should-contain [ + . . + .a . + .bcd↩ . + .efg↩ . + ] +] + +scenario editor-can-scroll-up-past-nonempty-lines [ + local-scope + assume-screen 10/width, 4/height + # text with empty line in second screen + s:text <- new [axx +bxx +cxx +dxx +exx +fxx +gxx +hxx +] + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + screen-should-contain [ + . . + .axx . + .bxx . + .cxx . + ] + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .cxx . + .dxx . + .exx . + ] + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .exx . + .fxx . + .gxx . + ] + # scroll back up past empty line + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .cxx . + .dxx . + .exx . + ] +] + +scenario editor-can-scroll-up-past-empty-lines [ + local-scope + assume-screen 10/width, 4/height + # text with empty line in second screen + s:text <- new [axy +bxy +cxy + +dxy +exy +fxy +gxy +] + e:&:editor <- new-editor s, 0/left, 4/right + editor-render screen, e + screen-should-contain [ + . . + .axy . + .bxy . + .cxy . + ] + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .cxy . + . . + .dxy . + ] + assume-console [ + press page-down + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .dxy . + .exy . + .fxy . + ] + # scroll back up past empty line + assume-console [ + press page-up + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .cxy . + . . + .dxy . + ] +] diff --git a/edit/004-programming-environment.mu b/edit/004-programming-environment.mu new file mode 100644 index 00000000..7efa3d47 --- /dev/null +++ b/edit/004-programming-environment.mu @@ -0,0 +1,642 @@ +## putting the environment together out of editors +# +# Consists of one editor on the left for recipes and one on the right for the +# sandbox. + +def! main [ + local-scope + open-console + env:&:environment <- new-programming-environment 0/filesystem, 0/screen + render-all 0/screen, env, render + event-loop 0/screen, 0/console, env, 0/filesystem + # never gets here +] + +container environment [ + recipes:&:editor + current-sandbox:&:editor + sandbox-in-focus?:bool # false => cursor in recipes; true => cursor in current-sandbox +] + +def new-programming-environment resources:&:resources, screen:&:screen, test-sandbox-editor-contents:text -> result:&:environment [ + local-scope + load-ingredients + width:num <- screen-width screen + result <- new environment:type + # recipe editor on the left + initial-recipe-contents:text <- slurp resources, [lesson/recipes.mu] # ignore errors + divider:num, _ <- divide-with-remainder width, 2 + recipes:&:editor <- new-editor initial-recipe-contents, 0/left, divider/right + # sandbox editor on the right + sandbox-left:num <- add divider, 1 + current-sandbox:&:editor <- new-editor test-sandbox-editor-contents, sandbox-left, width/right + *result <- put *result, recipes:offset, recipes + *result <- put *result, current-sandbox:offset, current-sandbox + *result <- put *result, sandbox-in-focus?:offset, 0/false + +] + +def event-loop screen:&:screen, console:&:console, env:&:environment, resources:&:resources -> screen:&:screen, console:&:console, env:&:environment, resources:&:resources [ + local-scope + load-ingredients + recipes:&:editor <- get *env, recipes:offset + current-sandbox:&:editor <- get *env, current-sandbox:offset + sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset + # if we fall behind we'll stop updating the screen, but then we have to + # render the entire screen when we catch up. + # todo: test this + render-all-on-no-more-events?:bool <- copy 0/false + { + # looping over each (keyboard or touch) event as it occurs + +next-event + e:event, found?:bool, quit?:bool, console <- read-event console + loop-unless found? + break-if quit? # only in tests + trace 10, [app], [next-event] + + # check for global events that will trigger regardless of which editor has focus + { + k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant + break-unless is-keycode? + + } + { + c:char, is-unicode?:bool <- maybe-convert e:event, text:variant + break-unless is-unicode? + + } + # 'touch' event - send to both sides, see what picks it up + { + t:touch-event, is-touch?:bool <- maybe-convert e:event, touch:variant + break-unless is-touch? + # ignore all but 'left-click' events for now + # todo: test this + touch-type:num <- get t, type:offset + is-left-click?:bool <- equal touch-type, 65513/mouse-left + loop-unless is-left-click?, +next-event + click-row:num <- get t, row:offset + click-column:num <- get t, column:offset + # later exceptions for non-editor touches will go here + + # send to both editors + _ <- move-cursor-in-editor screen, recipes, t + sandbox-in-focus?:bool <- move-cursor-in-editor screen, current-sandbox, t + *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus? + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + loop +next-event + } + # 'resize' event - redraw editor + # todo: test this after supporting resize in assume-console + { + r:resize-event, is-resize?:bool <- maybe-convert e:event, resize:variant + break-unless is-resize? + # if more events, we're still resizing; wait until we stop + more-events?:bool <- has-more-events? console + { + break-unless more-events? + render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event + } + { + break-if more-events? + env, screen <- resize screen, env + screen <- render-all screen, env, render-without-moving-cursor + render-all-on-no-more-events? <- copy 0/false # full render done + } + loop +next-event + } + # if it's not global and not a touch event, send to appropriate editor + { + hide-screen screen + sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset + { + break-if sandbox-in-focus? + render?:bool <- handle-keyboard-event screen, recipes, e:event + # refresh screen only if no more events + # if there are more events to process, wait for them to clear up, then make sure you render-all afterward. + more-events?:bool <- has-more-events? console + { + break-unless more-events? + render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event + jump +finish-event + } + { + break-if more-events? + { + break-unless render-all-on-no-more-events? + # no more events, and we have to force render + screen <- render-all screen, env, render + render-all-on-no-more-events? <- copy 0/false + jump +finish-event + } + # no more events, no force render + { + break-unless render? + screen <- render-recipes screen, env, render + jump +finish-event + } + } + } + { + break-unless sandbox-in-focus? + render?:bool <- handle-keyboard-event screen, current-sandbox, e:event + # refresh screen only if no more events + # if there are more events to process, wait for them to clear up, then make sure you render-all afterward. + more-events?:bool <- has-more-events? console + { + break-unless more-events? + render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event + jump +finish-event + } + { + break-if more-events? + { + break-unless render-all-on-no-more-events? + # no more events, and we have to force render + screen <- render-all screen, env, render + render-all-on-no-more-events? <- copy 0/false + jump +finish-event + } + # no more events, no force render + { + break-unless render? + screen <- render-sandbox-side screen, env, render + jump +finish-event + } + } + } + +finish-event + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + show-screen screen + } + loop + } +] + +def resize screen:&:screen, env:&:environment -> env:&:environment, screen:&:screen [ + local-scope + load-ingredients + clear-screen screen # update screen dimensions + width:num <- screen-width screen + divider:num, _ <- divide-with-remainder width, 2 + # update recipe editor + recipes:&:editor <- get *env, recipes:offset + right:num <- subtract divider, 1 + *recipes <- put *recipes, right:offset, right + # reset cursor (later we'll try to preserve its position) + *recipes <- put *recipes, cursor-row:offset, 1 + *recipes <- put *recipes, cursor-column:offset, 0 + # update sandbox editor + current-sandbox:&:editor <- get *env, current-sandbox:offset + left:num <- add divider, 1 + *current-sandbox <- put *current-sandbox, left:offset, left + right:num <- subtract width, 1 + *current-sandbox <- put *current-sandbox, right:offset, right + # reset cursor (later we'll try to preserve its position) + *current-sandbox <- put *current-sandbox, cursor-row:offset, 1 + *current-sandbox <- put *current-sandbox, cursor-column:offset, left +] + +# Variant of 'render' that updates cursor-row and cursor-column based on +# before-cursor (rather than the other way around). If before-cursor moves +# off-screen, it resets cursor-row and cursor-column. +def render-without-moving-cursor screen:&:screen, editor:&:editor -> last-row:num, last-column:num, screen:&:screen, editor:&:editor [ + local-scope + load-ingredients + return-unless editor, 1/top, 0/left + left:num <- get *editor, left:offset + screen-height:num <- screen-height screen + right:num <- get *editor, right:offset + curr:&:duplex-list:char <- get *editor, top-of-screen:offset + prev:&:duplex-list:char <- copy curr # just in case curr becomes null and we can't compute prev + curr <- next curr + +render-loop-initialization + color:num <- copy 7/white + row:num <- copy 1/top + column:num <- copy left + # save before-cursor + old-before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + # initialze cursor-row/cursor-column/before-cursor to the top of the screen + # by default + *editor <- put *editor, cursor-row:offset, row + *editor <- put *editor, cursor-column:offset, column + top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset + *editor <- put *editor, before-cursor:offset, top-of-screen + screen <- move-cursor screen, row, column + { + +next-character + break-unless curr + off-screen?:bool <- greater-or-equal row, screen-height + break-if off-screen? + # if we find old-before-cursor still on the new resized screen, update + # editor.cursor-row and editor.cursor-column based on + # old-before-cursor + { + at-cursor?:bool <- equal old-before-cursor, prev + break-unless at-cursor? + *editor <- put *editor, cursor-row:offset, row + *editor <- put *editor, cursor-column:offset, column + *editor <- put *editor, before-cursor:offset, old-before-cursor + } + c:char <- get *curr, value:offset + + { + # newline? move to left rather than 0 + newline?:bool <- equal c, 10/newline + break-unless newline? + # clear rest of line in this window + clear-line-until screen, right + # skip to next line + row <- add row, 1 + column <- copy left + screen <- move-cursor screen, row, column + curr <- next curr + prev <- next prev + loop +next-character + } + { + # at right? wrap. even if there's only one more letter left; we need + # room for clicking on the cursor after it. + at-right?:bool <- equal column, right + break-unless at-right? + # print wrap icon + wrap-icon:char <- copy 8617/loop-back-to-left + print screen, wrap-icon, 245/grey + column <- copy left + row <- add row, 1 + screen <- move-cursor screen, row, column + # don't increment curr + loop +next-character + } + print screen, c, color + curr <- next curr + prev <- next prev + column <- add column, 1 + loop + } + # save first character off-screen + *editor <- put *editor, bottom-of-screen:offset, curr + *editor <- put *editor, bottom:offset, row + return row, column +] + +scenario point-at-multiple-editors [ + local-scope + trace-until 100/app # trace too long + assume-screen 30/width, 5/height + # initialize both halves of screen + assume-resources [ + [lesson/recipes.mu] <- [ + |abc| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [def] # contents of sandbox editor + # focus on both sides + assume-console [ + left-click 1, 1 + left-click 1, 17 + ] + # check cursor column in each + run [ + event-loop screen, console, env, resources + recipes:&:editor <- get *env, recipes:offset + 5:num/raw <- get *recipes, cursor-column:offset + sandbox:&:editor <- get *env, current-sandbox:offset + 7:num/raw <- get *sandbox, cursor-column:offset + ] + memory-should-contain [ + 5 <- 1 + 7 <- 17 + ] +] + +scenario edit-multiple-editors [ + local-scope + trace-until 100/app # trace too long + assume-screen 30/width, 5/height + # initialize both halves of screen + assume-resources [ + [lesson/recipes.mu] <- [ + |abc| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [def] # contents of sandbox + render-all screen, env, render + # type one letter in each of them + assume-console [ + left-click 1, 1 + type [0] + left-click 1, 17 + type [1] + ] + run [ + event-loop screen, console, env, resources + recipes:&:editor <- get *env, recipes:offset + 5:num/raw <- get *recipes, cursor-column:offset + sandbox:&:editor <- get *env, current-sandbox:offset + 7:num/raw <- get *sandbox, cursor-column:offset + ] + screen-should-contain [ + . run (F4) . # this line has a different background, but we don't test that yet + .a0bc ┊d1ef . + . ┊──────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + memory-should-contain [ + 5 <- 2 # cursor column of recipe editor + 7 <- 18 # cursor column of sandbox editor + ] + # show the cursor at the right window + run [ + cursor:char <- copy 9251/␣ + print screen, cursor + ] + screen-should-contain [ + . run (F4) . + .a0bc ┊d1␣f . + . ┊──────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario editor-in-focus-keeps-cursor [ + local-scope + trace-until 100/app # trace too long + assume-screen 30/width, 5/height + assume-resources [ + [lesson/recipes.mu] <- [ + |abc| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [def] + render-all screen, env, render + # initialize programming environment and highlight cursor + assume-console [] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # is cursor at the right place? + screen-should-contain [ + . run (F4) . + .␣bc ┊def . + . ┊──────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + # now try typing a letter + assume-console [ + type [z] + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # cursor should still be right + screen-should-contain [ + . run (F4) . + .z␣bc ┊def . + . ┊──────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario backspace-in-sandbox-editor-joins-lines [ + local-scope + trace-until 100/app # trace too long + assume-screen 30/width, 5/height + assume-resources [ + ] + # initialize sandbox side with two lines + test-sandbox-editor-contents:text <- new [abc +def] + env:&:environment <- new-programming-environment resources, screen, test-sandbox-editor-contents + render-all screen, env, render + screen-should-contain [ + . run (F4) . + . ┊abc . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊def . + . ┊──────────────. + . ┊ . + ] + # position cursor at start of second line and hit backspace + assume-console [ + left-click 2, 16 + press backspace + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # cursor moves to end of old line + screen-should-contain [ + . run (F4) . + . ┊abc␣ef . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊──────────────. + . ┊ . + ] +] + +def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ + local-scope + load-ingredients + trace 10, [app], [render all] + hide-screen screen + # top menu + trace 11, [app], [render top menu] + width:num <- screen-width screen + draw-horizontal screen, 0, 0/left, width, 32/space, 0/black, 238/grey + button-start:num <- subtract width, 20 + button-on-screen?:bool <- greater-or-equal button-start, 0 + assert button-on-screen?, [screen too narrow for menu] + screen <- move-cursor screen, 0/row, button-start + print screen, [ run (F4) ], 255/white, 161/reddish + # dotted line down the middle + trace 11, [app], [render divider] + divider:num, _ <- divide-with-remainder width, 2 + height:num <- screen-height screen + draw-vertical screen, divider, 1/top, height, 9482/vertical-dotted + # + screen <- render-recipes screen, env, render-editor + screen <- render-sandbox-side screen, env, render-editor + + # + recipes:&:editor <- get *env, recipes:offset + current-sandbox:&:editor <- get *env, current-sandbox:offset + sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + # + show-screen screen +] + +def render-recipes screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ + local-scope + load-ingredients + trace 11, [app], [render recipes] + recipes:&:editor <- get *env, recipes:offset + # render recipes + left:num <- get *recipes, left:offset + right:num <- get *recipes, right:offset + row:num, column:num, screen <- call render-editor, screen, recipes + clear-line-until screen, right + row <- add row, 1 + + # draw dotted line after recipes + draw-horizontal screen, row, left, right, 9480/horizontal-dotted + row <- add row, 1 + clear-screen-from screen, row, left, left, right +] + +# replaced in a later layer +def render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ + local-scope + load-ingredients + current-sandbox:&:editor <- get *env, current-sandbox:offset + left:num <- get *current-sandbox, left:offset + right:num <- get *current-sandbox, right:offset + row:num, column:num, screen, current-sandbox <- call render-editor, screen, current-sandbox + clear-line-until screen, right + row <- add row, 1 + # draw solid line after code (you'll see why in later layers) + draw-horizontal screen, row, left, right + row <- add row, 1 + clear-screen-from screen, row, left, left, right +] + +def update-cursor screen:&:screen, recipes:&:editor, current-sandbox:&:editor, sandbox-in-focus?:bool, env:&:environment -> screen:&:screen [ + local-scope + load-ingredients + + { + break-if sandbox-in-focus? + cursor-row:num <- get *recipes, cursor-row:offset + cursor-column:num <- get *recipes, cursor-column:offset + } + { + break-unless sandbox-in-focus? + cursor-row:num <- get *current-sandbox, cursor-row:offset + cursor-column:num <- get *current-sandbox, cursor-column:offset + } + screen <- move-cursor screen, cursor-row, cursor-column +] + +# like 'render' for texts, but with colorization for comments like in the editor +def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [ + local-scope + load-ingredients + return-unless s + color:num <- copy 7/white + column:num <- copy left + screen <- move-cursor screen, row, column + screen-height:num <- screen-height screen + i:num <- copy 0 + len:num <- length *s + { + +next-character + done?:bool <- greater-or-equal i, len + break-if done? + done? <- greater-or-equal row, screen-height + break-if done? + c:char <- index *s, i + # only line different from render + { + # at right? wrap. + at-right?:bool <- equal column, right + break-unless at-right? + # print wrap icon + wrap-icon:char <- copy 8617/loop-back-to-left + print screen, wrap-icon, 245/grey + column <- copy left + row <- add row, 1 + screen <- move-cursor screen, row, column + loop +next-character # retry i + } + i <- add i, 1 + { + # newline? move to left rather than 0 + newline?:bool <- equal c, 10/newline + break-unless newline? + # clear rest of line in this window + { + done?:bool <- greater-than column, right + break-if done? + space:char <- copy 32/space + print screen, space + column <- add column, 1 + loop + } + row <- add row, 1 + column <- copy left + screen <- move-cursor screen, row, column + loop +next-character + } + print screen, c, color + column <- add column, 1 + loop + } + was-at-left?:bool <- equal column, left + clear-line-until screen, right + { + break-if was-at-left? + row <- add row, 1 + } + move-cursor screen, row, left +] + +# ctrl-l - redraw screen (just in case it printed junk somehow) + +after [ + { + redraw-screen?:bool <- equal c, 12/ctrl-l + break-unless redraw-screen? + screen <- render-all screen, env:&:environment, render + sync-screen screen + loop +next-event + } +] + +# ctrl-n - switch focus +# todo: test this + +after [ + { + switch-side?:bool <- equal c, 14/ctrl-n + break-unless switch-side? + sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset + sandbox-in-focus? <- not sandbox-in-focus? + *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus? + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + loop +next-event + } +] + +## helpers + +def draw-vertical screen:&:screen, col:num, y:num, bottom:num -> screen:&:screen [ + local-scope + load-ingredients + style:char, style-found?:bool <- next-ingredient + { + break-if style-found? + style <- copy 9474/vertical + } + color:num, color-found?:bool <- next-ingredient + { + # default color to white + break-if color-found? + color <- copy 245/grey + } + { + continue?:bool <- lesser-than y, bottom + break-unless continue? + screen <- move-cursor screen, y, col + print screen, style, color + y <- add y, 1 + loop + } +] diff --git a/edit/004-shortcuts.mu b/edit/004-shortcuts.mu deleted file mode 100644 index 941d316d..00000000 --- a/edit/004-shortcuts.mu +++ /dev/null @@ -1,3299 +0,0 @@ -## special shortcuts for manipulating the editor -# Some keys on the keyboard generate unicode characters, others generate -# terminfo key codes. We need to modify different places in the two cases. - -# tab - insert two spaces - -scenario editor-inserts-two-spaces-on-tab [ - local-scope - assume-screen 10/width, 5/height - # just one character in final line - s:text <- new [ab -cd] - e:&:editor <- new-editor s, 0/left, 5/right - assume-console [ - press tab - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - . ab . - .cd . - ] -] - -after [ - { - tab?:bool <- equal c, 9/tab - break-unless tab? - - insert-at-cursor editor, 32/space, screen - insert-at-cursor editor, 32/space, screen - - return 1/go-render - } -] - -# backspace - delete character before cursor - -scenario editor-handles-backspace-key [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abc], 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 1, 1 - press backspace - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - screen-should-contain [ - . . - .bc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 4 <- 1 - 5 <- 0 - ] - check-trace-count-for-label 3, [print-character] # length of original line to overwrite -] - -after [ - { - delete-previous-character?:bool <- equal c, 8/backspace - break-unless delete-previous-character? - - go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen - - return - } -] - -# return values: -# go-render? - whether caller needs to update the screen -# backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc. -def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [ - local-scope - load-ingredients - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - data:&:duplex-list:char <- get *editor, data:offset - # if at start of text (before-cursor at § sentinel), return - prev:&:duplex-list:char <- prev before-cursor - return-unless prev, 0/no-more-render, 0/nothing-deleted - trace 10, [app], [delete-before-cursor] - original-row:num <- get *editor, cursor-row:offset - scroll?:bool <- move-cursor-coordinates-left editor - backspaced-cell:&:duplex-list:char <- copy before-cursor - data <- remove before-cursor, data # will also neatly trim next/prev pointers in backspaced-cell/before-cursor - before-cursor <- copy prev - *editor <- put *editor, before-cursor:offset, before-cursor - return-if scroll?, 1/go-render - screen-width:num <- screen-width screen - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - # did we just backspace over a newline? - same-row?:bool <- equal cursor-row, original-row - return-unless same-row?, 1/go-render - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - curr:&:duplex-list:char <- next before-cursor - screen <- move-cursor screen, cursor-row, cursor-column - curr-column:num <- copy cursor-column - { - # hit right margin? give up and let caller render - at-right?:bool <- greater-or-equal curr-column, right - return-if at-right?, 1/go-render - break-unless curr - # newline? done. - currc:char <- get *curr, value:offset - at-newline?:bool <- equal currc, 10/newline - break-if at-newline? - screen <- print screen, currc - curr-column <- add curr-column, 1 - curr <- next curr - loop - } - # we're guaranteed not to be at the right margin - space:char <- copy 32/space - screen <- print screen, space - go-render? <- copy 0/false -] - -def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [ - local-scope - load-ingredients - go-render?:bool <- copy 0/false - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - left:num <- get *editor, left:offset - # if not at left margin, move one character left - { - at-left-margin?:bool <- equal cursor-column, left - break-if at-left-margin? - trace 10, [app], [decrementing cursor column] - cursor-column <- subtract cursor-column, 1 - *editor <- put *editor, cursor-column:offset, cursor-column - return - } - # if at left margin, we must move to previous row: - top-of-screen?:bool <- equal cursor-row, 1 # exclude menu bar - { - break-if top-of-screen? - cursor-row <- subtract cursor-row, 1 - *editor <- put *editor, cursor-row:offset, cursor-row - } - { - break-unless top-of-screen? - - go-render? <- copy 1/true - } - { - # case 1: if previous character was newline, figure out how long the previous line is - previous-character:char <- get *before-cursor, value:offset - previous-character-is-newline?:bool <- equal previous-character, 10/newline - break-unless previous-character-is-newline? - # compute length of previous line - trace 10, [app], [switching to previous line] - d:&:duplex-list:char <- get *editor, data:offset - end-of-line:num <- previous-line-length before-cursor, d - right:num <- get *editor, right:offset - width:num <- subtract right, left - wrap?:bool <- greater-than end-of-line, width - { - break-unless wrap? - _, column-offset:num <- divide-with-remainder end-of-line, width - cursor-column <- add left, column-offset - *editor <- put *editor, cursor-column:offset, cursor-column - } - { - break-if wrap? - cursor-column <- add left, end-of-line - *editor <- put *editor, cursor-column:offset, cursor-column - } - return - } - # case 2: if previous-character was not newline, we're just at a wrapped line - trace 10, [app], [wrapping to previous line] - right:num <- get *editor, right:offset - cursor-column <- subtract right, 1 # leave room for wrap icon - *editor <- put *editor, cursor-column:offset, cursor-column -] - -# takes a pointer 'curr' into the doubly-linked list and its sentinel, counts -# the length of the previous line before the 'curr' pointer. -def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [ - local-scope - load-ingredients - result:num <- copy 0 - return-unless curr - at-start?:bool <- equal curr, start - return-if at-start? - { - curr <- prev curr - break-unless curr - at-start?:bool <- equal curr, start - break-if at-start? - c:char <- get *curr, value:offset - at-newline?:bool <- equal c, 10/newline - break-if at-newline? - result <- add result, 1 - loop - } -] - -scenario editor-clears-last-line-on-backspace [ - local-scope - assume-screen 10/width, 5/height - # just one character in final line - s:text <- new [ab -cd] - e:&:editor <- new-editor s, 0/left, 10/right - assume-console [ - left-click 2, 0 # cursor at only character in final line - press backspace - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - screen-should-contain [ - . . - .abcd . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 4 <- 1 - 5 <- 2 - ] -] - -scenario editor-joins-and-wraps-lines-on-backspace [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with two long-ish but non-wrapping lines - s:text <- new [abc def -ghi jkl] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # position the cursor at the start of the second and hit backspace - assume-console [ - left-click 2, 0 - press backspace - ] - run [ - editor-event-loop screen, console, e - ] - # resulting single line should wrap correctly - screen-should-contain [ - . . - .abc defgh↩. - .i jkl . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-wraps-long-lines-on-backspace [ - local-scope - assume-screen 10/width, 5/height - # initialize editor in part of the screen with a long line - e:&:editor <- new-editor [abc def ghij], 0/left, 8/right - editor-render screen, e - # confirm that it wraps - screen-should-contain [ - . . - .abc def↩ . - . ghij . - .┈┈┈┈┈┈┈┈ . - ] - $clear-trace - # position the cursor somewhere in the middle of the top screen line and hit backspace - assume-console [ - left-click 1, 4 - press backspace - ] - run [ - editor-event-loop screen, console, e - ] - # resulting single line should wrap correctly and not overflow its bounds - screen-should-contain [ - . . - .abcdef ↩ . - .ghij . - .┈┈┈┈┈┈┈┈ . - . . - ] -] - -# delete - delete character at cursor - -scenario editor-handles-delete-key [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abc], 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - press delete - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .bc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 3, [print-character] # length of original line to overwrite - $clear-trace - assume-console [ - press delete - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .c . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 2, [print-character] # new length to overwrite -] - -after [ - { - delete-next-character?:bool <- equal k, 65522/delete - break-unless delete-next-character? - - go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen - - return - } -] - -def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [ - local-scope - load-ingredients - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - data:&:duplex-list:char <- get *editor, data:offset - deleted-cell:&:duplex-list:char <- next before-cursor - return-unless deleted-cell, 0/don't-render - currc:char <- get *deleted-cell, value:offset - data <- remove deleted-cell, data - deleted-newline?:bool <- equal currc, 10/newline - return-if deleted-newline?, 1/go-render - # wasn't a newline? render rest of line - curr:&:duplex-list:char <- next before-cursor # refresh after remove above - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - screen <- move-cursor screen, cursor-row, cursor-column - curr-column:num <- copy cursor-column - screen-width:num <- screen-width screen - { - # hit right margin? give up and let caller render - at-right?:bool <- greater-or-equal curr-column, screen-width - return-if at-right?, 1/go-render - break-unless curr - # newline? done. - currc:char <- get *curr, value:offset - at-newline?:bool <- equal currc, 10/newline - break-if at-newline? - screen <- print screen, currc - curr-column <- add curr-column, 1 - curr <- next curr - loop - } - # we're guaranteed not to be at the right margin - space:char <- copy 32/space - screen <- print screen, space - go-render? <- copy 0/false -] - -# right arrow - -scenario editor-moves-cursor-right-with-key [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abc], 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - press right-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .a0bc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 3, [print-character] # 0 and following characters -] - -after [ - { - move-to-next-character?:bool <- equal k, 65514/right-arrow - break-unless move-to-next-character? - # if not at end of text - next-cursor:&:duplex-list:char <- next before-cursor - break-unless next-cursor - # scan to next character - - before-cursor <- copy next-cursor - *editor <- put *editor, before-cursor:offset, before-cursor - go-render?:bool <- move-cursor-coordinates-right editor, screen-height - screen <- move-cursor screen, cursor-row, cursor-column - undo-coalesce-tag:num <- copy 2/right-arrow - - return - } -] - -def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [ - local-scope - load-ingredients - before-cursor:&:duplex-list:char <- get *editor before-cursor:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - # if crossed a newline, move cursor to start of next row - { - old-cursor-character:char <- get *before-cursor, value:offset - was-at-newline?:bool <- equal old-cursor-character, 10/newline - break-unless was-at-newline? - cursor-row <- add cursor-row, 1 - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- copy left - *editor <- put *editor, cursor-column:offset, cursor-column - below-screen?:bool <- greater-or-equal cursor-row, screen-height # must be equal - return-unless below-screen?, 0/don't-render - - cursor-row <- subtract cursor-row, 1 # bring back into screen range - *editor <- put *editor, cursor-row:offset, cursor-row - return 1/go-render - } - # if the line wraps, move cursor to start of next row - { - # if we're at the column just before the wrap indicator - wrap-column:num <- subtract right, 1 - at-wrap?:bool <- equal cursor-column, wrap-column - break-unless at-wrap? - # and if next character isn't newline - next:&:duplex-list:char <- next before-cursor - break-unless next - next-character:char <- get *next, value:offset - newline?:bool <- equal next-character, 10/newline - break-if newline? - cursor-row <- add cursor-row, 1 - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- copy left - *editor <- put *editor, cursor-column:offset, cursor-column - below-screen?:bool <- greater-or-equal cursor-row, screen-height # must be equal - return-unless below-screen?, 0/no-more-render - - cursor-row <- subtract cursor-row, 1 # bring back into screen range - *editor <- put *editor, cursor-row:offset, cursor-row - return 1/go-render - } - # otherwise move cursor one character right - cursor-column <- add cursor-column, 1 - *editor <- put *editor, cursor-column:offset, cursor-column - go-render? <- copy 0/false -] - -scenario editor-moves-cursor-to-next-line-with-right-arrow [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # type right-arrow a few times to get to start of second line - assume-console [ - press right-arrow - press right-arrow - press right-arrow - press right-arrow # next line - ] - run [ - editor-event-loop screen, console, e - ] - check-trace-count-for-label 0, [print-character] - # type something and ensure it goes where it should - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .0d . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 2, [print-character] # new length of second line -] - -scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -d] - e:&:editor <- new-editor s, 1/left, 10/right - editor-render screen, e - assume-console [ - press right-arrow - press right-arrow - press right-arrow - press right-arrow # next line - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - . abc . - . 0d . - . ┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abcdef], 0/left, 5/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 1, 3 - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - screen-should-contain [ - . . - .abcd↩ . - .ef . - .┈┈┈┈┈ . - . . - ] - memory-should-contain [ - 3 <- 2 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [ - local-scope - assume-screen 10/width, 5/height - # line just barely wrapping - e:&:editor <- new-editor [abcde], 0/left, 5/right - editor-render screen, e - $clear-trace - # position cursor at last character before wrap and hit right-arrow - assume-console [ - left-click 1, 3 - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 2 - 4 <- 0 - ] - # now hit right arrow again - assume-console [ - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abcdef], 1/left, 6/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 1, 4 - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - screen-should-contain [ - . . - . abcd↩ . - . ef . - . ┈┈┈┈┈ . - . . - ] - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # move to end of line, press right-arrow, type a character - assume-console [ - left-click 1, 3 - press right-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - # new character should be in next line - screen-should-contain [ - . . - .abc . - .0d . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 2, [print-character] -] - -# todo: ctrl-right: next word-end - -# left arrow - -scenario editor-moves-cursor-left-with-key [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abc], 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 1, 2 - press left-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .a0bc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 3, [print-character] -] - -after [ - { - move-to-previous-character?:bool <- equal k, 65515/left-arrow - break-unless move-to-previous-character? - trace 10, [app], [left arrow] - # if not at start of text (before-cursor at § sentinel) - prev:&:duplex-list:char <- prev before-cursor - return-unless prev, 0/don't-render - - go-render? <- move-cursor-coordinates-left editor - before-cursor <- copy prev - *editor <- put *editor, before-cursor:offset, before-cursor - undo-coalesce-tag:num <- copy 1/left-arrow - - return - } -] - -scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with two lines - s:text <- new [abc -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # position cursor at start of second line (so there's no previous newline) - assume-console [ - left-click 2, 0 - press left-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with three lines - s:text <- new [abc -def -g] - e:&:editor <- new-editor s:text, 0/left, 10/right - editor-render screen, e - $clear-trace - # position cursor further down (so there's a newline before the character at - # the cursor) - assume-console [ - left-click 3, 0 - press left-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def0 . - .g . - .┈┈┈┈┈┈┈┈┈┈. - ] - check-trace-count-for-label 1, [print-character] # just the '0' -] - -scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -def -g] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # position cursor at start of text, press left-arrow, then type a character - assume-console [ - left-click 1, 0 - press left-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - # left-arrow should have had no effect - screen-should-contain [ - . . - .0abc . - .def . - .g . - .┈┈┈┈┈┈┈┈┈┈. - ] - check-trace-count-for-label 4, [print-character] # length of first line -] - -scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with text containing an empty line - s:text <- new [abc - -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e:&:editor - $clear-trace - # position cursor right after empty line - assume-console [ - left-click 3, 0 - press left-arrow - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .0 . - .d . - .┈┈┈┈┈┈┈┈┈┈. - ] - check-trace-count-for-label 1, [print-character] # just the '0' -] - -scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with a wrapping line - e:&:editor <- new-editor [abcdef], 0/left, 5/right - editor-render screen, e - $clear-trace - screen-should-contain [ - . . - .abcd↩ . - .ef . - .┈┈┈┈┈ . - . . - ] - # position cursor right after empty line - assume-console [ - left-click 2, 0 - press left-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 # previous row - 4 <- 3 # right margin except wrap icon - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with a wrapping line followed by a second line - s:text <- new [abcdef -g] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - $clear-trace - screen-should-contain [ - . . - .abcd↩ . - .ef . - .g . - .┈┈┈┈┈ . - ] - # position cursor right after empty line - assume-console [ - left-click 3, 0 - press left-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 2 # previous row - 4 <- 2 # end of wrapped line - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [ - local-scope - assume-screen 10/width, 5/height - # initialize editor with a line on the verge of wrapping, followed by a second line - s:text <- new [abcd -e] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - $clear-trace - screen-should-contain [ - . . - .abcd . - .e . - .┈┈┈┈┈ . - . . - ] - # position cursor right after empty line - assume-console [ - left-click 2, 0 - press left-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 # previous row - 4 <- 4 # end of wrapped line - ] - check-trace-count-for-label 0, [print-character] -] - -# todo: ctrl-left: previous word-start - -# up arrow - -scenario editor-moves-to-previous-line-with-up-arrow [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -def] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 2, 1 - press up-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .a0bc . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - { - move-to-previous-line?:bool <- equal k, 65517/up-arrow - break-unless move-to-previous-line? - - go-render? <- move-to-previous-line editor - undo-coalesce-tag:num <- copy 3/up-arrow - - return - } -] - -def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [ - local-scope - load-ingredients - go-render?:bool <- copy 0/false - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - already-at-top?:bool <- lesser-or-equal cursor-row, 1/top - { - # if cursor not at top, move it - break-if already-at-top? - # if not at newline, move to start of line (previous newline) - # then scan back another line - # if either step fails, give up without modifying cursor or coordinates - curr:&:duplex-list:char <- copy before-cursor - { - old:&:duplex-list:char <- copy curr - c2:char <- get *curr, value:offset - at-newline?:bool <- equal c2, 10/newline - break-if at-newline? - curr:&:duplex-list:char <- before-previous-line curr, editor - no-motion?:bool <- equal curr, old - return-if no-motion? - } - { - old <- copy curr - curr <- before-previous-line curr, editor - no-motion?:bool <- equal curr, old - return-if no-motion? - } - before-cursor <- copy curr - *editor <- put *editor, before-cursor:offset, before-cursor - cursor-row <- subtract cursor-row, 1 - *editor <- put *editor, cursor-row:offset, cursor-row - # scan ahead to right column or until end of line - target-column:num <- copy cursor-column - cursor-column <- copy left - *editor <- put *editor, cursor-column:offset, cursor-column - { - done?:bool <- greater-or-equal cursor-column, target-column - break-if done? - curr:&:duplex-list:char <- next before-cursor - break-unless curr - currc:char <- get *curr, value:offset - at-newline?:bool <- equal currc, 10/newline - break-if at-newline? - # - before-cursor <- copy curr - *editor <- put *editor, before-cursor:offset, before-cursor - cursor-column <- add cursor-column, 1 - *editor <- put *editor, cursor-column:offset, cursor-column - loop - } - return - } - { - # if cursor already at top, scroll up - break-unless already-at-top? - - return 1/go-render - } -] - -scenario editor-adjusts-column-at-previous-line [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [ab -def] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 2, 3 - press up-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .ab0 . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-adjusts-column-at-empty-line [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [ -def] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 2, 3 - press up-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .0 . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-moves-to-previous-line-from-left-margin [ - local-scope - assume-screen 10/width, 5/height - # start out with three lines - s:text <- new [abc -def -ghi] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # click on the third line and hit up-arrow, so you end up just after a newline - assume-console [ - left-click 3, 0 - press up-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 2 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .0def . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -# down arrow - -scenario editor-moves-to-next-line-with-down-arrow [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -def] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # cursor starts out at (1, 0) - assume-console [ - press down-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # ..and ends at (2, 0) - memory-should-contain [ - 3 <- 2 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .0def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - { - move-to-next-line?:bool <- equal k, 65516/down-arrow - break-unless move-to-next-line? - - go-render? <- move-to-next-line editor, screen-height - undo-coalesce-tag:num <- copy 4/down-arrow - - return - } -] - -def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [ - local-scope - load-ingredients - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - last-line:num <- subtract screen-height, 1 - already-at-bottom?:bool <- greater-or-equal cursor-row, last-line - { - # if cursor not at bottom, move it - break-if already-at-bottom? - # scan to start of next line, then to right column or until end of line - max:num <- subtract right, left - next-line:&:duplex-list:char <- before-start-of-next-line before-cursor, max - { - # already at end of buffer? try to scroll up (so we can see more - # warnings or sandboxes below) - no-motion?:bool <- equal next-line, before-cursor - break-unless no-motion? - scroll?:bool <- greater-than cursor-row, 1 - break-if scroll?, +try-to-scroll - return 0/don't-render - } - cursor-row <- add cursor-row, 1 - *editor <- put *editor, cursor-row:offset, cursor-row - before-cursor <- copy next-line - *editor <- put *editor, before-cursor:offset, before-cursor - target-column:num <- copy cursor-column - cursor-column <- copy left - *editor <- put *editor, cursor-column:offset, cursor-column - { - done?:bool <- greater-or-equal cursor-column, target-column - break-if done? - curr:&:duplex-list:char <- next before-cursor - break-unless curr - currc:char <- get *curr, value:offset - at-newline?:bool <- equal currc, 10/newline - break-if at-newline? - # - before-cursor <- copy curr - *editor <- put *editor, before-cursor:offset, before-cursor - cursor-column <- add cursor-column, 1 - *editor <- put *editor, cursor-column:offset, cursor-column - loop - } - return 0/don't-render - } - +try-to-scroll - - go-render? <- copy 1/true -] - -scenario editor-adjusts-column-at-next-line [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -de] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - assume-console [ - left-click 1, 3 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 3 <- 2 - 4 <- 2 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .de0 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# ctrl-a/home - move cursor to start of line - -scenario editor-moves-to-start-of-line-with-ctrl-a [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on second line, press ctrl-a - assume-console [ - left-click 2, 3 - press ctrl-a - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to start of line - memory-should-contain [ - 4 <- 2 - 5 <- 0 - ] - check-trace-count-for-label 0, [print-character] -] - -after [ - { - move-to-start-of-line?:bool <- equal c, 1/ctrl-a - break-unless move-to-start-of-line? - - move-to-start-of-line editor - undo-coalesce-tag:num <- copy 0/never - - return 0/don't-render - } -] - -after [ - { - move-to-start-of-line?:bool <- equal k, 65521/home - break-unless move-to-start-of-line? - - move-to-start-of-line editor - undo-coalesce-tag:num <- copy 0/never - - return 0/don't-render - } -] - -def move-to-start-of-line editor:&:editor -> editor:&:editor [ - local-scope - load-ingredients - # update cursor column - left:num <- get *editor, left:offset - cursor-column:num <- copy left - *editor <- put *editor, cursor-column:offset, cursor-column - # update before-cursor - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - init:&:duplex-list:char <- get *editor, data:offset - # while not at start of line, move - { - at-start-of-text?:bool <- equal before-cursor, init - break-if at-start-of-text? - prev:char <- get *before-cursor, value:offset - at-start-of-line?:bool <- equal prev, 10/newline - break-if at-start-of-line? - before-cursor <- prev before-cursor - *editor <- put *editor, before-cursor:offset, before-cursor - assert before-cursor, [move-to-start-of-line tried to move before start of text] - loop - } -] - -scenario editor-moves-to-start-of-line-with-ctrl-a-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on first line (no newline before), press ctrl-a - assume-console [ - left-click 1, 3 - press ctrl-a - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to start of line - memory-should-contain [ - 4 <- 1 - 5 <- 0 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-to-start-of-line-with-home [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - $clear-trace - # start on second line, press 'home' - assume-console [ - left-click 2, 3 - press home - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to start of line - memory-should-contain [ - 3 <- 2 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-to-start-of-line-with-home-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on first line (no newline before), press 'home' - assume-console [ - left-click 1, 3 - press home - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to start of line - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - check-trace-count-for-label 0, [print-character] -] - -# ctrl-e/end - move cursor to end of line - -scenario editor-moves-to-end-of-line-with-ctrl-e [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on first line, press ctrl-e - assume-console [ - left-click 1, 1 - press ctrl-e - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to end of line - memory-should-contain [ - 4 <- 1 - 5 <- 3 - ] - check-trace-count-for-label 0, [print-character] - # editor inserts future characters at cursor - assume-console [ - type [z] - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - memory-should-contain [ - 4 <- 1 - 5 <- 4 - ] - screen-should-contain [ - . . - .123z . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - check-trace-count-for-label 1, [print-character] -] - -after [ - { - move-to-end-of-line?:bool <- equal c, 5/ctrl-e - break-unless move-to-end-of-line? - - move-to-end-of-line editor - undo-coalesce-tag:num <- copy 0/never - - return 0/don't-render - } -] - -after [ - { - move-to-end-of-line?:bool <- equal k, 65520/end - break-unless move-to-end-of-line? - - move-to-end-of-line editor - undo-coalesce-tag:num <- copy 0/never - - return 0/don't-render - } -] - -def move-to-end-of-line editor:&:editor -> editor:&:editor [ - local-scope - load-ingredients - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - cursor-column:num <- get *editor, cursor-column:offset - # while not at start of line, move - { - next:&:duplex-list:char <- next before-cursor - break-unless next # end of text - nextc:char <- get *next, value:offset - at-end-of-line?:bool <- equal nextc, 10/newline - break-if at-end-of-line? - before-cursor <- copy next - *editor <- put *editor, before-cursor:offset, before-cursor - cursor-column <- add cursor-column, 1 - *editor <- put *editor, cursor-column:offset, cursor-column - loop - } -] - -scenario editor-moves-to-end-of-line-with-ctrl-e-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on second line (no newline after), press ctrl-e - assume-console [ - left-click 2, 1 - press ctrl-e - ] - run [ - editor-event-loop screen, console, e - 4:num/raw <- get *e, cursor-row:offset - 5:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to end of line - memory-should-contain [ - 4 <- 2 - 5 <- 3 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-to-end-of-line-with-end [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on first line, press 'end' - assume-console [ - left-click 1, 1 - press end - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to end of line - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - check-trace-count-for-label 0, [print-character] -] - -scenario editor-moves-to-end-of-line-with-end-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # start on second line (no newline after), press 'end' - assume-console [ - left-click 2, 1 - press end - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # cursor moves to end of line - memory-should-contain [ - 3 <- 2 - 4 <- 3 - ] - check-trace-count-for-label 0, [print-character] -] - -# ctrl-u - delete text from start of line until (but not at) cursor - -scenario editor-deletes-to-start-of-line-with-ctrl-u [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start on second line, press ctrl-u - assume-console [ - left-click 2, 2 - press ctrl-u - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to start of line - screen-should-contain [ - . . - .123 . - .6 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - { - delete-to-start-of-line?:bool <- equal c, 21/ctrl-u - break-unless delete-to-start-of-line? - - deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor - - return 1/go-render - } -] - -def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [ - local-scope - load-ingredients - # compute range to delete - init:&:duplex-list:char <- get *editor, data:offset - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - start:&:duplex-list:char <- copy before-cursor - end:&:duplex-list:char <- next before-cursor - { - at-start-of-text?:bool <- equal start, init - break-if at-start-of-text? - curr:char <- get *start, value:offset - at-start-of-line?:bool <- equal curr, 10/newline - break-if at-start-of-line? - start <- prev start - assert start, [delete-to-start-of-line tried to move before start of text] - loop - } - # snip it out - result:&:duplex-list:char <- next start - remove-between start, end - # adjust cursor - before-cursor <- copy start - *editor <- put *editor, before-cursor:offset, before-cursor - left:num <- get *editor, left:offset - *editor <- put *editor, cursor-column:offset, left -] - -scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start on first line (no newline before), press ctrl-u - assume-console [ - left-click 1, 2 - press ctrl-u - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to start of line - screen-should-contain [ - . . - .3 . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start past end of line, press ctrl-u - assume-console [ - left-click 1, 3 - press ctrl-u - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to start of line - screen-should-contain [ - . . - . . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-start-of-final-line-with-ctrl-u [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start past end of final line, press ctrl-u - assume-console [ - left-click 2, 3 - press ctrl-u - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to start of line - screen-should-contain [ - . . - .123 . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# ctrl-k - delete text from cursor to end of line (but not the newline) - -scenario editor-deletes-to-end-of-line-with-ctrl-k [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start on first line, press ctrl-k - assume-console [ - left-click 1, 1 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to end of line - screen-should-contain [ - . . - .1 . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - { - delete-to-end-of-line?:bool <- equal c, 11/ctrl-k - break-unless delete-to-end-of-line? - - deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor - - return 1/go-render - } -] - -def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [ - local-scope - load-ingredients - # compute range to delete - start:&:duplex-list:char <- get *editor, before-cursor:offset - end:&:duplex-list:char <- next start - { - at-end-of-text?:bool <- equal end, 0/null - break-if at-end-of-text? - curr:char <- get *end, value:offset - at-end-of-line?:bool <- equal curr, 10/newline - break-if at-end-of-line? - end <- next end - loop - } - # snip it out - result <- next start - remove-between start, end -] - -scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start on second line (no newline after), press ctrl-k - assume-console [ - left-click 2, 1 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes to end of line - screen-should-contain [ - . . - .123 . - .4 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start at end of line - assume-console [ - left-click 1, 2 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes just last character - screen-should-contain [ - . . - .12 . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start past end of line - assume-console [ - left-click 1, 3 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes nothing - screen-should-contain [ - . . - .123 . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start at end of text - assume-console [ - left-click 2, 2 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes just the final character - screen-should-contain [ - . . - .123 . - .45 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [123 -456] - e:&:editor <- new-editor s, 0/left, 10/right - # start past end of text - assume-console [ - left-click 2, 3 - press ctrl-k - ] - run [ - editor-event-loop screen, console, e - ] - # cursor deletes nothing - screen-should-contain [ - . . - .123 . - .456 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# cursor-down can scroll if necessary - -scenario editor-can-scroll-down-using-arrow-keys [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with >3 lines - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # position cursor at last line, then try to move further down - assume-console [ - left-click 3, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen slides by one line - screen-should-contain [ - . . - .b . - .c . - .d . - ] -] - -after [ - trace 10, [app], [scroll down] - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - max:num <- subtract right, left - old-top:&:duplex-list:char <- copy top-of-screen - top-of-screen <- before-start-of-next-line top-of-screen, max - *editor <- put *editor, top-of-screen:offset, top-of-screen - no-movement?:bool <- equal old-top, top-of-screen - return-if no-movement?, 0/don't-render -] - -# takes a pointer into the doubly-linked list, scans ahead at most 'max' -# positions until the next newline -# beware: never return null pointer. -def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [ - local-scope - load-ingredients - count:num <- copy 0 - curr:&:duplex-list:char <- copy original - # skip the initial newline if it exists - { - c:char <- get *curr, value:offset - at-newline?:bool <- equal c, 10/newline - break-unless at-newline? - curr <- next curr - count <- add count, 1 - } - { - return-unless curr, original - done?:bool <- greater-or-equal count, max - break-if done? - c:char <- get *curr, value:offset - at-newline?:bool <- equal c, 10/newline - break-if at-newline? - curr <- next curr - count <- add count, 1 - loop - } - return-unless curr, original - return curr -] - -scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with a long, wrapped line and more than a screen of - # other lines - s:text <- new [abcdef -g -h -i] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - screen-should-contain [ - . . - .abcd↩ . - .ef . - .g . - ] - # position cursor at last line, then try to move further down - assume-console [ - left-click 3, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .ef . - .g . - .h . - ] -] - -scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # editor starts with a long line wrapping twice - s:text <- new [abcdefghij -k -l -m] - e:&:editor <- new-editor s, 0/left, 5/right - # position cursor at last line, then try to move further down - assume-console [ - left-click 3, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line containing a wrap icon - screen-should-contain [ - . . - .efgh↩ . - .ij . - .k . - ] - # scroll down again - assume-console [ - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .ij . - .k . - .l . - ] -] - -scenario editor-scrolls-down-when-line-wraps [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 5/width, 4/height - # editor contains a long line in the third line - s:text <- new [a -b -cdef] - e:&:editor <- new-editor s, 0/left, 5/right - # position cursor at end, type a character - assume-console [ - left-click 3, 4 - type [g] - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen scrolls - screen-should-contain [ - . . - .b . - .cdef↩. - .g . - ] - memory-should-contain [ - 3 <- 3 - 4 <- 1 - ] -] - -scenario editor-scrolls-down-on-newline [ - local-scope - assume-screen 5/width, 4/height - # position cursor after last line and type newline - s:text <- new [a -b -c] - e:&:editor <- new-editor s, 0/left, 5/right - assume-console [ - left-click 3, 4 - type [ -] - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen scrolls - screen-should-contain [ - . . - .b . - .c . - . . - ] - memory-should-contain [ - 3 <- 3 - 4 <- 0 - ] -] - -scenario editor-scrolls-down-on-right-arrow [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 5/width, 4/height - # editor contains a wrapped line - s:text <- new [a -b -cdefgh] - e:&:editor <- new-editor s, 0/left, 5/right - # position cursor at end of screen and try to move right - assume-console [ - left-click 3, 3 - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen scrolls - screen-should-contain [ - . . - .b . - .cdef↩. - .gh . - ] - memory-should-contain [ - 3 <- 3 - 4 <- 0 - ] -] - -scenario editor-scrolls-down-on-right-arrow-2 [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 5/width, 4/height - # editor contains more lines than can fit on screen - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 5/right - # position cursor at end of screen and try to move right - assume-console [ - left-click 3, 3 - press right-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen scrolls - screen-should-contain [ - . . - .b . - .c . - .d . - ] - memory-should-contain [ - 3 <- 3 - 4 <- 0 - ] -] - -scenario editor-scrolls-at-end-on-down-arrow [ - local-scope - assume-screen 10/width, 5/height - s:text <- new [abc -de] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - $clear-trace - # try to move down past end of text - assume-console [ - left-click 2, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen should scroll, moving cursor to end of text - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - assume-console [ - type [0] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .de0 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # try to move down again - $clear-trace - assume-console [ - left-click 2, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen stops scrolling because cursor is already at top - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - check-trace-count-for-label 0, [print-character] - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .de01 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-combines-page-and-line-scroll [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with a few pages of lines - s:text <- new [a -b -c -d -e -f -g] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - # scroll down one page and one line - assume-console [ - press page-down - left-click 3, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen scrolls down 3 lines - screen-should-contain [ - . . - .d . - .e . - .f . - ] -] - -# cursor-up can scroll if necessary - -scenario editor-can-scroll-up-using-arrow-keys [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with >3 lines - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # position cursor at top of second page, then try to move up - assume-console [ - press page-down - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen slides by one line - screen-should-contain [ - . . - .b . - .c . - .d . - ] -] - -after [ - trace 10, [app], [scroll up] - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - old-top:&:duplex-list:char <- copy top-of-screen - top-of-screen <- before-previous-line top-of-screen, editor - *editor <- put *editor, top-of-screen:offset, top-of-screen - no-movement?:bool <- equal old-top, top-of-screen - return-if no-movement?, 0/don't-render -] - -# takes a pointer into the doubly-linked list, scans back to before start of -# previous *wrapped* line -# beware: never return null pointer -def before-previous-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [ - local-scope - load-ingredients - curr:&:duplex-list:char <- copy in - c:char <- get *curr, value:offset - # compute max, number of characters to skip - # 1 + len%(width-1) - # except rotate second term to vary from 1 to width-1 rather than 0 to width-2 - left:num <- get *editor, left:offset - right:num <- get *editor, right:offset - max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon - sentinel:&:duplex-list:char <- get *editor, data:offset - len:num <- previous-line-length curr, sentinel - { - break-if len - # empty line; just skip this newline - prev:&:duplex-list:char <- prev curr - return-unless prev, curr - return prev - } - _, max:num <- divide-with-remainder len, max-line-length - # remainder 0 => scan one width-worth - { - break-if max - max <- copy max-line-length - } - max <- add max, 1 - count:num <- copy 0 - # skip 'max' characters - { - done?:bool <- greater-or-equal count, max - break-if done? - prev:&:duplex-list:char <- prev curr - break-unless prev - curr <- copy prev - count <- add count, 1 - loop - } - return curr -] - -scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with a long, wrapped line and more than a screen of - # other lines - s:text <- new [abcdef -g -h -i] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - screen-should-contain [ - . . - .abcd↩ . - .ef . - .g . - ] - # position cursor at top of second page, just below wrapped line - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .g . - .h . - .i . - ] - # now move up one line - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .ef . - .g . - .h . - ] -] - -scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [ - local-scope - # screen has 1 line for menu + 4 lines - assume-screen 10/width, 5/height - # editor starts with a long line wrapping twice, occupying 3 of the 4 lines - s:text <- new [abcdefghij -k -l -m] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - # position cursor at top of second page - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .k . - .l . - .m . - .┈┈┈┈┈ . - ] - # move up one line - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .ij . - .k . - .l . - .m . - ] - # move up a second line - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .efgh↩ . - .ij . - .k . - .l . - ] - # move up a third line - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .abcd↩ . - .efgh↩ . - .ij . - .k . - ] -] - -# same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length -# slightly off, just to prevent over-training -scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with a long, wrapped line and more than a screen of - # other lines - s:text <- new [abcdef -g -h -i] - e:&:editor <- new-editor s, 0/left, 6/right - editor-render screen, e - screen-should-contain [ - . . - .abcde↩ . - .f . - .g . - ] - # position cursor at top of second page, just below wrapped line - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .g . - .h . - .i . - ] - # now move up one line - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows partial wrapped line - screen-should-contain [ - . . - .f . - .g . - .h . - ] -] - -# check empty lines -scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [ - local-scope - assume-screen 10/width, 4/height - # initialize editor with some lines around an empty line - s:text <- new [a -b - -c -d -e] - e:&:editor <- new-editor s, 0/left, 6/right - editor-render screen, e - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - . . - .c . - .d . - ] - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .d . - .e . - .┈┈┈┈┈┈ . - ] - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - . . - .c . - .d . - ] -] - -scenario editor-scrolls-up-on-left-arrow [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 5/width, 4/height - # editor contains >3 lines - s:text <- new [a -b -c -d -e] - e:&:editor <- new-editor s, 0/left, 5/right - editor-render screen, e - # position cursor at top of second page - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .c . - .d . - .e . - ] - # now try to move left - assume-console [ - press left-arrow - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen scrolls - screen-should-contain [ - . . - .b . - .c . - .d . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] -] - -scenario editor-can-scroll-up-to-start-of-file [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with >3 lines - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # position cursor at top of second page, then try to move up to start of - # text - assume-console [ - press page-down - press up-arrow - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen slides by one line - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # try to move up again - assume-console [ - press up-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen remains unchanged - screen-should-contain [ - . . - .a . - .b . - .c . - ] -] - -# ctrl-f/page-down - render next page if it exists - -scenario editor-can-scroll [ - local-scope - assume-screen 10/width, 4/height - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows next page - screen-should-contain [ - . . - .c . - .d . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -after [ - { - page-down?:bool <- equal c, 6/ctrl-f - break-unless page-down? - old-top:&:duplex-list:char <- get *editor, top-of-screen:offset - - page-down editor - undo-coalesce-tag:num <- copy 0/never - - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - movement?:bool <- not-equal top-of-screen, old-top - return movement?/go-render - } -] - -after [ - { - page-down?:bool <- equal k, 65518/page-down - break-unless page-down? - old-top:&:duplex-list:char <- get *editor, top-of-screen:offset - - page-down editor - undo-coalesce-tag:num <- copy 0/never - - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - movement?:bool <- not-equal top-of-screen, old-top - return movement?/go-render - } -] - -# page-down skips entire wrapped lines, so it can't scroll past lines -# taking up the entire screen -def page-down editor:&:editor -> editor:&:editor [ - local-scope - load-ingredients - # if editor contents don't overflow screen, do nothing - bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset - return-unless bottom-of-screen - # if not, position cursor at final character - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - before-cursor:&:duplex-list:char <- prev bottom-of-screen - *editor <- put *editor, before-cursor:offset, before-cursor - # keep one line in common with previous page - { - last:char <- get *before-cursor, value:offset - newline?:bool <- equal last, 10/newline - break-unless newline?:bool - before-cursor <- prev before-cursor - *editor <- put *editor, before-cursor:offset, before-cursor - } - # move cursor and top-of-screen to start of that line - move-to-start-of-line editor - before-cursor <- get *editor, before-cursor:offset - *editor <- put *editor, top-of-screen:offset, before-cursor -] - -scenario editor-does-not-scroll-past-end [ - local-scope - assume-screen 10/width, 4/height - s:text <- new [a -b] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .┈┈┈┈┈┈┈┈┈┈. - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen remains unmodified - screen-should-contain [ - . . - .a . - .b . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-starts-next-page-at-start-of-wrapped-line [ - local-scope - # screen has 1 line for menu + 3 lines for text - assume-screen 10/width, 4/height - # editor contains a long last line - s:text <- new [a -b -cdefgh] - # editor screen triggers wrap of last line - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - # some part of last line is not displayed - screen-should-contain [ - . . - .a . - .b . - .cde↩ . - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows entire wrapped line - screen-should-contain [ - . . - .cde↩ . - .fgh . - .┈┈┈┈ . - ] -] - -scenario editor-starts-next-page-at-start-of-wrapped-line-2 [ - local-scope - # screen has 1 line for menu + 3 lines for text - assume-screen 10/width, 4/height - # editor contains a very long line that occupies last two lines of screen - # and still has something left over - s:text <- new [a -bcdefgh] - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - # some part of last line is not displayed - screen-should-contain [ - . . - .a . - .bcd↩ . - .efg↩ . - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows entire wrapped line - screen-should-contain [ - . . - .bcd↩ . - .efg↩ . - .h . - ] -] - -# ctrl-b/page-up - render previous page if it exists - -scenario editor-can-scroll-up [ - local-scope - assume-screen 10/width, 4/height - s:text <- new [a -b -c -d] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows next page - screen-should-contain [ - . . - .c . - .d . - .┈┈┈┈┈┈┈┈┈┈. - ] - # scroll back up - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows original page again - screen-should-contain [ - . . - .a . - .b . - .c . - ] -] - -after [ - { - page-up?:bool <- equal c, 2/ctrl-b - break-unless page-up? - old-top:&:duplex-list:char <- get *editor, top-of-screen:offset - - editor <- page-up editor, screen-height - undo-coalesce-tag:num <- copy 0/never - - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - movement?:bool <- not-equal top-of-screen, old-top - return movement?/go-render - } -] - -after [ - { - page-up?:bool <- equal k, 65519/page-up - break-unless page-up? - old-top:&:duplex-list:char <- get *editor, top-of-screen:offset - - editor <- page-up editor, screen-height - undo-coalesce-tag:num <- copy 0/never - - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - movement?:bool <- not-equal top-of-screen, old-top - # don't bother re-rendering if nothing changed. todo: test this - return movement?/go-render - } -] - -def page-up editor:&:editor, screen-height:num -> editor:&:editor [ - local-scope - load-ingredients - max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line - count:num <- copy 0 - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - { - done?:bool <- greater-or-equal count, max - break-if done? - prev:&:duplex-list:char <- before-previous-line top-of-screen, editor - break-unless prev - top-of-screen <- copy prev - *editor <- put *editor, top-of-screen:offset, top-of-screen - count <- add count, 1 - loop - } -] - -scenario editor-can-scroll-up-multiple-pages [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 10/width, 4/height - # initialize editor with 8 lines - s:text <- new [a -b -c -d -e -f -g -h] - e:&:editor <- new-editor s, 0/left, 10/right - editor-render screen, e - screen-should-contain [ - . . - .a . - .b . - .c . - ] - # scroll down two pages - assume-console [ - press page-down - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows third page - screen-should-contain [ - . . - .e . - .f . - .g . - ] - # scroll up - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows second page - screen-should-contain [ - . . - .c . - .d . - .e . - ] - # scroll up again - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows original page again - screen-should-contain [ - . . - .a . - .b . - .c . - ] -] - -scenario editor-can-scroll-up-wrapped-lines [ - local-scope - # screen has 1 line for menu + 5 lines for text - assume-screen 10/width, 6/height - # editor contains a long line in the first page - s:text <- new [a -b -cdefgh -i -j -k -l -m -n -o] - # editor screen triggers wrap of last line - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - # some part of last line is not displayed - screen-should-contain [ - . . - .a . - .b . - .cde↩ . - .fgh . - .i . - ] - # scroll down a page and a line - assume-console [ - press page-down - left-click 5, 0 - press down-arrow - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows entire wrapped line - screen-should-contain [ - . . - .j . - .k . - .l . - .m . - .n . - ] - # now scroll up one page - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - # screen resets - screen-should-contain [ - . . - .b . - .cde↩ . - .fgh . - .i . - .j . - ] -] - -scenario editor-can-scroll-up-wrapped-lines-2 [ - local-scope - # screen has 1 line for menu + 3 lines for text - assume-screen 10/width, 4/height - # editor contains a very long line that occupies last two lines of screen - # and still has something left over - s:text <- new [a -bcdefgh] - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - # some part of last line is not displayed - screen-should-contain [ - . . - .a . - .bcd↩ . - .efg↩ . - ] - # scroll down - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - # screen shows entire wrapped line - screen-should-contain [ - . . - .bcd↩ . - .efg↩ . - .h . - ] - # scroll back up - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - # screen resets - screen-should-contain [ - . . - .a . - .bcd↩ . - .efg↩ . - ] -] - -scenario editor-can-scroll-up-past-nonempty-lines [ - local-scope - assume-screen 10/width, 4/height - # text with empty line in second screen - s:text <- new [axx -bxx -cxx -dxx -exx -fxx -gxx -hxx -] - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - screen-should-contain [ - . . - .axx . - .bxx . - .cxx . - ] - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .cxx . - .dxx . - .exx . - ] - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .exx . - .fxx . - .gxx . - ] - # scroll back up past empty line - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .cxx . - .dxx . - .exx . - ] -] - -scenario editor-can-scroll-up-past-empty-lines [ - local-scope - assume-screen 10/width, 4/height - # text with empty line in second screen - s:text <- new [axy -bxy -cxy - -dxy -exy -fxy -gxy -] - e:&:editor <- new-editor s, 0/left, 4/right - editor-render screen, e - screen-should-contain [ - . . - .axy . - .bxy . - .cxy . - ] - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .cxy . - . . - .dxy . - ] - assume-console [ - press page-down - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .dxy . - .exy . - .fxy . - ] - # scroll back up past empty line - assume-console [ - press page-up - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .cxy . - . . - .dxy . - ] -] diff --git a/edit/005-programming-environment.mu b/edit/005-programming-environment.mu deleted file mode 100644 index 7efa3d47..00000000 --- a/edit/005-programming-environment.mu +++ /dev/null @@ -1,642 +0,0 @@ -## putting the environment together out of editors -# -# Consists of one editor on the left for recipes and one on the right for the -# sandbox. - -def! main [ - local-scope - open-console - env:&:environment <- new-programming-environment 0/filesystem, 0/screen - render-all 0/screen, env, render - event-loop 0/screen, 0/console, env, 0/filesystem - # never gets here -] - -container environment [ - recipes:&:editor - current-sandbox:&:editor - sandbox-in-focus?:bool # false => cursor in recipes; true => cursor in current-sandbox -] - -def new-programming-environment resources:&:resources, screen:&:screen, test-sandbox-editor-contents:text -> result:&:environment [ - local-scope - load-ingredients - width:num <- screen-width screen - result <- new environment:type - # recipe editor on the left - initial-recipe-contents:text <- slurp resources, [lesson/recipes.mu] # ignore errors - divider:num, _ <- divide-with-remainder width, 2 - recipes:&:editor <- new-editor initial-recipe-contents, 0/left, divider/right - # sandbox editor on the right - sandbox-left:num <- add divider, 1 - current-sandbox:&:editor <- new-editor test-sandbox-editor-contents, sandbox-left, width/right - *result <- put *result, recipes:offset, recipes - *result <- put *result, current-sandbox:offset, current-sandbox - *result <- put *result, sandbox-in-focus?:offset, 0/false - -] - -def event-loop screen:&:screen, console:&:console, env:&:environment, resources:&:resources -> screen:&:screen, console:&:console, env:&:environment, resources:&:resources [ - local-scope - load-ingredients - recipes:&:editor <- get *env, recipes:offset - current-sandbox:&:editor <- get *env, current-sandbox:offset - sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset - # if we fall behind we'll stop updating the screen, but then we have to - # render the entire screen when we catch up. - # todo: test this - render-all-on-no-more-events?:bool <- copy 0/false - { - # looping over each (keyboard or touch) event as it occurs - +next-event - e:event, found?:bool, quit?:bool, console <- read-event console - loop-unless found? - break-if quit? # only in tests - trace 10, [app], [next-event] - - # check for global events that will trigger regardless of which editor has focus - { - k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant - break-unless is-keycode? - - } - { - c:char, is-unicode?:bool <- maybe-convert e:event, text:variant - break-unless is-unicode? - - } - # 'touch' event - send to both sides, see what picks it up - { - t:touch-event, is-touch?:bool <- maybe-convert e:event, touch:variant - break-unless is-touch? - # ignore all but 'left-click' events for now - # todo: test this - touch-type:num <- get t, type:offset - is-left-click?:bool <- equal touch-type, 65513/mouse-left - loop-unless is-left-click?, +next-event - click-row:num <- get t, row:offset - click-column:num <- get t, column:offset - # later exceptions for non-editor touches will go here - - # send to both editors - _ <- move-cursor-in-editor screen, recipes, t - sandbox-in-focus?:bool <- move-cursor-in-editor screen, current-sandbox, t - *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus? - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - loop +next-event - } - # 'resize' event - redraw editor - # todo: test this after supporting resize in assume-console - { - r:resize-event, is-resize?:bool <- maybe-convert e:event, resize:variant - break-unless is-resize? - # if more events, we're still resizing; wait until we stop - more-events?:bool <- has-more-events? console - { - break-unless more-events? - render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event - } - { - break-if more-events? - env, screen <- resize screen, env - screen <- render-all screen, env, render-without-moving-cursor - render-all-on-no-more-events? <- copy 0/false # full render done - } - loop +next-event - } - # if it's not global and not a touch event, send to appropriate editor - { - hide-screen screen - sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset - { - break-if sandbox-in-focus? - render?:bool <- handle-keyboard-event screen, recipes, e:event - # refresh screen only if no more events - # if there are more events to process, wait for them to clear up, then make sure you render-all afterward. - more-events?:bool <- has-more-events? console - { - break-unless more-events? - render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event - jump +finish-event - } - { - break-if more-events? - { - break-unless render-all-on-no-more-events? - # no more events, and we have to force render - screen <- render-all screen, env, render - render-all-on-no-more-events? <- copy 0/false - jump +finish-event - } - # no more events, no force render - { - break-unless render? - screen <- render-recipes screen, env, render - jump +finish-event - } - } - } - { - break-unless sandbox-in-focus? - render?:bool <- handle-keyboard-event screen, current-sandbox, e:event - # refresh screen only if no more events - # if there are more events to process, wait for them to clear up, then make sure you render-all afterward. - more-events?:bool <- has-more-events? console - { - break-unless more-events? - render-all-on-no-more-events? <- copy 1/true # no rendering now, full rendering on some future event - jump +finish-event - } - { - break-if more-events? - { - break-unless render-all-on-no-more-events? - # no more events, and we have to force render - screen <- render-all screen, env, render - render-all-on-no-more-events? <- copy 0/false - jump +finish-event - } - # no more events, no force render - { - break-unless render? - screen <- render-sandbox-side screen, env, render - jump +finish-event - } - } - } - +finish-event - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - show-screen screen - } - loop - } -] - -def resize screen:&:screen, env:&:environment -> env:&:environment, screen:&:screen [ - local-scope - load-ingredients - clear-screen screen # update screen dimensions - width:num <- screen-width screen - divider:num, _ <- divide-with-remainder width, 2 - # update recipe editor - recipes:&:editor <- get *env, recipes:offset - right:num <- subtract divider, 1 - *recipes <- put *recipes, right:offset, right - # reset cursor (later we'll try to preserve its position) - *recipes <- put *recipes, cursor-row:offset, 1 - *recipes <- put *recipes, cursor-column:offset, 0 - # update sandbox editor - current-sandbox:&:editor <- get *env, current-sandbox:offset - left:num <- add divider, 1 - *current-sandbox <- put *current-sandbox, left:offset, left - right:num <- subtract width, 1 - *current-sandbox <- put *current-sandbox, right:offset, right - # reset cursor (later we'll try to preserve its position) - *current-sandbox <- put *current-sandbox, cursor-row:offset, 1 - *current-sandbox <- put *current-sandbox, cursor-column:offset, left -] - -# Variant of 'render' that updates cursor-row and cursor-column based on -# before-cursor (rather than the other way around). If before-cursor moves -# off-screen, it resets cursor-row and cursor-column. -def render-without-moving-cursor screen:&:screen, editor:&:editor -> last-row:num, last-column:num, screen:&:screen, editor:&:editor [ - local-scope - load-ingredients - return-unless editor, 1/top, 0/left - left:num <- get *editor, left:offset - screen-height:num <- screen-height screen - right:num <- get *editor, right:offset - curr:&:duplex-list:char <- get *editor, top-of-screen:offset - prev:&:duplex-list:char <- copy curr # just in case curr becomes null and we can't compute prev - curr <- next curr - +render-loop-initialization - color:num <- copy 7/white - row:num <- copy 1/top - column:num <- copy left - # save before-cursor - old-before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - # initialze cursor-row/cursor-column/before-cursor to the top of the screen - # by default - *editor <- put *editor, cursor-row:offset, row - *editor <- put *editor, cursor-column:offset, column - top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset - *editor <- put *editor, before-cursor:offset, top-of-screen - screen <- move-cursor screen, row, column - { - +next-character - break-unless curr - off-screen?:bool <- greater-or-equal row, screen-height - break-if off-screen? - # if we find old-before-cursor still on the new resized screen, update - # editor.cursor-row and editor.cursor-column based on - # old-before-cursor - { - at-cursor?:bool <- equal old-before-cursor, prev - break-unless at-cursor? - *editor <- put *editor, cursor-row:offset, row - *editor <- put *editor, cursor-column:offset, column - *editor <- put *editor, before-cursor:offset, old-before-cursor - } - c:char <- get *curr, value:offset - - { - # newline? move to left rather than 0 - newline?:bool <- equal c, 10/newline - break-unless newline? - # clear rest of line in this window - clear-line-until screen, right - # skip to next line - row <- add row, 1 - column <- copy left - screen <- move-cursor screen, row, column - curr <- next curr - prev <- next prev - loop +next-character - } - { - # at right? wrap. even if there's only one more letter left; we need - # room for clicking on the cursor after it. - at-right?:bool <- equal column, right - break-unless at-right? - # print wrap icon - wrap-icon:char <- copy 8617/loop-back-to-left - print screen, wrap-icon, 245/grey - column <- copy left - row <- add row, 1 - screen <- move-cursor screen, row, column - # don't increment curr - loop +next-character - } - print screen, c, color - curr <- next curr - prev <- next prev - column <- add column, 1 - loop - } - # save first character off-screen - *editor <- put *editor, bottom-of-screen:offset, curr - *editor <- put *editor, bottom:offset, row - return row, column -] - -scenario point-at-multiple-editors [ - local-scope - trace-until 100/app # trace too long - assume-screen 30/width, 5/height - # initialize both halves of screen - assume-resources [ - [lesson/recipes.mu] <- [ - |abc| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [def] # contents of sandbox editor - # focus on both sides - assume-console [ - left-click 1, 1 - left-click 1, 17 - ] - # check cursor column in each - run [ - event-loop screen, console, env, resources - recipes:&:editor <- get *env, recipes:offset - 5:num/raw <- get *recipes, cursor-column:offset - sandbox:&:editor <- get *env, current-sandbox:offset - 7:num/raw <- get *sandbox, cursor-column:offset - ] - memory-should-contain [ - 5 <- 1 - 7 <- 17 - ] -] - -scenario edit-multiple-editors [ - local-scope - trace-until 100/app # trace too long - assume-screen 30/width, 5/height - # initialize both halves of screen - assume-resources [ - [lesson/recipes.mu] <- [ - |abc| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [def] # contents of sandbox - render-all screen, env, render - # type one letter in each of them - assume-console [ - left-click 1, 1 - type [0] - left-click 1, 17 - type [1] - ] - run [ - event-loop screen, console, env, resources - recipes:&:editor <- get *env, recipes:offset - 5:num/raw <- get *recipes, cursor-column:offset - sandbox:&:editor <- get *env, current-sandbox:offset - 7:num/raw <- get *sandbox, cursor-column:offset - ] - screen-should-contain [ - . run (F4) . # this line has a different background, but we don't test that yet - .a0bc ┊d1ef . - . ┊──────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - memory-should-contain [ - 5 <- 2 # cursor column of recipe editor - 7 <- 18 # cursor column of sandbox editor - ] - # show the cursor at the right window - run [ - cursor:char <- copy 9251/␣ - print screen, cursor - ] - screen-should-contain [ - . run (F4) . - .a0bc ┊d1␣f . - . ┊──────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario editor-in-focus-keeps-cursor [ - local-scope - trace-until 100/app # trace too long - assume-screen 30/width, 5/height - assume-resources [ - [lesson/recipes.mu] <- [ - |abc| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [def] - render-all screen, env, render - # initialize programming environment and highlight cursor - assume-console [] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # is cursor at the right place? - screen-should-contain [ - . run (F4) . - .␣bc ┊def . - . ┊──────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - # now try typing a letter - assume-console [ - type [z] - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # cursor should still be right - screen-should-contain [ - . run (F4) . - .z␣bc ┊def . - . ┊──────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario backspace-in-sandbox-editor-joins-lines [ - local-scope - trace-until 100/app # trace too long - assume-screen 30/width, 5/height - assume-resources [ - ] - # initialize sandbox side with two lines - test-sandbox-editor-contents:text <- new [abc -def] - env:&:environment <- new-programming-environment resources, screen, test-sandbox-editor-contents - render-all screen, env, render - screen-should-contain [ - . run (F4) . - . ┊abc . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊def . - . ┊──────────────. - . ┊ . - ] - # position cursor at start of second line and hit backspace - assume-console [ - left-click 2, 16 - press backspace - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # cursor moves to end of old line - screen-should-contain [ - . run (F4) . - . ┊abc␣ef . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊──────────────. - . ┊ . - ] -] - -def render-all screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ - local-scope - load-ingredients - trace 10, [app], [render all] - hide-screen screen - # top menu - trace 11, [app], [render top menu] - width:num <- screen-width screen - draw-horizontal screen, 0, 0/left, width, 32/space, 0/black, 238/grey - button-start:num <- subtract width, 20 - button-on-screen?:bool <- greater-or-equal button-start, 0 - assert button-on-screen?, [screen too narrow for menu] - screen <- move-cursor screen, 0/row, button-start - print screen, [ run (F4) ], 255/white, 161/reddish - # dotted line down the middle - trace 11, [app], [render divider] - divider:num, _ <- divide-with-remainder width, 2 - height:num <- screen-height screen - draw-vertical screen, divider, 1/top, height, 9482/vertical-dotted - # - screen <- render-recipes screen, env, render-editor - screen <- render-sandbox-side screen, env, render-editor - - # - recipes:&:editor <- get *env, recipes:offset - current-sandbox:&:editor <- get *env, current-sandbox:offset - sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - # - show-screen screen -] - -def render-recipes screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ - local-scope - load-ingredients - trace 11, [app], [render recipes] - recipes:&:editor <- get *env, recipes:offset - # render recipes - left:num <- get *recipes, left:offset - right:num <- get *recipes, right:offset - row:num, column:num, screen <- call render-editor, screen, recipes - clear-line-until screen, right - row <- add row, 1 - - # draw dotted line after recipes - draw-horizontal screen, row, left, right, 9480/horizontal-dotted - row <- add row, 1 - clear-screen-from screen, row, left, left, right -] - -# replaced in a later layer -def render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ - local-scope - load-ingredients - current-sandbox:&:editor <- get *env, current-sandbox:offset - left:num <- get *current-sandbox, left:offset - right:num <- get *current-sandbox, right:offset - row:num, column:num, screen, current-sandbox <- call render-editor, screen, current-sandbox - clear-line-until screen, right - row <- add row, 1 - # draw solid line after code (you'll see why in later layers) - draw-horizontal screen, row, left, right - row <- add row, 1 - clear-screen-from screen, row, left, left, right -] - -def update-cursor screen:&:screen, recipes:&:editor, current-sandbox:&:editor, sandbox-in-focus?:bool, env:&:environment -> screen:&:screen [ - local-scope - load-ingredients - - { - break-if sandbox-in-focus? - cursor-row:num <- get *recipes, cursor-row:offset - cursor-column:num <- get *recipes, cursor-column:offset - } - { - break-unless sandbox-in-focus? - cursor-row:num <- get *current-sandbox, cursor-row:offset - cursor-column:num <- get *current-sandbox, cursor-column:offset - } - screen <- move-cursor screen, cursor-row, cursor-column -] - -# like 'render' for texts, but with colorization for comments like in the editor -def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [ - local-scope - load-ingredients - return-unless s - color:num <- copy 7/white - column:num <- copy left - screen <- move-cursor screen, row, column - screen-height:num <- screen-height screen - i:num <- copy 0 - len:num <- length *s - { - +next-character - done?:bool <- greater-or-equal i, len - break-if done? - done? <- greater-or-equal row, screen-height - break-if done? - c:char <- index *s, i - # only line different from render - { - # at right? wrap. - at-right?:bool <- equal column, right - break-unless at-right? - # print wrap icon - wrap-icon:char <- copy 8617/loop-back-to-left - print screen, wrap-icon, 245/grey - column <- copy left - row <- add row, 1 - screen <- move-cursor screen, row, column - loop +next-character # retry i - } - i <- add i, 1 - { - # newline? move to left rather than 0 - newline?:bool <- equal c, 10/newline - break-unless newline? - # clear rest of line in this window - { - done?:bool <- greater-than column, right - break-if done? - space:char <- copy 32/space - print screen, space - column <- add column, 1 - loop - } - row <- add row, 1 - column <- copy left - screen <- move-cursor screen, row, column - loop +next-character - } - print screen, c, color - column <- add column, 1 - loop - } - was-at-left?:bool <- equal column, left - clear-line-until screen, right - { - break-if was-at-left? - row <- add row, 1 - } - move-cursor screen, row, left -] - -# ctrl-l - redraw screen (just in case it printed junk somehow) - -after [ - { - redraw-screen?:bool <- equal c, 12/ctrl-l - break-unless redraw-screen? - screen <- render-all screen, env:&:environment, render - sync-screen screen - loop +next-event - } -] - -# ctrl-n - switch focus -# todo: test this - -after [ - { - switch-side?:bool <- equal c, 14/ctrl-n - break-unless switch-side? - sandbox-in-focus?:bool <- get *env, sandbox-in-focus?:offset - sandbox-in-focus? <- not sandbox-in-focus? - *env <- put *env, sandbox-in-focus?:offset, sandbox-in-focus? - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - loop +next-event - } -] - -## helpers - -def draw-vertical screen:&:screen, col:num, y:num, bottom:num -> screen:&:screen [ - local-scope - load-ingredients - style:char, style-found?:bool <- next-ingredient - { - break-if style-found? - style <- copy 9474/vertical - } - color:num, color-found?:bool <- next-ingredient - { - # default color to white - break-if color-found? - color <- copy 245/grey - } - { - continue?:bool <- lesser-than y, bottom - break-unless continue? - screen <- move-cursor screen, y, col - print screen, style, color - y <- add y, 1 - loop - } -] diff --git a/edit/005-sandbox.mu b/edit/005-sandbox.mu new file mode 100644 index 00000000..dac50c83 --- /dev/null +++ b/edit/005-sandbox.mu @@ -0,0 +1,1213 @@ +## running code from the editor and creating sandboxes +# +# Running code in the sandbox editor prepends its contents to a list of +# (non-editable) sandboxes below the editor, showing the result and maybe a +# few other things (later layers). +# +# This layer draws the menubar buttons in non-editable sandboxes but they +# don't do anything yet. Later layers implement each button. + +def! main [ + local-scope + open-console + env:&:environment <- new-programming-environment 0/filesystem, 0/screen + env <- restore-sandboxes env + render-all 0/screen, env, render + event-loop 0/screen, 0/console, env, 0/filesystem + # never gets here +] + +container environment [ + sandbox:&:sandbox # list of sandboxes, from top to bottom. TODO: switch to &:list:sandbox + render-from:num + number-of-sandboxes:num +] + +after [ + *result <- put *result, render-from:offset, -1 +] + +container sandbox [ + data:text + response:text + # coordinates to track clicks + # constraint: will be 0 for sandboxes at positions before env.render-from + starting-row-on-screen:num + code-ending-row-on-screen:num # past end of code + screen:&:screen # prints in the sandbox go here + next-sandbox:&:sandbox +] + +scenario run-and-show-results [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # recipe editor is empty + assume-resources [ + ] + # sandbox editor contains an instruction without storing outputs + env:&:environment <- new-programming-environment resources, screen, [divide-with-remainder 11, 3] + # run the code in the editors + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that screen prints the results + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊divide-with-remainder 11, 3 . + . ┊3 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + screen-should-contain-in-color 7/white, [ + . . + . . + . . + . . + . divide-with-remainder 11, 3 . + . . + . . + . . + . . + ] + screen-should-contain-in-color 245/grey, [ + . . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + . ┊3 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # sandbox menu in reverse video + screen-should-contain-in-color 232/black, [ + . . + . . + . . + . 0 edit copy delete . + ] + # run another command + assume-console [ + left-click 1, 80 + type [add 2, 2] + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that screen prints both sandboxes + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊divide-with-remainder 11, 3 . + . ┊3 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +after [ + # F4? load all code and run all sandboxes. + { + do-run?:bool <- equal k, 65532/F4 + break-unless do-run? + screen <- update-status screen, [running... ], 245/grey + error?:bool <- run-sandboxes env, resources, screen + # F4 might update warnings and results on both sides + screen <- render-all screen, env, render + { + break-if error? + screen <- update-status screen, [ ], 245/grey + } + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + loop +next-event + } +] + +def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ + local-scope + load-ingredients + errors-found?:bool <- update-recipes env, resources, screen + return-if errors-found? + # check contents of right editor (sandbox) + + current-sandbox:&:editor <- get *env, current-sandbox:offset + { + sandbox-contents:text <- editor-contents current-sandbox + break-unless sandbox-contents + # if contents exist, first save them + # run them and turn them into a new sandbox + new-sandbox:&:sandbox <- new sandbox:type + *new-sandbox <- put *new-sandbox, data:offset, sandbox-contents + # push to head of sandbox list + dest:&:sandbox <- get *env, sandbox:offset + *new-sandbox <- put *new-sandbox, next-sandbox:offset, dest + *env <- put *env, sandbox:offset, new-sandbox + # update sandbox count + sandbox-count:num <- get *env, number-of-sandboxes:offset + sandbox-count <- add sandbox-count, 1 + *env <- put *env, number-of-sandboxes:offset, sandbox-count + # clear sandbox editor + init:&:duplex-list:char <- push 167/§, 0/tail + *current-sandbox <- put *current-sandbox, data:offset, init + *current-sandbox <- put *current-sandbox, top-of-screen:offset, init + } + # save all sandboxes before running, just in case we die when running + save-sandboxes env, resources + # run all sandboxes + curr:&:sandbox <- get *env, sandbox:offset + idx:num <- copy 0 + { + break-unless curr + curr <- update-sandbox curr, env, idx + curr <- get *curr, next-sandbox:offset + idx <- add idx, 1 + loop + } + +] + +# load code from disk +# replaced in a later layer (whereupon errors-found? will actually be set) +def update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ + local-scope + load-ingredients + recipes:&:editor <- get *env, recipes:offset + in:text <- editor-contents recipes + resources <- dump resources, [lesson/recipes.mu], in + reload in + errors-found? <- copy 0/false +] + +# replaced in a later layer +def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ + local-scope + load-ingredients + data:text <- get *sandbox, data:offset + response:text, _, fake-screen:&:screen <- run-sandboxed data + *sandbox <- put *sandbox, response:offset, response + *sandbox <- put *sandbox, screen:offset, fake-screen +] + +def update-status screen:&:screen, msg:text, color:num -> screen:&:screen [ + local-scope + load-ingredients + screen <- move-cursor screen, 0, 2 + screen <- print screen, msg, color, 238/grey/background +] + +def save-sandboxes env:&:environment, resources:&:resources -> resources:&:resources [ + local-scope + load-ingredients + current-sandbox:&:editor <- get *env, current-sandbox:offset + # first clear previous versions, in case we deleted some sandbox + $system [rm lesson/[0-9]* >/dev/null 2>/dev/null] # some shells can't handle '>&' + curr:&:sandbox <- get *env, sandbox:offset + idx:num <- copy 0 + { + break-unless curr + data:text <- get *curr, data:offset + filename:text <- append [lesson/], idx + resources <- dump resources, filename, data + + idx <- add idx, 1 + curr <- get *curr, next-sandbox:offset + loop + } +] + +def! render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ + local-scope + load-ingredients + trace 11, [app], [render sandbox side] + current-sandbox:&:editor <- get *env, current-sandbox:offset + row:num, column:num <- copy 1, 0 + left:num <- get *current-sandbox, left:offset + right:num <- get *current-sandbox, right:offset + # render sandbox editor + render-from:num <- get *env, render-from:offset + { + render-current-sandbox?:bool <- equal render-from, -1 + break-unless render-current-sandbox? + row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox + clear-screen-from screen, row, column, left, right + row <- add row, 1 + } + # render sandboxes + draw-horizontal screen, row, left, right + sandbox:&:sandbox <- get *env, sandbox:offset + row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from + clear-rest-of-screen screen, row, left, right +] + +def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, row:num, render-from:num, idx:num -> row:num, screen:&:screen, sandbox:&:sandbox [ + local-scope + load-ingredients + return-unless sandbox + screen-height:num <- screen-height screen + at-bottom?:bool <- greater-or-equal row, screen-height + return-if at-bottom?:bool + hidden?:bool <- lesser-than idx, render-from + { + break-if hidden? + # render sandbox menu + row <- add row, 1 + screen <- move-cursor screen, row, left + screen <- render-sandbox-menu screen, idx, left, right + # save menu row so we can detect clicks to it later + *sandbox <- put *sandbox, starting-row-on-screen:offset, row + # render sandbox contents + row <- add row, 1 + screen <- move-cursor screen, row, left + sandbox-data:text <- get *sandbox, data:offset + row, screen <- render-code screen, sandbox-data, left, right, row + *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row + # render sandbox warnings, screen or response, in that order + sandbox-response:text <- get *sandbox, response:offset + + { + sandbox-screen:&:screen <- get *sandbox, screen:offset + empty-screen?:bool <- fake-screen-is-empty? sandbox-screen + break-if empty-screen? + row, screen <- render-screen screen, sandbox-screen, left, right, row + } + { + break-unless empty-screen? + + row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row + } + +render-sandbox-end + at-bottom?:bool <- greater-or-equal row, screen-height + return-if at-bottom? + # draw solid line after sandbox + draw-horizontal screen, row, left, right + } + # if hidden, reset row attributes + { + break-unless hidden? + *sandbox <- put *sandbox, starting-row-on-screen:offset, 0 + *sandbox <- put *sandbox, code-ending-row-on-screen:offset, 0 + + } + # draw next sandbox + next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset + next-idx:num <- add idx, 1 + row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx +] + +def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [ + local-scope + load-ingredients + move-cursor-to-column screen, left + edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num <- sandbox-menu-columns left, right + print screen, sandbox-index, 232/dark-grey, 245/grey + start-buttons:num <- subtract edit-button-left, 1 + clear-line-until screen, start-buttons, 245/grey + print screen, [edit], 232/black, 94/background-orange + clear-line-until screen, edit-button-right, 94/background-orange + _, col:num <- cursor-position screen + at-start-of-copy-button?:bool <- equal col, copy-button-left + assert at-start-of-copy-button?, [aaa] + print screen, [copy], 232/black, 58/background-green + clear-line-until screen, copy-button-right, 58/background-green + _, col:num <- cursor-position screen + at-start-of-delete-button?:bool <- equal col, delete-button-left + assert at-start-of-delete-button?, [bbb] + print screen, [delete], 232/black, 52/background-red + clear-line-until screen, right, 52/background-red +] + +# divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons +# delete-button-right == right +# all left/right pairs are inclusive +def sandbox-menu-columns left:num, right:num -> edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num [ + local-scope + load-ingredients + start-buttons:num <- add left, 4/space-for-sandbox-index + buttons-space:num <- subtract right, start-buttons + button-width:num <- divide-with-remainder buttons-space, 3 # integer division + buttons-wide-enough?:bool <- greater-or-equal button-width, 8 + assert buttons-wide-enough?, [sandbox must be at least 30 or so characters wide] + edit-button-left:num <- copy start-buttons + copy-button-left:num <- add start-buttons, button-width + edit-button-right:num <- subtract copy-button-left, 1 + delete-button-left:num <- subtract right, button-width + copy-button-right:num <- subtract delete-button-left, 1 +] + +# print a text 's' to 'editor' in 'color' starting at 'row' +# clear rest of last line, move cursor to next line +def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num -> row:num, screen:&:screen [ + local-scope + load-ingredients + return-unless s + column:num <- copy left + screen <- move-cursor screen, row, column + screen-height:num <- screen-height screen + i:num <- copy 0 + len:num <- length *s + { + +next-character + done?:bool <- greater-or-equal i, len + break-if done? + done? <- greater-or-equal row, screen-height + break-if done? + c:char <- index *s, i + { + # at right? wrap. + at-right?:bool <- equal column, right + break-unless at-right? + # print wrap icon + wrap-icon:char <- copy 8617/loop-back-to-left + print screen, wrap-icon, 245/grey + column <- copy left + row <- add row, 1 + screen <- move-cursor screen, row, column + loop +next-character # retry i + } + i <- add i, 1 + { + # newline? move to left rather than 0 + newline?:bool <- equal c, 10/newline + break-unless newline? + # clear rest of line in this window + { + done?:bool <- greater-than column, right + break-if done? + space:char <- copy 32/space + print screen, space + column <- add column, 1 + loop + } + row <- add row, 1 + column <- copy left + screen <- move-cursor screen, row, column + loop +next-character + } + print screen, c, color + column <- add column, 1 + loop + } + was-at-left?:bool <- equal column, left + clear-line-until screen, right + { + break-if was-at-left? + row <- add row, 1 + } + move-cursor screen, row, left +] + +# assumes programming environment has no sandboxes; restores them from previous session +def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environment [ + local-scope + load-ingredients + # read all scenarios, pushing them to end of a list of scenarios + idx:num <- copy 0 + curr:&:sandbox <- copy 0 + prev:&:sandbox <- copy 0 + { + filename:text <- append [lesson/], idx + contents:text <- slurp resources, filename + break-unless contents # stop at first error; assuming file didn't exist + # todo: handle empty sandbox + # create new sandbox for file + curr <- new sandbox:type + *curr <- put *curr, data:offset, contents + + { + break-if idx + *env <- put *env, sandbox:offset, curr + } + { + break-unless idx + *prev <- put *prev, next-sandbox:offset, curr + } + idx <- add idx, 1 + prev <- copy curr + loop + } + # update sandbox count + *env <- put *env, number-of-sandboxes:offset, idx +] + +# print the fake sandbox screen to 'screen' with appropriate delimiters +# leave cursor at start of next line +def render-screen screen:&:screen, sandbox-screen:&:screen, left:num, right:num, row:num -> row:num, screen:&:screen [ + local-scope + load-ingredients + return-unless sandbox-screen + # print 'screen:' + row <- render-text screen, [screen:], left, right, 245/grey, row + screen <- move-cursor screen, row, left + # start printing sandbox-screen + column:num <- copy left + s-width:num <- screen-width sandbox-screen + s-height:num <- screen-height sandbox-screen + buf:&:@:screen-cell <- get *sandbox-screen, data:offset + stop-printing:num <- add left, s-width, 3 + max-column:num <- min stop-printing, right + i:num <- copy 0 + len:num <- length *buf + screen-height:num <- screen-height screen + { + done?:bool <- greater-or-equal i, len + break-if done? + done? <- greater-or-equal row, screen-height + break-if done? + column <- copy left + screen <- move-cursor screen, row, column + # initial leader for each row: two spaces and a '.' + space:char <- copy 32/space + print screen, space, 245/grey + print screen, space, 245/grey + full-stop:char <- copy 46/period + print screen, full-stop, 245/grey + column <- add left, 3 + { + # print row + row-done?:bool <- greater-or-equal column, max-column + break-if row-done? + curr:screen-cell <- index *buf, i + c:char <- get curr, contents:offset + color:num <- get curr, color:offset + { + # damp whites down to grey + white?:bool <- equal color, 7/white + break-unless white? + color <- copy 245/grey + } + print screen, c, color + column <- add column, 1 + i <- add i, 1 + loop + } + # print final '.' + print screen, full-stop, 245/grey + column <- add column, 1 + { + # clear rest of current line + line-done?:bool <- greater-than column, right + break-if line-done? + print screen, space + column <- add column, 1 + loop + } + row <- add row, 1 + loop + } +] + +scenario run-updates-results [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 12/height + # define a recipe (no indent for the 'add' line below so column numbers are more obvious) + assume-resources [ + [lesson/recipes.mu] <- [ + || + |recipe foo [| + | local-scope| + | z:num <- add 2, 2| + | reply z| + |]| + ] + ] + # sandbox editor contains an instruction without storing outputs + env:&:environment <- new-programming-environment resources, screen, [foo] # contents of sandbox editor + # run the code in the editors + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .recipe foo [ ┊─────────────────────────────────────────────────. + . local-scope ┊0 edit copy delete . + . z:num <- add 2, 2 ┊foo . + . reply z ┊4 . + .] ┊─────────────────────────────────────────────────. + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + # make a change (incrementing one of the args to 'add'), then rerun + assume-console [ + left-click 4, 28 # one past the value of the second arg + press backspace + type [3] + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that screen updates the result on the right + screen-should-contain [ + . run (F4) . + . ┊ . + .recipe foo [ ┊─────────────────────────────────────────────────. + . local-scope ┊0 edit copy delete . + . z:num <- add 2, 3 ┊foo . + . reply z ┊5 . + .] ┊─────────────────────────────────────────────────. + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-instruction-manages-screen-per-sandbox [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 20/height + # empty recipes + assume-resources [ + ] + # sandbox editor contains an instruction + env:&:environment <- new-programming-environment resources, screen, [print screen, 4] # contents of sandbox editor + # run the code in the editor + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that it prints a little toy screen + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊print screen, 4 . + . ┊screen: . + . ┊ .4 . . + . ┊ . . . + . ┊ . . . + . ┊ . . . + . ┊ . . . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +def editor-contents editor:&:editor -> result:text [ + local-scope + load-ingredients + buf:&:buffer <- new-buffer 80 + curr:&:duplex-list:char <- get *editor, data:offset + # skip § sentinel + assert curr, [editor without data is illegal; must have at least a sentinel] + curr <- next curr + return-unless curr, 0 + { + break-unless curr + c:char <- get *curr, value:offset + buf <- append buf, c + curr <- next curr + loop + } + result <- buffer-to-array buf +] + +scenario editor-provides-edited-contents [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + assume-console [ + left-click 1, 2 + type [def] + ] + run [ + editor-event-loop screen, console, e + s:text <- editor-contents e + 1:@:char/raw <- copy *s + ] + memory-should-contain [ + 1:array:character <- [abdefc] + ] +] + +# keep the bottom of recipes from scrolling off the screen + +scenario scrolling-down-past-bottom-of-recipe-editor [ + local-scope + trace-until 100/app + assume-screen 100/width, 10/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + assume-console [ + press enter + press down-arrow + ] + event-loop screen, console, env, resources + # no scroll + screen-should-contain [ + . run (F4) . + . ┊ . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario cursor-down-in-recipe-editor [ + local-scope + trace-until 100/app + assume-screen 100/width, 10/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + assume-console [ + press enter + press up-arrow + press down-arrow # while cursor isn't at bottom + ] + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + # cursor moves back to bottom + screen-should-contain [ + . run (F4) . + . ┊ . + .␣ ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +# we'll not use the recipe-editor's 'bottom' element directly, because later +# layers will add other stuff to the left side below the editor (error messages) + +container environment [ + recipe-bottom:num +] + +after [ + *env <- put *env, recipe-bottom:offset, row +] + +after [ + { + break-if sandbox-in-focus? + down-arrow?:bool <- equal k, 65516/down-arrow + break-unless down-arrow? + recipe-editor:&:editor <- get *env, recipes:offset + recipe-cursor-row:num <- get *recipe-editor, cursor-row:offset + recipe-editor-bottom:num <- get *recipe-editor, bottom:offset + at-bottom-of-editor?:bool <- greater-or-equal recipe-cursor-row, recipe-editor-bottom + break-unless at-bottom-of-editor? + more-to-scroll?:bool <- more-to-scroll? env, screen + break-if more-to-scroll? + loop +next-event + } + { + break-if sandbox-in-focus? + page-down?:bool <- equal k, 65518/page-down + break-unless page-down? + more-to-scroll?:bool <- more-to-scroll? env, screen + break-if more-to-scroll? + loop +next-event + } +] + +after [ + { + break-if sandbox-in-focus? + page-down?:bool <- equal k, 6/ctrl-f + break-unless page-down? + more-to-scroll?:bool <- more-to-scroll? env, screen + break-if more-to-scroll? + loop +next-event + } +] + +def more-to-scroll? env:&:environment, screen:&:screen -> result:bool [ + local-scope + load-ingredients + recipe-bottom:num <- get *env, recipe-bottom:offset + height:num <- screen-height screen + result <- greater-or-equal recipe-bottom, height +] + +scenario scrolling-down-past-bottom-of-recipe-editor-2 [ + local-scope + trace-until 100/app + assume-screen 100/width, 10/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + assume-console [ + # add a line + press enter + # cursor back to top line + press up-arrow + # try to scroll + press page-down # or ctrl-f + ] + event-loop screen, console, env, resources + # no scroll, and cursor remains at top line + screen-should-contain [ + . run (F4) . + . ┊ . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario scrolling-down-past-bottom-of-recipe-editor-3 [ + local-scope + trace-until 100/app + assume-screen 100/width, 10/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [ab +cd] + render-all screen, env, render + assume-console [ + # add a line + press enter + # switch to sandbox + press ctrl-n + # move cursor + press down-arrow + ] + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + # no scroll on recipe side, cursor moves on sandbox side + screen-should-contain [ + . run (F4) . + . ┊ab . + . ┊␣d . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +# scrolling through sandboxes + +scenario scrolling-down-past-bottom-of-sandbox-editor [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 2, 2] + render-all screen, env, render + assume-console [ + # create a sandbox + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + ] + # switch to sandbox window and hit 'page-down' + assume-console [ + press ctrl-n + press page-down + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # sandbox editor hidden; first sandbox displayed + # cursor moves to first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣ edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # hit 'page-up' + assume-console [ + press page-up + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # sandbox editor displays again, cursor is in editor + screen-should-contain [ + . run (F4) . + . ┊␣ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + ] +] + +# page-down on sandbox side updates render-from to scroll sandboxes +after [ + { + break-unless sandbox-in-focus? + page-down?:bool <- equal k, 65518/page-down + break-unless page-down? + sandbox:&:sandbox <- get *env, sandbox:offset + break-unless sandbox + # slide down if possible + { + render-from:num <- get *env, render-from:offset + number-of-sandboxes:num <- get *env, number-of-sandboxes:offset + max:num <- subtract number-of-sandboxes, 1 + at-end?:bool <- greater-or-equal render-from, max + jump-if at-end?, +finish-event # render nothing + render-from <- add render-from, 1 + *env <- put *env, render-from:offset, render-from + } + hide-screen screen + screen <- render-sandbox-side screen, env, render + show-screen screen + jump +finish-event + } +] + +# update-cursor takes render-from into account +after [ + { + break-unless sandbox-in-focus? + render-from:num <- get *env, render-from:offset + scrolling?:bool <- greater-or-equal render-from, 0 + break-unless scrolling? + cursor-column:num <- get *current-sandbox, left:offset + screen <- move-cursor screen, 2/row, cursor-column # highlighted sandbox will always start at row 2 + return + } +] + +# 'page-up' on sandbox side is like 'page-down': updates render-from when necessary +after [ + { + break-unless sandbox-in-focus? + page-up?:bool <- equal k, 65519/page-up + break-unless page-up? + render-from:num <- get *env, render-from:offset + at-beginning?:bool <- equal render-from, -1 + break-if at-beginning? + render-from <- subtract render-from, 1 + *env <- put *env, render-from:offset, render-from + hide-screen screen + screen <- render-sandbox-side screen, env, render + show-screen screen + jump +finish-event + } +] + +# sandbox belonging to 'env' whose next-sandbox is 'in' +# return 0 if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox +def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [ + local-scope + load-ingredients + curr:&:sandbox <- get *env, sandbox:offset + return-unless curr, 0/nil + next:&:sandbox <- get *curr, next-sandbox:offset + { + return-unless next, 0/nil + found?:bool <- equal next, in + break-if found? + curr <- copy next + next <- get *curr, next-sandbox:offset + loop + } + return curr +] + +scenario scrolling-down-past-bottom-on-recipe-side [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize sandbox side and create a sandbox + assume-resources [ + [lesson/recipes.mu] <- [ + || # file containing just a newline + ] + ] + # create a sandbox + env:&:environment <- new-programming-environment resources, screen, [add 2, 2] + render-all screen, env, render + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + # hit 'down' in recipe editor + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # cursor doesn't move when the end is already on-screen + screen-should-contain [ + . run (F4) . + .␣ ┊ . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 2, 2 . + ] +] + +scenario scrolling-through-multiple-sandboxes [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + ] + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + screen-should-contain [ + . run (F4) . + . ┊␣ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # hit 'page-down' + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # sandbox editor hidden; first sandbox displayed + # cursor moves to first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣ edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # hit 'page-down' again + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # just second sandbox displayed + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # hit 'page-down' again + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # no change + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # hit 'page-up' + assume-console [ + press page-up + ] + run [ + event-loop screen, console, env, resources + ] + # back to displaying both sandboxes without editor + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # hit 'page-up' again + assume-console [ + press page-up + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # back to displaying both sandboxes as well as editor + screen-should-contain [ + . run (F4) . + . ┊␣ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # hit 'page-up' again + assume-console [ + press page-up + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # no change + screen-should-contain [ + . run (F4) . + . ┊␣ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] +] + +scenario scrolling-manages-sandbox-index-correctly [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create a sandbox + assume-console [ + press ctrl-n + type [add 1, 1] + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # sandbox editor hidden; first sandbox displayed + # cursor moves to first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # hit 'page-up' again + assume-console [ + press page-up + ] + run [ + event-loop screen, console, env, resources + ] + # back to displaying both sandboxes as well as editor + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # hit 'page-down' + assume-console [ + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # sandbox editor hidden; first sandbox displayed + # cursor moves to first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] diff --git a/edit/006-sandbox-copy.mu b/edit/006-sandbox-copy.mu new file mode 100644 index 00000000..9df5e625 --- /dev/null +++ b/edit/006-sandbox-copy.mu @@ -0,0 +1,279 @@ +## the 'copy' button makes it easy to duplicate a sandbox, and thence to +## see code operate in multiple situations + +scenario copy-a-sandbox-to-editor [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # empty recipes + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # click at left edge of 'copy' button + assume-console [ + left-click 3, 69 + ] + run [ + event-loop screen, console, env, resources + ] + # it copies into editor + screen-should-contain [ + . run (F4) . + . ┊add 1, 1 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # cursor should be in the right place + assume-console [ + type [0] + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊0add 1, 1 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario copy-a-sandbox-to-editor-2 [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # empty recipes + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # click at right edge of 'copy' button (just before 'delete') + assume-console [ + left-click 3, 84 + ] + run [ + event-loop screen, console, env, resources + ] + # it copies into editor + screen-should-contain [ + . run (F4) . + . ┊add 1, 1 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # cursor should be in the right place + assume-console [ + type [0] + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊0add 1, 1 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +after [ + # support 'copy' button + { + copy?:bool <- should-attempt-copy? click-row, click-column, env + break-unless copy? + copy?, env <- try-copy-sandbox click-row, env + break-unless copy? + hide-screen screen + screen <- render-sandbox-side screen, env, render + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + show-screen screen + loop +next-event + } +] + +# some preconditions for attempting to copy a sandbox +def should-attempt-copy? click-row:num, click-column:num, env:&:environment -> result:bool [ + local-scope + load-ingredients + # are we below the sandbox editor? + click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env + return-unless click-sandbox-area?, 0/false + # narrower, is the click in the columns spanning the 'copy' button? + first-sandbox:&:editor <- get *env, current-sandbox:offset + assert first-sandbox, [!!] + sandbox-left-margin:num <- get *first-sandbox, left:offset + sandbox-right-margin:num <- get *first-sandbox, right:offset + _, _, copy-button-left:num, copy-button-right:num, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin + copy-button-vertical-area?:bool <- within-range? click-column, copy-button-left, copy-button-right + return-unless copy-button-vertical-area?, 0/false + # finally, is sandbox editor empty? + current-sandbox:&:editor <- get *env, current-sandbox:offset + result <- empty-editor? current-sandbox +] + +def try-copy-sandbox click-row:num, env:&:environment -> clicked-on-copy-button?:bool, env:&:environment [ + local-scope + load-ingredients + # identify the sandbox to copy, if the click was actually on the 'copy' button + sandbox:&:sandbox <- find-sandbox env, click-row + return-unless sandbox, 0/false + clicked-on-copy-button? <- copy 1/true + text:text <- get *sandbox, data:offset + current-sandbox:&:editor <- get *env, current-sandbox:offset + current-sandbox <- insert-text current-sandbox, text + # reset scroll + *env <- put *env, render-from:offset, -1 + # position cursor in sandbox editor + *env <- put *env, sandbox-in-focus?:offset, 1/true +] + +def find-sandbox env:&:environment, click-row:num -> result:&:sandbox [ + local-scope + load-ingredients + curr-sandbox:&:sandbox <- get *env, sandbox:offset + { + break-unless curr-sandbox + start:num <- get *curr-sandbox, starting-row-on-screen:offset + found?:bool <- equal click-row, start + return-if found?, curr-sandbox + curr-sandbox <- get *curr-sandbox, next-sandbox:offset + loop + } + return 0/not-found +] + +def click-on-sandbox-area? click-row:num, click-column:num, env:&:environment -> result:bool [ + local-scope + load-ingredients + current-sandbox:&:editor <- get *env, current-sandbox:offset + sandbox-left-margin:num <- get *current-sandbox, left:offset + on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin + return-unless on-sandbox-side?, 0/false + first-sandbox:&:sandbox <- get *env, sandbox:offset + return-unless first-sandbox, 0/false + first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset + result <- greater-or-equal click-row, first-sandbox-begins +] + +def empty-editor? editor:&:editor -> result:bool [ + local-scope + load-ingredients + head:&:duplex-list:char <- get *editor, data:offset + first:&:duplex-list:char <- next head + result <- not first +] + +def within-range? x:num, low:num, high:num -> result:bool [ + local-scope + load-ingredients + not-too-far-left?:bool <- greater-or-equal x, low + not-too-far-right?:bool <- lesser-or-equal x, high + result <- and not-too-far-left? not-too-far-right? +] + +scenario copy-fails-if-sandbox-editor-not-empty [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # empty recipes + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # type something into the sandbox editor, then click on the 'copy' button + assume-console [ + left-click 2, 70 # put cursor in sandbox editor + type [0] # type something + left-click 3, 70 # click 'copy' button + ] + run [ + event-loop screen, console, env, resources + ] + # copy doesn't happen + screen-should-contain [ + . run (F4) . + . ┊0 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊01 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] diff --git a/edit/006-sandbox.mu b/edit/006-sandbox.mu deleted file mode 100644 index dac50c83..00000000 --- a/edit/006-sandbox.mu +++ /dev/null @@ -1,1213 +0,0 @@ -## running code from the editor and creating sandboxes -# -# Running code in the sandbox editor prepends its contents to a list of -# (non-editable) sandboxes below the editor, showing the result and maybe a -# few other things (later layers). -# -# This layer draws the menubar buttons in non-editable sandboxes but they -# don't do anything yet. Later layers implement each button. - -def! main [ - local-scope - open-console - env:&:environment <- new-programming-environment 0/filesystem, 0/screen - env <- restore-sandboxes env - render-all 0/screen, env, render - event-loop 0/screen, 0/console, env, 0/filesystem - # never gets here -] - -container environment [ - sandbox:&:sandbox # list of sandboxes, from top to bottom. TODO: switch to &:list:sandbox - render-from:num - number-of-sandboxes:num -] - -after [ - *result <- put *result, render-from:offset, -1 -] - -container sandbox [ - data:text - response:text - # coordinates to track clicks - # constraint: will be 0 for sandboxes at positions before env.render-from - starting-row-on-screen:num - code-ending-row-on-screen:num # past end of code - screen:&:screen # prints in the sandbox go here - next-sandbox:&:sandbox -] - -scenario run-and-show-results [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - # recipe editor is empty - assume-resources [ - ] - # sandbox editor contains an instruction without storing outputs - env:&:environment <- new-programming-environment resources, screen, [divide-with-remainder 11, 3] - # run the code in the editors - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that screen prints the results - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊divide-with-remainder 11, 3 . - . ┊3 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - screen-should-contain-in-color 7/white, [ - . . - . . - . . - . . - . divide-with-remainder 11, 3 . - . . - . . - . . - . . - ] - screen-should-contain-in-color 245/grey, [ - . . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - . ┊3 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # sandbox menu in reverse video - screen-should-contain-in-color 232/black, [ - . . - . . - . . - . 0 edit copy delete . - ] - # run another command - assume-console [ - left-click 1, 80 - type [add 2, 2] - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that screen prints both sandboxes - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊divide-with-remainder 11, 3 . - . ┊3 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -after [ - # F4? load all code and run all sandboxes. - { - do-run?:bool <- equal k, 65532/F4 - break-unless do-run? - screen <- update-status screen, [running... ], 245/grey - error?:bool <- run-sandboxes env, resources, screen - # F4 might update warnings and results on both sides - screen <- render-all screen, env, render - { - break-if error? - screen <- update-status screen, [ ], 245/grey - } - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - loop +next-event - } -] - -def run-sandboxes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ - local-scope - load-ingredients - errors-found?:bool <- update-recipes env, resources, screen - return-if errors-found? - # check contents of right editor (sandbox) - - current-sandbox:&:editor <- get *env, current-sandbox:offset - { - sandbox-contents:text <- editor-contents current-sandbox - break-unless sandbox-contents - # if contents exist, first save them - # run them and turn them into a new sandbox - new-sandbox:&:sandbox <- new sandbox:type - *new-sandbox <- put *new-sandbox, data:offset, sandbox-contents - # push to head of sandbox list - dest:&:sandbox <- get *env, sandbox:offset - *new-sandbox <- put *new-sandbox, next-sandbox:offset, dest - *env <- put *env, sandbox:offset, new-sandbox - # update sandbox count - sandbox-count:num <- get *env, number-of-sandboxes:offset - sandbox-count <- add sandbox-count, 1 - *env <- put *env, number-of-sandboxes:offset, sandbox-count - # clear sandbox editor - init:&:duplex-list:char <- push 167/§, 0/tail - *current-sandbox <- put *current-sandbox, data:offset, init - *current-sandbox <- put *current-sandbox, top-of-screen:offset, init - } - # save all sandboxes before running, just in case we die when running - save-sandboxes env, resources - # run all sandboxes - curr:&:sandbox <- get *env, sandbox:offset - idx:num <- copy 0 - { - break-unless curr - curr <- update-sandbox curr, env, idx - curr <- get *curr, next-sandbox:offset - idx <- add idx, 1 - loop - } - -] - -# load code from disk -# replaced in a later layer (whereupon errors-found? will actually be set) -def update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ - local-scope - load-ingredients - recipes:&:editor <- get *env, recipes:offset - in:text <- editor-contents recipes - resources <- dump resources, [lesson/recipes.mu], in - reload in - errors-found? <- copy 0/false -] - -# replaced in a later layer -def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ - local-scope - load-ingredients - data:text <- get *sandbox, data:offset - response:text, _, fake-screen:&:screen <- run-sandboxed data - *sandbox <- put *sandbox, response:offset, response - *sandbox <- put *sandbox, screen:offset, fake-screen -] - -def update-status screen:&:screen, msg:text, color:num -> screen:&:screen [ - local-scope - load-ingredients - screen <- move-cursor screen, 0, 2 - screen <- print screen, msg, color, 238/grey/background -] - -def save-sandboxes env:&:environment, resources:&:resources -> resources:&:resources [ - local-scope - load-ingredients - current-sandbox:&:editor <- get *env, current-sandbox:offset - # first clear previous versions, in case we deleted some sandbox - $system [rm lesson/[0-9]* >/dev/null 2>/dev/null] # some shells can't handle '>&' - curr:&:sandbox <- get *env, sandbox:offset - idx:num <- copy 0 - { - break-unless curr - data:text <- get *curr, data:offset - filename:text <- append [lesson/], idx - resources <- dump resources, filename, data - - idx <- add idx, 1 - curr <- get *curr, next-sandbox:offset - loop - } -] - -def! render-sandbox-side screen:&:screen, env:&:environment, {render-editor: (recipe (address screen) (address editor) -> number number (address screen) (address editor))} -> screen:&:screen, env:&:environment [ - local-scope - load-ingredients - trace 11, [app], [render sandbox side] - current-sandbox:&:editor <- get *env, current-sandbox:offset - row:num, column:num <- copy 1, 0 - left:num <- get *current-sandbox, left:offset - right:num <- get *current-sandbox, right:offset - # render sandbox editor - render-from:num <- get *env, render-from:offset - { - render-current-sandbox?:bool <- equal render-from, -1 - break-unless render-current-sandbox? - row, column, screen, current-sandbox <- call render-editor, screen, current-sandbox - clear-screen-from screen, row, column, left, right - row <- add row, 1 - } - # render sandboxes - draw-horizontal screen, row, left, right - sandbox:&:sandbox <- get *env, sandbox:offset - row, screen <- render-sandboxes screen, sandbox, left, right, row, render-from - clear-rest-of-screen screen, row, left, right -] - -def render-sandboxes screen:&:screen, sandbox:&:sandbox, left:num, right:num, row:num, render-from:num, idx:num -> row:num, screen:&:screen, sandbox:&:sandbox [ - local-scope - load-ingredients - return-unless sandbox - screen-height:num <- screen-height screen - at-bottom?:bool <- greater-or-equal row, screen-height - return-if at-bottom?:bool - hidden?:bool <- lesser-than idx, render-from - { - break-if hidden? - # render sandbox menu - row <- add row, 1 - screen <- move-cursor screen, row, left - screen <- render-sandbox-menu screen, idx, left, right - # save menu row so we can detect clicks to it later - *sandbox <- put *sandbox, starting-row-on-screen:offset, row - # render sandbox contents - row <- add row, 1 - screen <- move-cursor screen, row, left - sandbox-data:text <- get *sandbox, data:offset - row, screen <- render-code screen, sandbox-data, left, right, row - *sandbox <- put *sandbox, code-ending-row-on-screen:offset, row - # render sandbox warnings, screen or response, in that order - sandbox-response:text <- get *sandbox, response:offset - - { - sandbox-screen:&:screen <- get *sandbox, screen:offset - empty-screen?:bool <- fake-screen-is-empty? sandbox-screen - break-if empty-screen? - row, screen <- render-screen screen, sandbox-screen, left, right, row - } - { - break-unless empty-screen? - - row, screen <- render-text screen, sandbox-response, left, right, 245/grey, row - } - +render-sandbox-end - at-bottom?:bool <- greater-or-equal row, screen-height - return-if at-bottom? - # draw solid line after sandbox - draw-horizontal screen, row, left, right - } - # if hidden, reset row attributes - { - break-unless hidden? - *sandbox <- put *sandbox, starting-row-on-screen:offset, 0 - *sandbox <- put *sandbox, code-ending-row-on-screen:offset, 0 - - } - # draw next sandbox - next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset - next-idx:num <- add idx, 1 - row, screen <- render-sandboxes screen, next-sandbox, left, right, row, render-from, next-idx -] - -def render-sandbox-menu screen:&:screen, sandbox-index:num, left:num, right:num -> screen:&:screen [ - local-scope - load-ingredients - move-cursor-to-column screen, left - edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num <- sandbox-menu-columns left, right - print screen, sandbox-index, 232/dark-grey, 245/grey - start-buttons:num <- subtract edit-button-left, 1 - clear-line-until screen, start-buttons, 245/grey - print screen, [edit], 232/black, 94/background-orange - clear-line-until screen, edit-button-right, 94/background-orange - _, col:num <- cursor-position screen - at-start-of-copy-button?:bool <- equal col, copy-button-left - assert at-start-of-copy-button?, [aaa] - print screen, [copy], 232/black, 58/background-green - clear-line-until screen, copy-button-right, 58/background-green - _, col:num <- cursor-position screen - at-start-of-delete-button?:bool <- equal col, delete-button-left - assert at-start-of-delete-button?, [bbb] - print screen, [delete], 232/black, 52/background-red - clear-line-until screen, right, 52/background-red -] - -# divide up the menu bar for a sandbox into 3 segments, for edit/copy/delete buttons -# delete-button-right == right -# all left/right pairs are inclusive -def sandbox-menu-columns left:num, right:num -> edit-button-left:num, edit-button-right:num, copy-button-left:num, copy-button-right:num, delete-button-left:num [ - local-scope - load-ingredients - start-buttons:num <- add left, 4/space-for-sandbox-index - buttons-space:num <- subtract right, start-buttons - button-width:num <- divide-with-remainder buttons-space, 3 # integer division - buttons-wide-enough?:bool <- greater-or-equal button-width, 8 - assert buttons-wide-enough?, [sandbox must be at least 30 or so characters wide] - edit-button-left:num <- copy start-buttons - copy-button-left:num <- add start-buttons, button-width - edit-button-right:num <- subtract copy-button-left, 1 - delete-button-left:num <- subtract right, button-width - copy-button-right:num <- subtract delete-button-left, 1 -] - -# print a text 's' to 'editor' in 'color' starting at 'row' -# clear rest of last line, move cursor to next line -def render-text screen:&:screen, s:text, left:num, right:num, color:num, row:num -> row:num, screen:&:screen [ - local-scope - load-ingredients - return-unless s - column:num <- copy left - screen <- move-cursor screen, row, column - screen-height:num <- screen-height screen - i:num <- copy 0 - len:num <- length *s - { - +next-character - done?:bool <- greater-or-equal i, len - break-if done? - done? <- greater-or-equal row, screen-height - break-if done? - c:char <- index *s, i - { - # at right? wrap. - at-right?:bool <- equal column, right - break-unless at-right? - # print wrap icon - wrap-icon:char <- copy 8617/loop-back-to-left - print screen, wrap-icon, 245/grey - column <- copy left - row <- add row, 1 - screen <- move-cursor screen, row, column - loop +next-character # retry i - } - i <- add i, 1 - { - # newline? move to left rather than 0 - newline?:bool <- equal c, 10/newline - break-unless newline? - # clear rest of line in this window - { - done?:bool <- greater-than column, right - break-if done? - space:char <- copy 32/space - print screen, space - column <- add column, 1 - loop - } - row <- add row, 1 - column <- copy left - screen <- move-cursor screen, row, column - loop +next-character - } - print screen, c, color - column <- add column, 1 - loop - } - was-at-left?:bool <- equal column, left - clear-line-until screen, right - { - break-if was-at-left? - row <- add row, 1 - } - move-cursor screen, row, left -] - -# assumes programming environment has no sandboxes; restores them from previous session -def restore-sandboxes env:&:environment, resources:&:resources -> env:&:environment [ - local-scope - load-ingredients - # read all scenarios, pushing them to end of a list of scenarios - idx:num <- copy 0 - curr:&:sandbox <- copy 0 - prev:&:sandbox <- copy 0 - { - filename:text <- append [lesson/], idx - contents:text <- slurp resources, filename - break-unless contents # stop at first error; assuming file didn't exist - # todo: handle empty sandbox - # create new sandbox for file - curr <- new sandbox:type - *curr <- put *curr, data:offset, contents - - { - break-if idx - *env <- put *env, sandbox:offset, curr - } - { - break-unless idx - *prev <- put *prev, next-sandbox:offset, curr - } - idx <- add idx, 1 - prev <- copy curr - loop - } - # update sandbox count - *env <- put *env, number-of-sandboxes:offset, idx -] - -# print the fake sandbox screen to 'screen' with appropriate delimiters -# leave cursor at start of next line -def render-screen screen:&:screen, sandbox-screen:&:screen, left:num, right:num, row:num -> row:num, screen:&:screen [ - local-scope - load-ingredients - return-unless sandbox-screen - # print 'screen:' - row <- render-text screen, [screen:], left, right, 245/grey, row - screen <- move-cursor screen, row, left - # start printing sandbox-screen - column:num <- copy left - s-width:num <- screen-width sandbox-screen - s-height:num <- screen-height sandbox-screen - buf:&:@:screen-cell <- get *sandbox-screen, data:offset - stop-printing:num <- add left, s-width, 3 - max-column:num <- min stop-printing, right - i:num <- copy 0 - len:num <- length *buf - screen-height:num <- screen-height screen - { - done?:bool <- greater-or-equal i, len - break-if done? - done? <- greater-or-equal row, screen-height - break-if done? - column <- copy left - screen <- move-cursor screen, row, column - # initial leader for each row: two spaces and a '.' - space:char <- copy 32/space - print screen, space, 245/grey - print screen, space, 245/grey - full-stop:char <- copy 46/period - print screen, full-stop, 245/grey - column <- add left, 3 - { - # print row - row-done?:bool <- greater-or-equal column, max-column - break-if row-done? - curr:screen-cell <- index *buf, i - c:char <- get curr, contents:offset - color:num <- get curr, color:offset - { - # damp whites down to grey - white?:bool <- equal color, 7/white - break-unless white? - color <- copy 245/grey - } - print screen, c, color - column <- add column, 1 - i <- add i, 1 - loop - } - # print final '.' - print screen, full-stop, 245/grey - column <- add column, 1 - { - # clear rest of current line - line-done?:bool <- greater-than column, right - break-if line-done? - print screen, space - column <- add column, 1 - loop - } - row <- add row, 1 - loop - } -] - -scenario run-updates-results [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 12/height - # define a recipe (no indent for the 'add' line below so column numbers are more obvious) - assume-resources [ - [lesson/recipes.mu] <- [ - || - |recipe foo [| - | local-scope| - | z:num <- add 2, 2| - | reply z| - |]| - ] - ] - # sandbox editor contains an instruction without storing outputs - env:&:environment <- new-programming-environment resources, screen, [foo] # contents of sandbox editor - # run the code in the editors - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .recipe foo [ ┊─────────────────────────────────────────────────. - . local-scope ┊0 edit copy delete . - . z:num <- add 2, 2 ┊foo . - . reply z ┊4 . - .] ┊─────────────────────────────────────────────────. - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - # make a change (incrementing one of the args to 'add'), then rerun - assume-console [ - left-click 4, 28 # one past the value of the second arg - press backspace - type [3] - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that screen updates the result on the right - screen-should-contain [ - . run (F4) . - . ┊ . - .recipe foo [ ┊─────────────────────────────────────────────────. - . local-scope ┊0 edit copy delete . - . z:num <- add 2, 3 ┊foo . - . reply z ┊5 . - .] ┊─────────────────────────────────────────────────. - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-instruction-manages-screen-per-sandbox [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 20/height - # empty recipes - assume-resources [ - ] - # sandbox editor contains an instruction - env:&:environment <- new-programming-environment resources, screen, [print screen, 4] # contents of sandbox editor - # run the code in the editor - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that it prints a little toy screen - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊print screen, 4 . - . ┊screen: . - . ┊ .4 . . - . ┊ . . . - . ┊ . . . - . ┊ . . . - . ┊ . . . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -def editor-contents editor:&:editor -> result:text [ - local-scope - load-ingredients - buf:&:buffer <- new-buffer 80 - curr:&:duplex-list:char <- get *editor, data:offset - # skip § sentinel - assert curr, [editor without data is illegal; must have at least a sentinel] - curr <- next curr - return-unless curr, 0 - { - break-unless curr - c:char <- get *curr, value:offset - buf <- append buf, c - curr <- next curr - loop - } - result <- buffer-to-array buf -] - -scenario editor-provides-edited-contents [ - local-scope - assume-screen 10/width, 5/height - e:&:editor <- new-editor [abc], 0/left, 10/right - assume-console [ - left-click 1, 2 - type [def] - ] - run [ - editor-event-loop screen, console, e - s:text <- editor-contents e - 1:@:char/raw <- copy *s - ] - memory-should-contain [ - 1:array:character <- [abdefc] - ] -] - -# keep the bottom of recipes from scrolling off the screen - -scenario scrolling-down-past-bottom-of-recipe-editor [ - local-scope - trace-until 100/app - assume-screen 100/width, 10/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - assume-console [ - press enter - press down-arrow - ] - event-loop screen, console, env, resources - # no scroll - screen-should-contain [ - . run (F4) . - . ┊ . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario cursor-down-in-recipe-editor [ - local-scope - trace-until 100/app - assume-screen 100/width, 10/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - assume-console [ - press enter - press up-arrow - press down-arrow # while cursor isn't at bottom - ] - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - # cursor moves back to bottom - screen-should-contain [ - . run (F4) . - . ┊ . - .␣ ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -# we'll not use the recipe-editor's 'bottom' element directly, because later -# layers will add other stuff to the left side below the editor (error messages) - -container environment [ - recipe-bottom:num -] - -after [ - *env <- put *env, recipe-bottom:offset, row -] - -after [ - { - break-if sandbox-in-focus? - down-arrow?:bool <- equal k, 65516/down-arrow - break-unless down-arrow? - recipe-editor:&:editor <- get *env, recipes:offset - recipe-cursor-row:num <- get *recipe-editor, cursor-row:offset - recipe-editor-bottom:num <- get *recipe-editor, bottom:offset - at-bottom-of-editor?:bool <- greater-or-equal recipe-cursor-row, recipe-editor-bottom - break-unless at-bottom-of-editor? - more-to-scroll?:bool <- more-to-scroll? env, screen - break-if more-to-scroll? - loop +next-event - } - { - break-if sandbox-in-focus? - page-down?:bool <- equal k, 65518/page-down - break-unless page-down? - more-to-scroll?:bool <- more-to-scroll? env, screen - break-if more-to-scroll? - loop +next-event - } -] - -after [ - { - break-if sandbox-in-focus? - page-down?:bool <- equal k, 6/ctrl-f - break-unless page-down? - more-to-scroll?:bool <- more-to-scroll? env, screen - break-if more-to-scroll? - loop +next-event - } -] - -def more-to-scroll? env:&:environment, screen:&:screen -> result:bool [ - local-scope - load-ingredients - recipe-bottom:num <- get *env, recipe-bottom:offset - height:num <- screen-height screen - result <- greater-or-equal recipe-bottom, height -] - -scenario scrolling-down-past-bottom-of-recipe-editor-2 [ - local-scope - trace-until 100/app - assume-screen 100/width, 10/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - assume-console [ - # add a line - press enter - # cursor back to top line - press up-arrow - # try to scroll - press page-down # or ctrl-f - ] - event-loop screen, console, env, resources - # no scroll, and cursor remains at top line - screen-should-contain [ - . run (F4) . - . ┊ . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario scrolling-down-past-bottom-of-recipe-editor-3 [ - local-scope - trace-until 100/app - assume-screen 100/width, 10/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [ab -cd] - render-all screen, env, render - assume-console [ - # add a line - press enter - # switch to sandbox - press ctrl-n - # move cursor - press down-arrow - ] - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - # no scroll on recipe side, cursor moves on sandbox side - screen-should-contain [ - . run (F4) . - . ┊ab . - . ┊␣d . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -# scrolling through sandboxes - -scenario scrolling-down-past-bottom-of-sandbox-editor [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 2, 2] - render-all screen, env, render - assume-console [ - # create a sandbox - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - ] - # switch to sandbox window and hit 'page-down' - assume-console [ - press ctrl-n - press page-down - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # sandbox editor hidden; first sandbox displayed - # cursor moves to first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣ edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # hit 'page-up' - assume-console [ - press page-up - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # sandbox editor displays again, cursor is in editor - screen-should-contain [ - . run (F4) . - . ┊␣ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - ] -] - -# page-down on sandbox side updates render-from to scroll sandboxes -after [ - { - break-unless sandbox-in-focus? - page-down?:bool <- equal k, 65518/page-down - break-unless page-down? - sandbox:&:sandbox <- get *env, sandbox:offset - break-unless sandbox - # slide down if possible - { - render-from:num <- get *env, render-from:offset - number-of-sandboxes:num <- get *env, number-of-sandboxes:offset - max:num <- subtract number-of-sandboxes, 1 - at-end?:bool <- greater-or-equal render-from, max - jump-if at-end?, +finish-event # render nothing - render-from <- add render-from, 1 - *env <- put *env, render-from:offset, render-from - } - hide-screen screen - screen <- render-sandbox-side screen, env, render - show-screen screen - jump +finish-event - } -] - -# update-cursor takes render-from into account -after [ - { - break-unless sandbox-in-focus? - render-from:num <- get *env, render-from:offset - scrolling?:bool <- greater-or-equal render-from, 0 - break-unless scrolling? - cursor-column:num <- get *current-sandbox, left:offset - screen <- move-cursor screen, 2/row, cursor-column # highlighted sandbox will always start at row 2 - return - } -] - -# 'page-up' on sandbox side is like 'page-down': updates render-from when necessary -after [ - { - break-unless sandbox-in-focus? - page-up?:bool <- equal k, 65519/page-up - break-unless page-up? - render-from:num <- get *env, render-from:offset - at-beginning?:bool <- equal render-from, -1 - break-if at-beginning? - render-from <- subtract render-from, 1 - *env <- put *env, render-from:offset, render-from - hide-screen screen - screen <- render-sandbox-side screen, env, render - show-screen screen - jump +finish-event - } -] - -# sandbox belonging to 'env' whose next-sandbox is 'in' -# return 0 if there's no such sandbox, either because 'in' doesn't exist in 'env', or because it's the first sandbox -def previous-sandbox env:&:environment, in:&:sandbox -> out:&:sandbox [ - local-scope - load-ingredients - curr:&:sandbox <- get *env, sandbox:offset - return-unless curr, 0/nil - next:&:sandbox <- get *curr, next-sandbox:offset - { - return-unless next, 0/nil - found?:bool <- equal next, in - break-if found? - curr <- copy next - next <- get *curr, next-sandbox:offset - loop - } - return curr -] - -scenario scrolling-down-past-bottom-on-recipe-side [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize sandbox side and create a sandbox - assume-resources [ - [lesson/recipes.mu] <- [ - || # file containing just a newline - ] - ] - # create a sandbox - env:&:environment <- new-programming-environment resources, screen, [add 2, 2] - render-all screen, env, render - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - # hit 'down' in recipe editor - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # cursor doesn't move when the end is already on-screen - screen-should-contain [ - . run (F4) . - .␣ ┊ . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 2, 2 . - ] -] - -scenario scrolling-through-multiple-sandboxes [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - ] - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - screen-should-contain [ - . run (F4) . - . ┊␣ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # hit 'page-down' - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # sandbox editor hidden; first sandbox displayed - # cursor moves to first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊␣ edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # hit 'page-down' again - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # just second sandbox displayed - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # hit 'page-down' again - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # no change - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # hit 'page-up' - assume-console [ - press page-up - ] - run [ - event-loop screen, console, env, resources - ] - # back to displaying both sandboxes without editor - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # hit 'page-up' again - assume-console [ - press page-up - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # back to displaying both sandboxes as well as editor - screen-should-contain [ - . run (F4) . - . ┊␣ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # hit 'page-up' again - assume-console [ - press page-up - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # no change - screen-should-contain [ - . run (F4) . - . ┊␣ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] -] - -scenario scrolling-manages-sandbox-index-correctly [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create a sandbox - assume-console [ - press ctrl-n - type [add 1, 1] - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # hit 'page-down' and 'page-up' a couple of times. sandbox index should be stable - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # sandbox editor hidden; first sandbox displayed - # cursor moves to first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # hit 'page-up' again - assume-console [ - press page-up - ] - run [ - event-loop screen, console, env, resources - ] - # back to displaying both sandboxes as well as editor - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # hit 'page-down' - assume-console [ - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # sandbox editor hidden; first sandbox displayed - # cursor moves to first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] diff --git a/edit/007-sandbox-copy.mu b/edit/007-sandbox-copy.mu deleted file mode 100644 index 9df5e625..00000000 --- a/edit/007-sandbox-copy.mu +++ /dev/null @@ -1,279 +0,0 @@ -## the 'copy' button makes it easy to duplicate a sandbox, and thence to -## see code operate in multiple situations - -scenario copy-a-sandbox-to-editor [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # empty recipes - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # click at left edge of 'copy' button - assume-console [ - left-click 3, 69 - ] - run [ - event-loop screen, console, env, resources - ] - # it copies into editor - screen-should-contain [ - . run (F4) . - . ┊add 1, 1 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # cursor should be in the right place - assume-console [ - type [0] - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊0add 1, 1 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario copy-a-sandbox-to-editor-2 [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # empty recipes - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # click at right edge of 'copy' button (just before 'delete') - assume-console [ - left-click 3, 84 - ] - run [ - event-loop screen, console, env, resources - ] - # it copies into editor - screen-should-contain [ - . run (F4) . - . ┊add 1, 1 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # cursor should be in the right place - assume-console [ - type [0] - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊0add 1, 1 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -after [ - # support 'copy' button - { - copy?:bool <- should-attempt-copy? click-row, click-column, env - break-unless copy? - copy?, env <- try-copy-sandbox click-row, env - break-unless copy? - hide-screen screen - screen <- render-sandbox-side screen, env, render - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - show-screen screen - loop +next-event - } -] - -# some preconditions for attempting to copy a sandbox -def should-attempt-copy? click-row:num, click-column:num, env:&:environment -> result:bool [ - local-scope - load-ingredients - # are we below the sandbox editor? - click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env - return-unless click-sandbox-area?, 0/false - # narrower, is the click in the columns spanning the 'copy' button? - first-sandbox:&:editor <- get *env, current-sandbox:offset - assert first-sandbox, [!!] - sandbox-left-margin:num <- get *first-sandbox, left:offset - sandbox-right-margin:num <- get *first-sandbox, right:offset - _, _, copy-button-left:num, copy-button-right:num, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin - copy-button-vertical-area?:bool <- within-range? click-column, copy-button-left, copy-button-right - return-unless copy-button-vertical-area?, 0/false - # finally, is sandbox editor empty? - current-sandbox:&:editor <- get *env, current-sandbox:offset - result <- empty-editor? current-sandbox -] - -def try-copy-sandbox click-row:num, env:&:environment -> clicked-on-copy-button?:bool, env:&:environment [ - local-scope - load-ingredients - # identify the sandbox to copy, if the click was actually on the 'copy' button - sandbox:&:sandbox <- find-sandbox env, click-row - return-unless sandbox, 0/false - clicked-on-copy-button? <- copy 1/true - text:text <- get *sandbox, data:offset - current-sandbox:&:editor <- get *env, current-sandbox:offset - current-sandbox <- insert-text current-sandbox, text - # reset scroll - *env <- put *env, render-from:offset, -1 - # position cursor in sandbox editor - *env <- put *env, sandbox-in-focus?:offset, 1/true -] - -def find-sandbox env:&:environment, click-row:num -> result:&:sandbox [ - local-scope - load-ingredients - curr-sandbox:&:sandbox <- get *env, sandbox:offset - { - break-unless curr-sandbox - start:num <- get *curr-sandbox, starting-row-on-screen:offset - found?:bool <- equal click-row, start - return-if found?, curr-sandbox - curr-sandbox <- get *curr-sandbox, next-sandbox:offset - loop - } - return 0/not-found -] - -def click-on-sandbox-area? click-row:num, click-column:num, env:&:environment -> result:bool [ - local-scope - load-ingredients - current-sandbox:&:editor <- get *env, current-sandbox:offset - sandbox-left-margin:num <- get *current-sandbox, left:offset - on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin - return-unless on-sandbox-side?, 0/false - first-sandbox:&:sandbox <- get *env, sandbox:offset - return-unless first-sandbox, 0/false - first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset - result <- greater-or-equal click-row, first-sandbox-begins -] - -def empty-editor? editor:&:editor -> result:bool [ - local-scope - load-ingredients - head:&:duplex-list:char <- get *editor, data:offset - first:&:duplex-list:char <- next head - result <- not first -] - -def within-range? x:num, low:num, high:num -> result:bool [ - local-scope - load-ingredients - not-too-far-left?:bool <- greater-or-equal x, low - not-too-far-right?:bool <- lesser-or-equal x, high - result <- and not-too-far-left? not-too-far-right? -] - -scenario copy-fails-if-sandbox-editor-not-empty [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # empty recipes - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 1, 1] # contents of sandbox editor - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # type something into the sandbox editor, then click on the 'copy' button - assume-console [ - left-click 2, 70 # put cursor in sandbox editor - type [0] # type something - left-click 3, 70 # click 'copy' button - ] - run [ - event-loop screen, console, env, resources - ] - # copy doesn't happen - screen-should-contain [ - . run (F4) . - . ┊0 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊01 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] diff --git a/edit/007-sandbox-delete.mu b/edit/007-sandbox-delete.mu new file mode 100644 index 00000000..4fa3c37d --- /dev/null +++ b/edit/007-sandbox-delete.mu @@ -0,0 +1,343 @@ +## deleting sandboxes + +scenario deleting-sandboxes [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + # run a few commands + assume-console [ + left-click 1, 80 + type [divide-with-remainder 11, 3] + press F4 + type [add 2, 2] + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊divide-with-remainder 11, 3 . + . ┊3 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # delete second sandbox by clicking on left edge of 'delete' button + assume-console [ + left-click 7, 85 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + ] + # delete first sandbox by clicking at right edge of 'delete' button + assume-console [ + left-click 3, 99 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + ] +] + +after [ + # support 'delete' button + { + delete?:bool <- should-attempt-delete? click-row, click-column, env + break-unless delete? + delete?, env <- try-delete-sandbox click-row, env + break-unless delete? + hide-screen screen + screen <- render-sandbox-side screen, env, render + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + show-screen screen + loop +next-event + } +] + +# some preconditions for attempting to delete a sandbox +def should-attempt-delete? click-row:num, click-column:num, env:&:environment -> result:bool [ + local-scope + load-ingredients + # are we below the sandbox editor? + click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env + return-unless click-sandbox-area?, 0/false + # narrower, is the click in the columns spanning the 'copy' button? + first-sandbox:&:editor <- get *env, current-sandbox:offset + assert first-sandbox, [!!] + sandbox-left-margin:num <- get *first-sandbox, left:offset + sandbox-right-margin:num <- get *first-sandbox, right:offset + _, _, _, _, delete-button-left:num <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin + result <- within-range? click-column, delete-button-left, sandbox-right-margin +] + +def try-delete-sandbox click-row:num, env:&:environment -> clicked-on-delete-button?:bool, env:&:environment [ + local-scope + load-ingredients + # identify the sandbox to delete, if the click was actually on the 'delete' button + sandbox:&:sandbox <- find-sandbox env, click-row + return-unless sandbox, 0/false + clicked-on-delete-button? <- copy 1/true + env <- delete-sandbox env, sandbox +] + +def delete-sandbox env:&:environment, sandbox:&:sandbox -> env:&:environment [ + local-scope + load-ingredients + curr-sandbox:&:sandbox <- get *env, sandbox:offset + first-sandbox?:bool <- equal curr-sandbox, sandbox + { + # first sandbox? pop + break-unless first-sandbox? + next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset + *env <- put *env, sandbox:offset, next-sandbox + } + { + # not first sandbox? + break-if first-sandbox? + prev-sandbox:&:sandbox <- copy curr-sandbox + curr-sandbox <- get *curr-sandbox, next-sandbox:offset + { + assert curr-sandbox, [sandbox not found! something is wrong.] + found?:bool <- equal curr-sandbox, sandbox + break-if found? + prev-sandbox <- copy curr-sandbox + curr-sandbox <- get *curr-sandbox, next-sandbox:offset + loop + } + # snip sandbox out of its list + next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset + *prev-sandbox <- put *prev-sandbox, next-sandbox:offset, next-sandbox + } + # update sandbox count + sandbox-count:num <- get *env, number-of-sandboxes:offset + sandbox-count <- subtract sandbox-count, 1 + *env <- put *env, number-of-sandboxes:offset, sandbox-count + # reset scroll if deleted sandbox was last + { + break-if next-sandbox + render-from:num <- get *env, render-from:offset + reset-scroll?:bool <- equal render-from, sandbox-count + break-unless reset-scroll? + *env <- put *env, render-from:offset, -1 + } +] + +scenario deleting-sandbox-after-scroll [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes and scroll to second + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + press page-down + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + ] + # delete the second sandbox + assume-console [ + left-click 6, 99 + ] + run [ + event-loop screen, console, env, resources + ] + # second sandbox shows in editor; scroll resets to display first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario deleting-top-sandbox-after-scroll [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes and scroll to second + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + press page-down + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + ] + # delete the second sandbox + assume-console [ + left-click 2, 99 + ] + run [ + event-loop screen, console, env, resources + ] + # second sandbox shows in editor; scroll resets to display first sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario deleting-final-sandbox-after-scroll [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes and scroll to second + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + press page-down + press page-down + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # delete the second sandbox + assume-console [ + left-click 2, 99 + ] + run [ + event-loop screen, console, env, resources + ] + # implicitly scroll up to first sandbox + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario deleting-updates-sandbox-count [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # delete the second sandbox, then try to scroll down twice + assume-console [ + left-click 3, 99 + press page-down + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # shouldn't go past last sandbox + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] diff --git a/edit/008-sandbox-delete.mu b/edit/008-sandbox-delete.mu deleted file mode 100644 index 4fa3c37d..00000000 --- a/edit/008-sandbox-delete.mu +++ /dev/null @@ -1,343 +0,0 @@ -## deleting sandboxes - -scenario deleting-sandboxes [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - # run a few commands - assume-console [ - left-click 1, 80 - type [divide-with-remainder 11, 3] - press F4 - type [add 2, 2] - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊divide-with-remainder 11, 3 . - . ┊3 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # delete second sandbox by clicking on left edge of 'delete' button - assume-console [ - left-click 7, 85 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - ] - # delete first sandbox by clicking at right edge of 'delete' button - assume-console [ - left-click 3, 99 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - ] -] - -after [ - # support 'delete' button - { - delete?:bool <- should-attempt-delete? click-row, click-column, env - break-unless delete? - delete?, env <- try-delete-sandbox click-row, env - break-unless delete? - hide-screen screen - screen <- render-sandbox-side screen, env, render - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - show-screen screen - loop +next-event - } -] - -# some preconditions for attempting to delete a sandbox -def should-attempt-delete? click-row:num, click-column:num, env:&:environment -> result:bool [ - local-scope - load-ingredients - # are we below the sandbox editor? - click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env - return-unless click-sandbox-area?, 0/false - # narrower, is the click in the columns spanning the 'copy' button? - first-sandbox:&:editor <- get *env, current-sandbox:offset - assert first-sandbox, [!!] - sandbox-left-margin:num <- get *first-sandbox, left:offset - sandbox-right-margin:num <- get *first-sandbox, right:offset - _, _, _, _, delete-button-left:num <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin - result <- within-range? click-column, delete-button-left, sandbox-right-margin -] - -def try-delete-sandbox click-row:num, env:&:environment -> clicked-on-delete-button?:bool, env:&:environment [ - local-scope - load-ingredients - # identify the sandbox to delete, if the click was actually on the 'delete' button - sandbox:&:sandbox <- find-sandbox env, click-row - return-unless sandbox, 0/false - clicked-on-delete-button? <- copy 1/true - env <- delete-sandbox env, sandbox -] - -def delete-sandbox env:&:environment, sandbox:&:sandbox -> env:&:environment [ - local-scope - load-ingredients - curr-sandbox:&:sandbox <- get *env, sandbox:offset - first-sandbox?:bool <- equal curr-sandbox, sandbox - { - # first sandbox? pop - break-unless first-sandbox? - next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset - *env <- put *env, sandbox:offset, next-sandbox - } - { - # not first sandbox? - break-if first-sandbox? - prev-sandbox:&:sandbox <- copy curr-sandbox - curr-sandbox <- get *curr-sandbox, next-sandbox:offset - { - assert curr-sandbox, [sandbox not found! something is wrong.] - found?:bool <- equal curr-sandbox, sandbox - break-if found? - prev-sandbox <- copy curr-sandbox - curr-sandbox <- get *curr-sandbox, next-sandbox:offset - loop - } - # snip sandbox out of its list - next-sandbox:&:sandbox <- get *curr-sandbox, next-sandbox:offset - *prev-sandbox <- put *prev-sandbox, next-sandbox:offset, next-sandbox - } - # update sandbox count - sandbox-count:num <- get *env, number-of-sandboxes:offset - sandbox-count <- subtract sandbox-count, 1 - *env <- put *env, number-of-sandboxes:offset, sandbox-count - # reset scroll if deleted sandbox was last - { - break-if next-sandbox - render-from:num <- get *env, render-from:offset - reset-scroll?:bool <- equal render-from, sandbox-count - break-unless reset-scroll? - *env <- put *env, render-from:offset, -1 - } -] - -scenario deleting-sandbox-after-scroll [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes and scroll to second - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - press page-down - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - ] - # delete the second sandbox - assume-console [ - left-click 6, 99 - ] - run [ - event-loop screen, console, env, resources - ] - # second sandbox shows in editor; scroll resets to display first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario deleting-top-sandbox-after-scroll [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes and scroll to second - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - press page-down - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - ] - # delete the second sandbox - assume-console [ - left-click 2, 99 - ] - run [ - event-loop screen, console, env, resources - ] - # second sandbox shows in editor; scroll resets to display first sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario deleting-final-sandbox-after-scroll [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes and scroll to second - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - press page-down - press page-down - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # delete the second sandbox - assume-console [ - left-click 2, 99 - ] - run [ - event-loop screen, console, env, resources - ] - # implicitly scroll up to first sandbox - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario deleting-updates-sandbox-count [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # delete the second sandbox, then try to scroll down twice - assume-console [ - left-click 3, 99 - press page-down - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # shouldn't go past last sandbox - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] diff --git a/edit/008-sandbox-edit.mu b/edit/008-sandbox-edit.mu new file mode 100644 index 00000000..dd5c1bb9 --- /dev/null +++ b/edit/008-sandbox-edit.mu @@ -0,0 +1,324 @@ +## editing sandboxes after they've been created + +scenario clicking-on-sandbox-edit-button-moves-it-to-editor [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # empty recipes + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 2, 2] + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # click at left edge of 'edit' button + assume-console [ + left-click 3, 55 + ] + run [ + event-loop screen, console, env, resources + ] + # it pops back into editor + screen-should-contain [ + . run (F4) . + . ┊add 2, 2 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] + # cursor should be in the right place + assume-console [ + type [0] + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊0add 2, 2 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario clicking-on-sandbox-edit-button-moves-it-to-editor-2 [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # empty recipes + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [add 2, 2] + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # click at right edge of 'edit' button (just before 'copy') + assume-console [ + left-click 3, 68 + ] + run [ + event-loop screen, console, env, resources + ] + # it pops back into editor + screen-should-contain [ + . run (F4) . + . ┊add 2, 2 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] + # cursor should be in the right place + assume-console [ + type [0] + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊0add 2, 2 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +after [ + # support 'edit' button + { + edit?:bool <- should-attempt-edit? click-row, click-column, env + break-unless edit? + edit?, env <- try-edit-sandbox click-row, env + break-unless edit? + hide-screen screen + screen <- render-sandbox-side screen, env, render + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + show-screen screen + loop +next-event + } +] + +# some preconditions for attempting to edit a sandbox +def should-attempt-edit? click-row:num, click-column:num, env:&:environment -> result:bool [ + local-scope + load-ingredients + # are we below the sandbox editor? + click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env + return-unless click-sandbox-area?, 0/false + # narrower, is the click in the columns spanning the 'edit' button? + first-sandbox:&:editor <- get *env, current-sandbox:offset + assert first-sandbox, [!!] + sandbox-left-margin:num <- get *first-sandbox, left:offset + sandbox-right-margin:num <- get *first-sandbox, right:offset + edit-button-left:num, edit-button-right:num, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin + edit-button-vertical-area?:bool <- within-range? click-column, edit-button-left, edit-button-right + return-unless edit-button-vertical-area?, 0/false + # finally, is sandbox editor empty? + current-sandbox:&:editor <- get *env, current-sandbox:offset + result <- empty-editor? current-sandbox +] + +def try-edit-sandbox click-row:num, env:&:environment -> clicked-on-edit-button?:bool, env:&:environment [ + local-scope + load-ingredients + # identify the sandbox to edit, if the click was actually on the 'edit' button + sandbox:&:sandbox <- find-sandbox env, click-row + return-unless sandbox, 0/false + clicked-on-edit-button? <- copy 1/true + # 'edit' button = 'copy' button + 'delete' button + text:text <- get *sandbox, data:offset + current-sandbox:&:editor <- get *env, current-sandbox:offset + current-sandbox <- insert-text current-sandbox, text + env <- delete-sandbox env, sandbox + # reset scroll + *env <- put *env, render-from:offset, -1 + # position cursor in sandbox editor + *env <- put *env, sandbox-in-focus?:offset, 1/true +] + +scenario sandbox-with-print-can-be-edited [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 20/height + # left editor is empty + assume-resources [ + ] + # right editor contains a print instruction + env:&:environment <- new-programming-environment resources, screen, [print screen, 4] + # run the sandbox + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊print screen, 4 . + . ┊screen: . + . ┊ .4 . . + . ┊ . . . + . ┊ . . . + . ┊ . . . + . ┊ . . . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # edit the sandbox + assume-console [ + left-click 3, 65 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . run (F4) . + . ┊print screen, 4 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + ] +] + +scenario editing-sandbox-after-scrolling-resets-scroll [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes and scroll to second + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + press page-down + press page-down + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # edit the second sandbox + assume-console [ + left-click 2, 55 + ] + run [ + event-loop screen, console, env, resources + ] + # second sandbox shows in editor; scroll resets to display first sandbox + screen-should-contain [ + . run (F4) . + . ┊add 2, 2 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario editing-sandbox-updates-sandbox-count [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # initialize environment + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + render-all screen, env, render + # create 2 sandboxes + assume-console [ + press ctrl-n + type [add 2, 2] + press F4 + type [add 1, 1] + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # edit the second sandbox, then resave + assume-console [ + left-click 3, 60 + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # no change in contents + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 1, 1 . + . ┊2 . + . ┊─────────────────────────────────────────────────. + . ┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + ] + # now try to scroll past end + assume-console [ + press page-down + press page-down + press page-down + ] + run [ + event-loop screen, console, env, resources + ] + # screen should show just final sandbox with the right index (1) + screen-should-contain [ + . run (F4) . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] diff --git a/edit/009-sandbox-edit.mu b/edit/009-sandbox-edit.mu deleted file mode 100644 index dd5c1bb9..00000000 --- a/edit/009-sandbox-edit.mu +++ /dev/null @@ -1,324 +0,0 @@ -## editing sandboxes after they've been created - -scenario clicking-on-sandbox-edit-button-moves-it-to-editor [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # empty recipes - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 2, 2] - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # click at left edge of 'edit' button - assume-console [ - left-click 3, 55 - ] - run [ - event-loop screen, console, env, resources - ] - # it pops back into editor - screen-should-contain [ - . run (F4) . - . ┊add 2, 2 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] - # cursor should be in the right place - assume-console [ - type [0] - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊0add 2, 2 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario clicking-on-sandbox-edit-button-moves-it-to-editor-2 [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # empty recipes - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [add 2, 2] - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # click at right edge of 'edit' button (just before 'copy') - assume-console [ - left-click 3, 68 - ] - run [ - event-loop screen, console, env, resources - ] - # it pops back into editor - screen-should-contain [ - . run (F4) . - . ┊add 2, 2 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] - # cursor should be in the right place - assume-console [ - type [0] - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊0add 2, 2 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -after [ - # support 'edit' button - { - edit?:bool <- should-attempt-edit? click-row, click-column, env - break-unless edit? - edit?, env <- try-edit-sandbox click-row, env - break-unless edit? - hide-screen screen - screen <- render-sandbox-side screen, env, render - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - show-screen screen - loop +next-event - } -] - -# some preconditions for attempting to edit a sandbox -def should-attempt-edit? click-row:num, click-column:num, env:&:environment -> result:bool [ - local-scope - load-ingredients - # are we below the sandbox editor? - click-sandbox-area?:bool <- click-on-sandbox-area? click-row, click-column, env - return-unless click-sandbox-area?, 0/false - # narrower, is the click in the columns spanning the 'edit' button? - first-sandbox:&:editor <- get *env, current-sandbox:offset - assert first-sandbox, [!!] - sandbox-left-margin:num <- get *first-sandbox, left:offset - sandbox-right-margin:num <- get *first-sandbox, right:offset - edit-button-left:num, edit-button-right:num, _ <- sandbox-menu-columns sandbox-left-margin, sandbox-right-margin - edit-button-vertical-area?:bool <- within-range? click-column, edit-button-left, edit-button-right - return-unless edit-button-vertical-area?, 0/false - # finally, is sandbox editor empty? - current-sandbox:&:editor <- get *env, current-sandbox:offset - result <- empty-editor? current-sandbox -] - -def try-edit-sandbox click-row:num, env:&:environment -> clicked-on-edit-button?:bool, env:&:environment [ - local-scope - load-ingredients - # identify the sandbox to edit, if the click was actually on the 'edit' button - sandbox:&:sandbox <- find-sandbox env, click-row - return-unless sandbox, 0/false - clicked-on-edit-button? <- copy 1/true - # 'edit' button = 'copy' button + 'delete' button - text:text <- get *sandbox, data:offset - current-sandbox:&:editor <- get *env, current-sandbox:offset - current-sandbox <- insert-text current-sandbox, text - env <- delete-sandbox env, sandbox - # reset scroll - *env <- put *env, render-from:offset, -1 - # position cursor in sandbox editor - *env <- put *env, sandbox-in-focus?:offset, 1/true -] - -scenario sandbox-with-print-can-be-edited [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 20/height - # left editor is empty - assume-resources [ - ] - # right editor contains a print instruction - env:&:environment <- new-programming-environment resources, screen, [print screen, 4] - # run the sandbox - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊print screen, 4 . - . ┊screen: . - . ┊ .4 . . - . ┊ . . . - . ┊ . . . - . ┊ . . . - . ┊ . . . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # edit the sandbox - assume-console [ - left-click 3, 65 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . run (F4) . - . ┊print screen, 4 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - ] -] - -scenario editing-sandbox-after-scrolling-resets-scroll [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes and scroll to second - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - press page-down - press page-down - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # edit the second sandbox - assume-console [ - left-click 2, 55 - ] - run [ - event-loop screen, console, env, resources - ] - # second sandbox shows in editor; scroll resets to display first sandbox - screen-should-contain [ - . run (F4) . - . ┊add 2, 2 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario editing-sandbox-updates-sandbox-count [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # initialize environment - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - render-all screen, env, render - # create 2 sandboxes - assume-console [ - press ctrl-n - type [add 2, 2] - press F4 - type [add 1, 1] - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # edit the second sandbox, then resave - assume-console [ - left-click 3, 60 - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # no change in contents - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 1, 1 . - . ┊2 . - . ┊─────────────────────────────────────────────────. - . ┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - ] - # now try to scroll past end - assume-console [ - press page-down - press page-down - press page-down - ] - run [ - event-loop screen, console, env, resources - ] - # screen should show just final sandbox with the right index (1) - screen-should-contain [ - . run (F4) . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊1 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] diff --git a/edit/009-sandbox-test.mu b/edit/009-sandbox-test.mu new file mode 100644 index 00000000..39b58ecb --- /dev/null +++ b/edit/009-sandbox-test.mu @@ -0,0 +1,207 @@ +## clicking on sandbox results to 'fix' them and turn sandboxes into tests + +scenario sandbox-click-on-result-toggles-color-to-green [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # basic recipe + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | reply 4| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + .recipe foo [ ┊ . + . reply 4 ┊─────────────────────────────────────────────────. + .] ┊0 edit copy delete . + . ┊foo . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # click on the '4' in the result + assume-console [ + left-click 5, 51 + ] + run [ + event-loop screen, console, env, resources + ] + # color toggles to green + screen-should-contain-in-color 2/green, [ + . . + . . + . . + . . + . . + . 4 . + . . + . . + ] + # cursor should remain unmoved + run [ + cursor:char <- copy 9251/␣ + print screen, cursor + ] + screen-should-contain [ + . run (F4) . + .␣ecipe foo [ ┊ . + . reply 4 ┊─────────────────────────────────────────────────. + .] ┊0 edit copy delete . + . ┊foo . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + # now change the result + # then rerun + assume-console [ + left-click 2, 11 # cursor to end of line + press backspace + type [3] + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # result turns red + screen-should-contain-in-color 1/red, [ + . . + . . + . . + . . + . . + . 3 . + . . + . . + ] +] + +# this requires tracking a couple more things +container sandbox [ + response-starting-row-on-screen:num + expected-response:text +] + +# include expected response when saving or restoring a sandbox +before [ + { + expected-response:text <- get *curr, expected-response:offset + break-unless expected-response + filename <- append filename, [.out] + resources <- dump resources, filename, expected-response + } +] + +before [ + { + filename <- append filename, [.out] + contents <- slurp resources, filename + break-unless contents + *curr <- put *curr, expected-response:offset, contents + } +] + +# clicks on sandbox responses save it as 'expected' +after [ + # check if it's inside the output of any sandbox + { + sandbox-left-margin:num <- get *current-sandbox, left:offset + click-column:num <- get t, column:offset + on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin + break-unless on-sandbox-side? + first-sandbox:&:sandbox <- get *env, sandbox:offset + break-unless first-sandbox + first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset + click-row:num <- get t, row:offset + below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins + break-unless below-sandbox-editor? + # identify the sandbox whose output is being clicked on + sandbox:&:sandbox <- find-click-in-sandbox-output env, click-row + break-unless sandbox + # toggle its expected-response, and save session + sandbox <- toggle-expected-response sandbox + save-sandboxes env, resources + hide-screen screen + screen <- render-sandbox-side screen, env, render + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + # no change in cursor + show-screen screen + loop +next-event + } +] + +def find-click-in-sandbox-output env:&:environment, click-row:num -> sandbox:&:sandbox [ + local-scope + load-ingredients + # assert click-row >= sandbox.starting-row-on-screen + sandbox:&:sandbox <- get *env, sandbox:offset + start:num <- get *sandbox, starting-row-on-screen:offset + clicked-on-sandboxes?:bool <- greater-or-equal click-row, start + assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor] + # while click-row < sandbox.next-sandbox.starting-row-on-screen + { + next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset + break-unless next-sandbox + next-start:num <- get *next-sandbox, starting-row-on-screen:offset + found?:bool <- lesser-than click-row, next-start + break-if found? + sandbox <- copy next-sandbox + loop + } + # return sandbox if click is in its output region + response-starting-row:num <- get *sandbox, response-starting-row-on-screen:offset + return-unless response-starting-row, 0/no-click-in-sandbox-output + click-in-response?:bool <- greater-or-equal click-row, response-starting-row + return-unless click-in-response?, 0/no-click-in-sandbox-output + return sandbox +] + +def toggle-expected-response sandbox:&:sandbox -> sandbox:&:sandbox [ + local-scope + load-ingredients + expected-response:text <- get *sandbox, expected-response:offset + { + # if expected-response is set, reset + break-unless expected-response + *sandbox <- put *sandbox, expected-response:offset, 0 + } + { + # if not, set expected response to the current response + break-if expected-response + response:text <- get *sandbox, response:offset + *sandbox <- put *sandbox, expected-response:offset, response + } +] + +# when rendering a sandbox, color it in red/green if expected response exists +after [ + { + break-unless sandbox-response + *sandbox <- put *sandbox, response-starting-row-on-screen:offset, row + expected-response:text <- get *sandbox, expected-response:offset + break-unless expected-response # fall-through to print in grey + response-is-expected?:bool <- equal expected-response, sandbox-response + { + break-if response-is-expected?:bool + row, screen <- render-text screen, sandbox-response, left, right, 1/red, row + } + { + break-unless response-is-expected?:bool + row, screen <- render-text screen, sandbox-response, left, right, 2/green, row + } + jump +render-sandbox-end + } +] + +before [ + *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0 +] diff --git a/edit/010-sandbox-test.mu b/edit/010-sandbox-test.mu deleted file mode 100644 index 39b58ecb..00000000 --- a/edit/010-sandbox-test.mu +++ /dev/null @@ -1,207 +0,0 @@ -## clicking on sandbox results to 'fix' them and turn sandboxes into tests - -scenario sandbox-click-on-result-toggles-color-to-green [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # basic recipe - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | reply 4| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - .recipe foo [ ┊ . - . reply 4 ┊─────────────────────────────────────────────────. - .] ┊0 edit copy delete . - . ┊foo . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # click on the '4' in the result - assume-console [ - left-click 5, 51 - ] - run [ - event-loop screen, console, env, resources - ] - # color toggles to green - screen-should-contain-in-color 2/green, [ - . . - . . - . . - . . - . . - . 4 . - . . - . . - ] - # cursor should remain unmoved - run [ - cursor:char <- copy 9251/␣ - print screen, cursor - ] - screen-should-contain [ - . run (F4) . - .␣ecipe foo [ ┊ . - . reply 4 ┊─────────────────────────────────────────────────. - .] ┊0 edit copy delete . - . ┊foo . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - # now change the result - # then rerun - assume-console [ - left-click 2, 11 # cursor to end of line - press backspace - type [3] - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # result turns red - screen-should-contain-in-color 1/red, [ - . . - . . - . . - . . - . . - . 3 . - . . - . . - ] -] - -# this requires tracking a couple more things -container sandbox [ - response-starting-row-on-screen:num - expected-response:text -] - -# include expected response when saving or restoring a sandbox -before [ - { - expected-response:text <- get *curr, expected-response:offset - break-unless expected-response - filename <- append filename, [.out] - resources <- dump resources, filename, expected-response - } -] - -before [ - { - filename <- append filename, [.out] - contents <- slurp resources, filename - break-unless contents - *curr <- put *curr, expected-response:offset, contents - } -] - -# clicks on sandbox responses save it as 'expected' -after [ - # check if it's inside the output of any sandbox - { - sandbox-left-margin:num <- get *current-sandbox, left:offset - click-column:num <- get t, column:offset - on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin - break-unless on-sandbox-side? - first-sandbox:&:sandbox <- get *env, sandbox:offset - break-unless first-sandbox - first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset - click-row:num <- get t, row:offset - below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins - break-unless below-sandbox-editor? - # identify the sandbox whose output is being clicked on - sandbox:&:sandbox <- find-click-in-sandbox-output env, click-row - break-unless sandbox - # toggle its expected-response, and save session - sandbox <- toggle-expected-response sandbox - save-sandboxes env, resources - hide-screen screen - screen <- render-sandbox-side screen, env, render - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - # no change in cursor - show-screen screen - loop +next-event - } -] - -def find-click-in-sandbox-output env:&:environment, click-row:num -> sandbox:&:sandbox [ - local-scope - load-ingredients - # assert click-row >= sandbox.starting-row-on-screen - sandbox:&:sandbox <- get *env, sandbox:offset - start:num <- get *sandbox, starting-row-on-screen:offset - clicked-on-sandboxes?:bool <- greater-or-equal click-row, start - assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor] - # while click-row < sandbox.next-sandbox.starting-row-on-screen - { - next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset - break-unless next-sandbox - next-start:num <- get *next-sandbox, starting-row-on-screen:offset - found?:bool <- lesser-than click-row, next-start - break-if found? - sandbox <- copy next-sandbox - loop - } - # return sandbox if click is in its output region - response-starting-row:num <- get *sandbox, response-starting-row-on-screen:offset - return-unless response-starting-row, 0/no-click-in-sandbox-output - click-in-response?:bool <- greater-or-equal click-row, response-starting-row - return-unless click-in-response?, 0/no-click-in-sandbox-output - return sandbox -] - -def toggle-expected-response sandbox:&:sandbox -> sandbox:&:sandbox [ - local-scope - load-ingredients - expected-response:text <- get *sandbox, expected-response:offset - { - # if expected-response is set, reset - break-unless expected-response - *sandbox <- put *sandbox, expected-response:offset, 0 - } - { - # if not, set expected response to the current response - break-if expected-response - response:text <- get *sandbox, response:offset - *sandbox <- put *sandbox, expected-response:offset, response - } -] - -# when rendering a sandbox, color it in red/green if expected response exists -after [ - { - break-unless sandbox-response - *sandbox <- put *sandbox, response-starting-row-on-screen:offset, row - expected-response:text <- get *sandbox, expected-response:offset - break-unless expected-response # fall-through to print in grey - response-is-expected?:bool <- equal expected-response, sandbox-response - { - break-if response-is-expected?:bool - row, screen <- render-text screen, sandbox-response, left, right, 1/red, row - } - { - break-unless response-is-expected?:bool - row, screen <- render-text screen, sandbox-response, left, right, 2/green, row - } - jump +render-sandbox-end - } -] - -before [ - *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0 -] diff --git a/edit/010-sandbox-trace.mu b/edit/010-sandbox-trace.mu new file mode 100644 index 00000000..65337127 --- /dev/null +++ b/edit/010-sandbox-trace.mu @@ -0,0 +1,253 @@ +## clicking on the code typed into a sandbox toggles its trace + +scenario sandbox-click-on-code-toggles-app-trace [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # basic recipe + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | stash [abc]| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + .recipe foo [ ┊ . + . stash [abc] ┊─────────────────────────────────────────────────. + .] ┊0 edit copy delete . + . ┊foo . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] + # click on the code in the sandbox + assume-console [ + left-click 4, 51 + ] + run [ + event-loop screen, console, env, resources + cursor:char <- copy 9251/␣ + print screen, cursor + ] + # trace now printed and cursor shouldn't have budged + screen-should-contain [ + . run (F4) . + .␣ecipe foo [ ┊ . + . stash [abc] ┊─────────────────────────────────────────────────. + .] ┊0 edit copy delete . + . ┊foo . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc . + ] + screen-should-contain-in-color 245/grey, [ + . . + . ┊ . + . ┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc . + ] + # click again on the same region + assume-console [ + left-click 4, 55 + ] + run [ + event-loop screen, console, env, resources + print screen, cursor + ] + # trace hidden again + screen-should-contain [ + . run (F4) . + .␣ecipe foo [ ┊ . + . stash [abc] ┊─────────────────────────────────────────────────. + .] ┊0 edit copy delete . + . ┊foo . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario sandbox-shows-app-trace-and-result [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # basic recipe + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | stash [abc]| + | reply 4| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + # run it + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + .recipe foo [ ┊ . + . stash [abc] ┊─────────────────────────────────────────────────. + . reply 4 ┊0 edit copy delete . + .] ┊foo . + . ┊4 . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + ] + # click on the code in the sandbox + assume-console [ + left-click 4, 51 + ] + run [ + event-loop screen, console, env, resources + ] + # trace now printed above result + screen-should-contain [ + . run (F4) . + .recipe foo [ ┊ . + . stash [abc] ┊─────────────────────────────────────────────────. + . reply 4 ┊0 edit copy delete . + .] ┊foo . + . ┊abc . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊8 instructions run . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario clicking-on-app-trace-does-nothing [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [stash 123456789] + # create and expand the trace + assume-console [ + press F4 + left-click 4, 51 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊stash 123456789 . + . ┊123456789 . + ] + # click on the stash under the edit-button region (or any of the other buttons, really) + assume-console [ + left-click 5, 57 + ] + run [ + event-loop screen, console, env, resources + ] + # no change; doesn't die + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊stash 123456789 . + . ┊123456789 . + ] +] + +container sandbox [ + trace:text + display-trace?:bool +] + +# replaced in a later layer +def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ + local-scope + load-ingredients + data:text <- get *sandbox, data:offset + response:text, _, fake-screen:&:screen, trace:text <- run-sandboxed data + *sandbox <- put *sandbox, response:offset, response + *sandbox <- put *sandbox, screen:offset, fake-screen + *sandbox <- put *sandbox, trace:offset, trace +] + +# clicks on sandbox code toggle its display-trace? flag +after [ + # check if it's inside the code of any sandbox + { + sandbox-left-margin:num <- get *current-sandbox, left:offset + click-column:num <- get t, column:offset + on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin + break-unless on-sandbox-side? + first-sandbox:&:sandbox <- get *env, sandbox:offset + break-unless first-sandbox + first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset + click-row:num <- get t, row:offset + below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins + break-unless below-sandbox-editor? + # identify the sandbox whose code is being clicked on + sandbox:&:sandbox <- find-click-in-sandbox-code env, click-row + break-unless sandbox + # toggle its display-trace? property + x:bool <- get *sandbox, display-trace?:offset + x <- not x + *sandbox <- put *sandbox, display-trace?:offset, x + hide-screen screen + screen <- render-sandbox-side screen, env, render + screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env + # no change in cursor + show-screen screen + loop +next-event + } +] + +def find-click-in-sandbox-code env:&:environment, click-row:num -> sandbox:&:sandbox [ + local-scope + load-ingredients + # assert click-row >= sandbox.starting-row-on-screen + sandbox <- get *env, sandbox:offset + start:num <- get *sandbox, starting-row-on-screen:offset + clicked-on-sandboxes?:bool <- greater-or-equal click-row, start + assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor] + # while click-row < sandbox.next-sandbox.starting-row-on-screen + { + next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset + break-unless next-sandbox + next-start:num <- get *next-sandbox, starting-row-on-screen:offset + found?:bool <- lesser-than click-row, next-start + break-if found? + sandbox <- copy next-sandbox + loop + } + # return sandbox if click is in its code region + code-ending-row:num <- get *sandbox, code-ending-row-on-screen:offset + click-above-response?:bool <- lesser-than click-row, code-ending-row + start:num <- get *sandbox, starting-row-on-screen:offset + click-below-menu?:bool <- greater-than click-row, start + click-on-sandbox-code?:bool <- and click-above-response?, click-below-menu? + { + break-if click-on-sandbox-code? + return 0/no-click-in-sandbox-output + } + return sandbox +] + +# when rendering a sandbox, dump its trace before response/warning if display-trace? property is set +after [ + { + display-trace?:bool <- get *sandbox, display-trace?:offset + break-unless display-trace? + sandbox-trace:text <- get *sandbox, trace:offset + break-unless sandbox-trace # nothing to print; move on + row, screen <- render-text screen, sandbox-trace, left, right, 245/grey, row + } + +] diff --git a/edit/011-errors.mu b/edit/011-errors.mu new file mode 100644 index 00000000..ad4d8836 --- /dev/null +++ b/edit/011-errors.mu @@ -0,0 +1,742 @@ +## handling malformed programs + +container environment [ + recipe-errors:text +] + +# copy code from recipe editor, persist to disk, load, save any errors +def! update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ + local-scope + load-ingredients + recipes:&:editor <- get *env, recipes:offset + in:text <- editor-contents recipes + resources <- dump resources, [lesson/recipes.mu], in + recipe-errors:text <- reload in + *env <- put *env, recipe-errors:offset, recipe-errors + # if recipe editor has errors, stop + { + break-unless recipe-errors + update-status screen, [errors found ], 1/red + errors-found? <- copy 1/true + return + } + errors-found? <- copy 0/false +] + +before [ + trace 11, [app], [render status] + recipe-errors:text <- get *env, recipe-errors:offset + { + break-unless recipe-errors + update-status screen, [errors found ], 1/red + } +] + +before [ + { + recipe-errors:text <- get *env, recipe-errors:offset + break-unless recipe-errors + row, screen <- render-text screen, recipe-errors, left, right, 1/red, row + } +] + +container environment [ + error-index:num # index of first sandbox with an error (or -1 if none) +] + +after [ + *result <- put *result, error-index:offset, -1 +] + +after [ + *env <- put *env, error-index:offset, -1 +] + +before [ + { + error-index:num <- get *env, error-index:offset + sandboxes-completed-successfully?:bool <- equal error-index, -1 + break-if sandboxes-completed-successfully? + errors-found? <- copy 1/true + } +] + +before [ + { + break-if recipe-errors + error-index:num <- get *env, error-index:offset + sandboxes-completed-successfully?:bool <- equal error-index, -1 + break-if sandboxes-completed-successfully? + error-index-text:text <- to-text error-index + status:text <- interpolate [errors found (_) ], error-index-text + update-status screen, status, 1/red + } +] + +container sandbox [ + errors:text +] + +def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ + local-scope + load-ingredients + data:text <- get *sandbox, data:offset + response:text, errors:text, fake-screen:&:screen, trace:text, completed?:bool <- run-sandboxed data + *sandbox <- put *sandbox, response:offset, response + *sandbox <- put *sandbox, errors:offset, errors + *sandbox <- put *sandbox, screen:offset, fake-screen + *sandbox <- put *sandbox, trace:offset, trace + { + break-if errors + break-if completed?:bool + errors <- new [took too long! +] + *sandbox <- put *sandbox, errors:offset, errors + } + { + break-unless errors + error-index:num <- get *env, error-index:offset + error-not-set?:bool <- equal error-index, -1 + break-unless error-not-set? + *env <- put *env, error-index:offset, idx + } +] + +# make sure we render any trace +after [ + { + sandbox-errors:text <- get *sandbox, errors:offset + break-unless sandbox-errors + *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0 # no response + row, screen <- render-text screen, sandbox-errors, left, right, 1/red, row + # don't try to print anything more for this sandbox + jump +render-sandbox-end + } +] + +scenario run-shows-errors-in-get [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | get 123:num, foo:offset| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + render-all screen, env, render + screen-should-contain [ + . run (F4) . + .recipe foo [ ┊foo . + . get 123:num, foo:offset ┊─────────────────────────────────────────────────. + .] ┊ . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . get 123:num, foo:offset ┊─────────────────────────────────────────────────. + .] ┊ . + . ┊ . + .foo: unknown element 'foo' in container 'number' ┊ . + .foo: first ingredient of 'get' should be a contai↩┊ . + .ner, but got '123:num' ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + screen-should-contain-in-color 1/red, [ + . errors found . + . . + . . + . . + . . + .foo: unknown element 'foo' in container 'number' . + .foo: first ingredient of 'get' should be a contai . + .ner, but got '123:num' . + . . + ] +] + +scenario run-updates-status-with-first-erroneous-sandbox [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + assume-console [ + left-click 3, 80 + # create invalid sandbox 1 + type [get foo, x:offset] + press F4 + # create invalid sandbox 0 + type [get foo, x:offset] + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # status line shows that error is in first sandbox + screen-should-contain [ + . errors found (0) run (F4) . + ] +] + +scenario run-updates-status-with-first-erroneous-sandbox-2 [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [] + assume-console [ + left-click 3, 80 + # create invalid sandbox 2 + type [get foo, x:offset] + press F4 + # create invalid sandbox 1 + type [get foo, x:offset] + press F4 + # create valid sandbox 0 + type [add 2, 2] + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # status line shows that error is in second sandbox + screen-should-contain [ + . errors found (1) run (F4) . + ] +] + +scenario run-hides-errors-from-past-sandboxes [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + ] + env:&:environment <- new-programming-environment resources, screen, [get foo, x:offset] # invalid + assume-console [ + press F4 # generate error + ] + event-loop screen, console, env, resources + assume-console [ + left-click 3, 58 + press ctrl-k + type [add 2, 2] # valid code + press F4 # update sandbox + ] + run [ + event-loop screen, console, env, resources + ] + # error should disappear + screen-should-contain [ + . run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊add 2, 2 . + . ┊4 . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario run-updates-errors-for-shape-shifting-recipes [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # define a shape-shifting recipe with an error + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo x:_elem -> z:_elem [| + | local-scope| + | load-ingredients| + | y:&:num <- copy 0| + | z <- add x, y| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo 2] + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . errors found (0) run (F4) . + .recipe foo x:_elem -> z:_elem [ ┊ . + . local-scope ┊─────────────────────────────────────────────────. + . load-ingredients ┊0 edit copy delete . + . y:&:num <- copy 0 ┊foo 2 . + . z <- add x, y ┊foo_2: 'add' requires number ingredients, but go↩. + .] ┊t 'y' . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + # now rerun everything + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # error should remain unchanged + screen-should-contain [ + . errors found (0) run (F4) . + .recipe foo x:_elem -> z:_elem [ ┊ . + . local-scope ┊─────────────────────────────────────────────────. + . load-ingredients ┊0 edit copy delete . + . y:&:num <- copy 0 ┊foo 2 . + . z <- add x, y ┊foo_3: 'add' requires number ingredients, but go↩. + .] ┊t 'y' . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # overload a well-known shape-shifting recipe + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe length l:&:list:_elem -> n:num [| + |]| + ] + ] + # call code that uses other variants of it, but not it itself + test-sandbox:text <- new [x:&:list:num <- copy 0 +to-text x] + env:&:environment <- new-programming-environment resources, screen, test-sandbox + # run it once + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + # no errors anywhere on screen (can't check anything else, since to-text will return an address) + screen-should-contain-in-color 1/red, [ + . . + . . + . . + . . + . <- . + . . + . . + . . + . . + . . + . . + . . + . . + . . + . . + ] + # rerun everything + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # still no errors + screen-should-contain-in-color 1/red, [ + . . + . . + . . + . . + . <- . + . . + . . + . . + . . + . . + . . + . . + . . + . . + . . + ] +] + +scenario run-shows-missing-type-errors [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | x <- copy 0| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . x <- copy 0 ┊─────────────────────────────────────────────────. + .] ┊ . + . ┊ . + .foo: missing type for 'x' in 'x <- copy 0' ┊ . + .foo: can't copy '0' to 'x'; types don't match ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-shows-unbalanced-bracket-errors [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # recipe is incomplete (unbalanced '[') + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo \\\[| + | x <- copy 0| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo \\[ ┊foo . + . x <- copy 0 ┊─────────────────────────────────────────────────. + . ┊ . + .9: unbalanced '\\[' for recipe ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-shows-get-on-non-container-errors [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | local-scope| + | x:&:point <- new point:type| + | get x:&:point, 1:offset| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . local-scope ┊─────────────────────────────────────────────────. + . x:&:point <- new point:type ┊ . + . get x:&:point, 1:offset ┊ . + .] ┊ . + . ┊ . + .foo: first ingredient of 'get' should be a contai↩┊ . + .ner, but got 'x:&:point' ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-shows-non-literal-get-argument-errors [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | local-scope| + | x:num <- copy 0| + | y:&:point <- new point:type| + | get *y:&:point, x:num| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . local-scope ┊─────────────────────────────────────────────────. + . x:num <- copy 0 ┊ . + . y:&:point <- new point:type ┊ . + . get *y:&:point, x:num ┊ . + .] ┊ . + . ┊ . + .foo: second ingredient of 'get' should have type ↩┊ . + .'offset', but got 'x:num' ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-shows-errors-everytime [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 15/height + # try to run a file with an error + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | local-scope| + | x:num <- copy y:num| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . local-scope ┊─────────────────────────────────────────────────. + . x:num <- copy y:num ┊ . + .] ┊ . + . ┊ . + .foo: tried to read ingredient 'y' in 'x:num <- co↩┊ . + .py y:num' but it hasn't been written to yet ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] + # rerun the file, check for the same error + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found run (F4) . + .recipe foo [ ┊foo . + . local-scope ┊─────────────────────────────────────────────────. + . x:num <- copy y:num ┊ . + .] ┊ . + . ┊ . + .foo: tried to read ingredient 'y' in 'x:num <- co↩┊ . + .py y:num' but it hasn't been written to yet ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario run-instruction-and-print-errors [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + assume-resources [ + ] + # sandbox editor contains an illegal instruction + env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset] + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that screen prints error message in red + screen-should-contain [ + . errors found (0) run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊get 1234:num, foo:offset . + . ┊unknown element 'foo' in container 'number' . + . ┊first ingredient of 'get' should be a container,↩. + . ┊ but got '1234:num' . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] + screen-should-contain-in-color 7/white, [ + . . + . . + . . + . . + . get 1234:num, foo:offset . + . . + . . + . . + ] + screen-should-contain-in-color 1/red, [ + . errors found (0) . + . . + . . + . . + . . + . unknown element 'foo' in container 'number' . + . first ingredient of 'get' should be a container, . + . but got '1234:num' . + . . + ] + screen-should-contain-in-color 245/grey, [ + . . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊ . + . ┊ . + . ┊ . + . ┊ ↩. + . ┊ . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario run-instruction-and-print-errors-only-once [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + assume-resources [ + ] + # sandbox editor contains an illegal instruction + env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset] + # run the code in the editors multiple times + assume-console [ + press F4 + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + # check that screen prints error message just once + screen-should-contain [ + . errors found (0) run (F4) . + . ┊ . + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. + . ┊0 edit copy delete . + . ┊get 1234:num, foo:offset . + . ┊unknown element 'foo' in container 'number' . + . ┊first ingredient of 'get' should be a container,↩. + . ┊ but got '1234:num' . + . ┊─────────────────────────────────────────────────. + . ┊ . + ] +] + +scenario sandbox-can-handle-infinite-loop [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 20/height + # sandbox editor will trigger an infinite loop + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | {| + | loop| + | }| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo] + # run the sandbox + assume-console [ + press F4 + ] + run [ + event-loop screen, console, env, resources + ] + screen-should-contain [ + . errors found (0) run (F4) . + .recipe foo [ ┊ . + . { ┊─────────────────────────────────────────────────. + . loop ┊0 edit copy delete . + . } ┊foo . + .] ┊took too long! . + . ┊─────────────────────────────────────────────────. + .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . + . ┊ . + ] +] + +scenario sandbox-with-errors-shows-trace [ + local-scope + trace-until 100/app # trace too long + assume-screen 100/width, 10/height + # generate a stash and a error + assume-resources [ + [lesson/recipes.mu] <- [ + |recipe foo [| + | local-scope| + | a:num <- next-ingredient| + | b:num <- next-ingredient| + | stash [dividing by], b| + | _, c:num <- divide-with-remainder a, b| + | reply b| + |]| + ] + ] + env:&:environment <- new-programming-environment resources, screen, [foo 4, 0] + # run + assume-console [ + press F4 + ] + event-loop screen, console, env, resources + # screen prints error message + screen-should-contain [ + . errors found (0) run (F4) . + .recipe foo [ ┊ . + . local-scope ┊─────────────────────────────────────────────────. + . a:num <- next-ingredient ┊0 edit copy delete . + . b:num <- next-ingredient ┊foo 4, 0 . + . stash [dividing by], b ┊foo: divide by zero in '_, c:num <- divide-with-↩. + . _, c:num <- divide-with-remainder a, b ┊remainder a, b' . + . reply b ┊─────────────────────────────────────────────────. + .] ┊ . + . ┊ . + ] + # click on the call in the sandbox + assume-console [ + left-click 4, 55 + ] + run [ + event-loop screen, console, env, resources + ] + # screen should expand trace + screen-should-contain [ + . errors found (0) run (F4) . + .recipe foo [ ┊ . + . local-scope ┊─────────────────────────────────────────────────. + . a:num <- next-ingredient ┊0 edit copy delete . + . b:num <- next-ingredient ┊foo 4, 0 . + . stash [dividing by], b ┊dividing by 0 . + . _, c:num <- divide-with-remainder a, b ┊14 instructions run . + . reply b ┊foo: divide by zero in '_, c:num <- divide-with-↩. + .] ┊remainder a, b' . + . ┊─────────────────────────────────────────────────. + ] +] diff --git a/edit/011-sandbox-trace.mu b/edit/011-sandbox-trace.mu deleted file mode 100644 index 65337127..00000000 --- a/edit/011-sandbox-trace.mu +++ /dev/null @@ -1,253 +0,0 @@ -## clicking on the code typed into a sandbox toggles its trace - -scenario sandbox-click-on-code-toggles-app-trace [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # basic recipe - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | stash [abc]| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - .recipe foo [ ┊ . - . stash [abc] ┊─────────────────────────────────────────────────. - .] ┊0 edit copy delete . - . ┊foo . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] - # click on the code in the sandbox - assume-console [ - left-click 4, 51 - ] - run [ - event-loop screen, console, env, resources - cursor:char <- copy 9251/␣ - print screen, cursor - ] - # trace now printed and cursor shouldn't have budged - screen-should-contain [ - . run (F4) . - .␣ecipe foo [ ┊ . - . stash [abc] ┊─────────────────────────────────────────────────. - .] ┊0 edit copy delete . - . ┊foo . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc . - ] - screen-should-contain-in-color 245/grey, [ - . . - . ┊ . - . ┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊abc . - ] - # click again on the same region - assume-console [ - left-click 4, 55 - ] - run [ - event-loop screen, console, env, resources - print screen, cursor - ] - # trace hidden again - screen-should-contain [ - . run (F4) . - .␣ecipe foo [ ┊ . - . stash [abc] ┊─────────────────────────────────────────────────. - .] ┊0 edit copy delete . - . ┊foo . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario sandbox-shows-app-trace-and-result [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # basic recipe - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | stash [abc]| - | reply 4| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - # run it - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - .recipe foo [ ┊ . - . stash [abc] ┊─────────────────────────────────────────────────. - . reply 4 ┊0 edit copy delete . - .] ┊foo . - . ┊4 . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - ] - # click on the code in the sandbox - assume-console [ - left-click 4, 51 - ] - run [ - event-loop screen, console, env, resources - ] - # trace now printed above result - screen-should-contain [ - . run (F4) . - .recipe foo [ ┊ . - . stash [abc] ┊─────────────────────────────────────────────────. - . reply 4 ┊0 edit copy delete . - .] ┊foo . - . ┊abc . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊8 instructions run . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario clicking-on-app-trace-does-nothing [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [stash 123456789] - # create and expand the trace - assume-console [ - press F4 - left-click 4, 51 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊stash 123456789 . - . ┊123456789 . - ] - # click on the stash under the edit-button region (or any of the other buttons, really) - assume-console [ - left-click 5, 57 - ] - run [ - event-loop screen, console, env, resources - ] - # no change; doesn't die - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊stash 123456789 . - . ┊123456789 . - ] -] - -container sandbox [ - trace:text - display-trace?:bool -] - -# replaced in a later layer -def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ - local-scope - load-ingredients - data:text <- get *sandbox, data:offset - response:text, _, fake-screen:&:screen, trace:text <- run-sandboxed data - *sandbox <- put *sandbox, response:offset, response - *sandbox <- put *sandbox, screen:offset, fake-screen - *sandbox <- put *sandbox, trace:offset, trace -] - -# clicks on sandbox code toggle its display-trace? flag -after [ - # check if it's inside the code of any sandbox - { - sandbox-left-margin:num <- get *current-sandbox, left:offset - click-column:num <- get t, column:offset - on-sandbox-side?:bool <- greater-or-equal click-column, sandbox-left-margin - break-unless on-sandbox-side? - first-sandbox:&:sandbox <- get *env, sandbox:offset - break-unless first-sandbox - first-sandbox-begins:num <- get *first-sandbox, starting-row-on-screen:offset - click-row:num <- get t, row:offset - below-sandbox-editor?:bool <- greater-or-equal click-row, first-sandbox-begins - break-unless below-sandbox-editor? - # identify the sandbox whose code is being clicked on - sandbox:&:sandbox <- find-click-in-sandbox-code env, click-row - break-unless sandbox - # toggle its display-trace? property - x:bool <- get *sandbox, display-trace?:offset - x <- not x - *sandbox <- put *sandbox, display-trace?:offset, x - hide-screen screen - screen <- render-sandbox-side screen, env, render - screen <- update-cursor screen, recipes, current-sandbox, sandbox-in-focus?, env - # no change in cursor - show-screen screen - loop +next-event - } -] - -def find-click-in-sandbox-code env:&:environment, click-row:num -> sandbox:&:sandbox [ - local-scope - load-ingredients - # assert click-row >= sandbox.starting-row-on-screen - sandbox <- get *env, sandbox:offset - start:num <- get *sandbox, starting-row-on-screen:offset - clicked-on-sandboxes?:bool <- greater-or-equal click-row, start - assert clicked-on-sandboxes?, [extract-sandbox called on click to sandbox editor] - # while click-row < sandbox.next-sandbox.starting-row-on-screen - { - next-sandbox:&:sandbox <- get *sandbox, next-sandbox:offset - break-unless next-sandbox - next-start:num <- get *next-sandbox, starting-row-on-screen:offset - found?:bool <- lesser-than click-row, next-start - break-if found? - sandbox <- copy next-sandbox - loop - } - # return sandbox if click is in its code region - code-ending-row:num <- get *sandbox, code-ending-row-on-screen:offset - click-above-response?:bool <- lesser-than click-row, code-ending-row - start:num <- get *sandbox, starting-row-on-screen:offset - click-below-menu?:bool <- greater-than click-row, start - click-on-sandbox-code?:bool <- and click-above-response?, click-below-menu? - { - break-if click-on-sandbox-code? - return 0/no-click-in-sandbox-output - } - return sandbox -] - -# when rendering a sandbox, dump its trace before response/warning if display-trace? property is set -after [ - { - display-trace?:bool <- get *sandbox, display-trace?:offset - break-unless display-trace? - sandbox-trace:text <- get *sandbox, trace:offset - break-unless sandbox-trace # nothing to print; move on - row, screen <- render-text screen, sandbox-trace, left, right, 245/grey, row - } - -] diff --git a/edit/012-editor-undo.mu b/edit/012-editor-undo.mu new file mode 100644 index 00000000..c2f6bca3 --- /dev/null +++ b/edit/012-editor-undo.mu @@ -0,0 +1,2109 @@ +## undo/redo + +# for every undoable event, create a type of *operation* that contains all the +# information needed to reverse it +exclusive-container operation [ + typing:insert-operation + move:move-operation + delete:delete-operation +] + +container insert-operation [ + before-row:num + before-column:num + before-top-of-screen:&:duplex-list:char + after-row:num + after-column:num + after-top-of-screen:&:duplex-list:char + # inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate + insert-from:&:duplex-list:char + insert-until:&:duplex-list:char + tag:num # event causing this operation; might be used to coalesce runs of similar events + # 0: no coalesce (enter+indent) + # 1: regular alphanumeric characters +] + +container move-operation [ + before-row:num + before-column:num + before-top-of-screen:&:duplex-list:char + after-row:num + after-column:num + after-top-of-screen:&:duplex-list:char + tag:num # event causing this operation; might be used to coalesce runs of similar events + # 0: no coalesce (touch events, etc) + # 1: left arrow + # 2: right arrow + # 3: up arrow + # 4: down arrow +] + +container delete-operation [ + before-row:num + before-column:num + before-top-of-screen:&:duplex-list:char + after-row:num + after-column:num + after-top-of-screen:&:duplex-list:char + deleted-text:&:duplex-list:char + delete-from:&:duplex-list:char + delete-until:&:duplex-list:char + tag:num # event causing this operation; might be used to coalesce runs of similar events + # 0: no coalesce (ctrl-k, ctrl-u) + # 1: backspace + # 2: delete +] + +# every editor accumulates a list of operations to undo/redo +container editor [ + undo:&:list:&:operation + redo:&:list:&:operation +] + +# ctrl-z - undo operation +after [ + { + undo?:bool <- equal c, 26/ctrl-z + break-unless undo? + undo:&:list:&:operation <- get *editor, undo:offset + break-unless undo + op:&:operation <- first undo + undo <- rest undo + *editor <- put *editor, undo:offset, undo + redo:&:list:&:operation <- get *editor, redo:offset + redo <- push op, redo + *editor <- put *editor, redo:offset, redo + + return 1/go-render + } +] + +# ctrl-y - redo operation +after [ + { + redo?:bool <- equal c, 25/ctrl-y + break-unless redo? + redo:&:list:&:operation <- get *editor, redo:offset + break-unless redo + op:&:operation <- first redo + redo <- rest redo + *editor <- put *editor, redo:offset, redo + undo:&:list:&:operation <- get *editor, undo:offset + undo <- push op, undo + *editor <- put *editor, undo:offset, undo + + return 1/go-render + } +] + +# undo typing + +scenario editor-can-undo-typing [ + local-scope + # create an editor and type a character + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + assume-console [ + type [0] + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # character should be gone + screen-should-contain [ + . . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .1 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# save operation to undo +after [ + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset +] +before [ + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + undo:&:list:&:operation <- get *editor, undo:offset + { + # if previous operation was an insert, coalesce this operation with it + break-unless undo + op:&:operation <- first undo + typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant + break-unless is-insert? + previous-coalesce-tag:num <- get typing, tag:offset + break-unless previous-coalesce-tag + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + insert-until:&:duplex-list:char <- next before-cursor + typing <- put typing, insert-until:offset, insert-until + typing <- put typing, after-row:offset, cursor-row + typing <- put typing, after-column:offset, cursor-column + typing <- put typing, after-top-of-screen:offset, top-after + *op <- merge 0/insert-operation, typing + break +done-adding-insert-operation + } + # if not, create a new operation + insert-from:&:duplex-list:char <- next cursor-before + insert-to:&:duplex-list:char <- next insert-from + op:&:operation <- new operation:type + *op <- merge 0/insert-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 1/coalesce + editor <- add-operation editor, op + +done-adding-insert-operation +] + +# enter operations never coalesce with typing before or after +after [ + cursor-row-before:num <- copy cursor-row + cursor-column-before:num <- copy cursor-column + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset +] +before [ + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-row:offset + # never coalesce + insert-from:&:duplex-list:char <- next cursor-before + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + insert-to:&:duplex-list:char <- next before-cursor + op:&:operation <- new operation:type + *op <- merge 0/insert-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 0/never-coalesce + editor <- add-operation editor, op +] + +# Everytime you add a new operation to the undo stack, be sure to clear the +# redo stack, because it's now obsolete. +# Beware: since we're counting cursor moves as operations, this means just +# moving the cursor can lose work on the undo stack. +def add-operation editor:&:editor, op:&:operation -> editor:&:editor [ + local-scope + load-ingredients + undo:&:list:&:operation <- get *editor, undo:offset + undo <- push op undo + *editor <- put *editor, undo:offset, undo + redo:&:list:&:operation <- get *editor, redo:offset + redo <- copy 0 + *editor <- put *editor, redo:offset, redo +] + +after [ + { + typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant + break-unless is-insert? + start:&:duplex-list:char <- get typing, insert-from:offset + end:&:duplex-list:char <- get typing, insert-until:offset + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + before-cursor:&:duplex-list:char <- prev start + *editor <- put *editor, before-cursor:offset, before-cursor + remove-between before-cursor, end + cursor-row <- get typing, before-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get typing, before-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get typing, before-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +scenario editor-can-undo-typing-multiple [ + local-scope + # create an editor and type multiple characters + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + assume-console [ + type [012] + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # all characters must be gone + screen-should-contain [ + . . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-can-undo-typing-multiple-2 [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + e:&:editor <- new-editor [a], 0/left, 10/right + editor-render screen, e + # type some characters + assume-console [ + type [012] + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .012a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # back to original text + screen-should-contain [ + . . + .a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor should be in the right place + assume-console [ + type [3] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .3a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-can-undo-typing-enter [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + e:&:editor <- new-editor [ abc], 0/left, 10/right + editor-render screen, e + # new line + assume-console [ + left-click 1, 8 + press enter + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + . abc . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # line is indented + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 2 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 5 + ] + # back to original text + screen-should-contain [ + . . + . abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor should be at end of line + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + . abc1 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# redo typing + +scenario editor-redo-typing [ + local-scope + # create an editor, type something, undo + assume-screen 10/width, 5/height + e:&:editor <- new-editor [a], 0/left, 10/right + editor-render screen, e + assume-console [ + type [012] + press ctrl-z + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # all characters must be back + screen-should-contain [ + . . + .012a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor should be in the right place + assume-console [ + type [3] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .0123a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + { + typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant + break-unless is-insert? + before-cursor <- get *editor, before-cursor:offset + insert-from:&:duplex-list:char <- get typing, insert-from:offset # ignore insert-to because it's already been spliced away + # assert insert-to matches next(before-cursor) + insert-range before-cursor, insert-from + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + cursor-row <- get typing, after-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get typing, after-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get typing, after-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +scenario editor-redo-typing-empty [ + local-scope + # create an editor, type something, undo + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + assume-console [ + type [012] + press ctrl-z + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # all characters must be back + screen-should-contain [ + . . + .012 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor should be in the right place + assume-console [ + type [3] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .0123 . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +scenario editor-work-clears-redo-stack [ + local-scope + # create an editor with some text, do some work, undo + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + assume-console [ + type [1] + press ctrl-z + ] + editor-event-loop screen, console, e + # do some more work + assume-console [ + type [0] + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .0abc . + .def . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # nothing should happen + screen-should-contain [ + . . + .0abc . + .def . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-redo-typing-and-enter-and-tab [ + local-scope + # create an editor + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + # insert some text and tabs, hit enter, some more text and tabs + assume-console [ + press tab + type [ab] + press tab + type [cd] + press enter + press tab + type [efg] + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + . ab cd . + . efg . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 7 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # typing in second line deleted, but not indent + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 2 + ] + screen-should-contain [ + . . + . ab cd . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # undo again + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # indent and newline deleted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 8 + ] + screen-should-contain [ + . . + . ab cd . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # undo again + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # empty screen + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + screen-should-contain [ + . . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 8 + ] + screen-should-contain [ + . . + . ab cd . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo again + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # newline and indent inserted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 2 + ] + screen-should-contain [ + . . + . ab cd . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo again + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # indent and newline deleted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 7 + ] + screen-should-contain [ + . . + . ab cd . + . efg . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# undo cursor movement and scroll + +scenario editor-can-undo-touch [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor + assume-console [ + left-click 3, 1 + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # click undone + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .1abc . + .def . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +after [ + cursor-row-before:num <- get *editor, cursor-row:offset + cursor-column-before:num <- get *editor, cursor-column:offset + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset +] +before [ + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + { + break-unless undo-coalesce-tag + # if previous operation was also a move, and also had the same coalesce + # tag, coalesce with it + undo:&:list:&:operation <- get *editor, undo:offset + break-unless undo + op:&:operation <- first undo + move:move-operation, is-move?:bool <- maybe-convert *op, move:variant + break-unless is-move? + previous-coalesce-tag:num <- get move, tag:offset + coalesce?:bool <- equal undo-coalesce-tag, previous-coalesce-tag + break-unless coalesce? + move <- put move, after-row:offset, cursor-row + move <- put move, after-column:offset, cursor-column + move <- put move, after-top-of-screen:offset, top-after + *op <- merge 1/move-operation, move + break +done-adding-move-operation + } + op:&:operation <- new operation:type + *op <- merge 1/move-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, undo-coalesce-tag + editor <- add-operation editor, op + +done-adding-move-operation +] + +after [ + { + move:move-operation, is-move?:bool <- maybe-convert *op, move:variant + break-unless is-move? + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + cursor-row <- get move, before-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get move, before-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get move, before-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +scenario editor-can-undo-scroll [ + local-scope + # screen has 1 line for menu + 3 lines + assume-screen 5/width, 4/height + # editor contains a wrapped line + contents:text <- new [a +b +cdefgh] + e:&:editor <- new-editor contents, 0/left, 5/right + # position cursor at end of screen and try to move right + assume-console [ + left-click 3, 3 + press right-arrow + ] + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + # screen scrolls + screen-should-contain [ + . . + .b . + .cdef↩. + .gh . + ] + memory-should-contain [ + 3 <- 3 + 4 <- 0 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moved back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 3 + 4 <- 3 + ] + # scroll undone + screen-should-contain [ + . . + .a . + .b . + .cdef↩. + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .b . + .cde1↩. + .fgh . + ] +] + +scenario editor-can-undo-left-arrow [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor + assume-console [ + left-click 3, 1 + press left-arrow + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 3 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def . + .g1hi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-up-arrow [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor + assume-console [ + left-click 3, 1 + press up-arrow + ] + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 3 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def . + .g1hi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-down-arrow [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor + assume-console [ + left-click 2, 1 + press down-arrow + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .d1ef . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-ctrl-f [ + local-scope + # create an editor with multiple pages of text + assume-screen 10/width, 5/height + contents:text <- new [a +b +c +d +e +f] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # scroll the page + assume-console [ + press ctrl-f + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # screen should again show page 1 + screen-should-contain [ + . . + .a . + .b . + .c . + .d . + ] +] + +scenario editor-can-undo-page-down [ + local-scope + # create an editor with multiple pages of text + assume-screen 10/width, 5/height + contents:text <- new [a +b +c +d +e +f] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # scroll the page + assume-console [ + press page-down + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # screen should again show page 1 + screen-should-contain [ + . . + .a . + .b . + .c . + .d . + ] +] + +scenario editor-can-undo-ctrl-b [ + local-scope + # create an editor with multiple pages of text + assume-screen 10/width, 5/height + contents:text <- new [a +b +c +d +e +f] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # scroll the page down and up + assume-console [ + press page-down + press ctrl-b + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # screen should again show page 2 + screen-should-contain [ + . . + .d . + .e . + .f . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-page-up [ + local-scope + # create an editor with multiple pages of text + assume-screen 10/width, 5/height + contents:text <- new [a +b +c +d +e +f] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # scroll the page down and up + assume-console [ + press page-down + press page-up + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # screen should again show page 2 + screen-should-contain [ + . . + .d . + .e . + .f . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-ctrl-a [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor, then to start of line + assume-console [ + left-click 2, 1 + press ctrl-a + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .d1ef . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-home [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor, then to start of line + assume-console [ + left-click 2, 1 + press home + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .d1ef . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-ctrl-e [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor, then to start of line + assume-console [ + left-click 2, 1 + press ctrl-e + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .d1ef . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-end [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor, then to start of line + assume-console [ + left-click 2, 1 + press end + ] + editor-event-loop screen, console, e + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves back + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .d1ef . + .ghi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +scenario editor-can-undo-multiple-arrows-in-the-same-direction [ + local-scope + # create an editor with some text + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # move the cursor + assume-console [ + left-click 2, 1 + press right-arrow + press right-arrow + press up-arrow + ] + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # up-arrow is undone + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 3 + ] + # undo again + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + # both right-arrows are undone + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 2 + 4 <- 1 + ] +] + +# redo cursor movement and scroll + +scenario editor-redo-touch [ + local-scope + # create an editor with some text, click on a character, undo + assume-screen 10/width, 5/height + contents:text <- new [abc +def +ghi] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + assume-console [ + left-click 3, 1 + press ctrl-z + ] + editor-event-loop screen, console, e + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # cursor moves to left-click + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 3 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def . + .g1hi . + .┈┈┈┈┈┈┈┈┈┈. + ] +] + +after [ + { + move:move-operation, is-move?:bool <- maybe-convert *op, move:variant + break-unless is-move? + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + cursor-row <- get move, after-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get move, after-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get move, after-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +scenario editor-separates-undo-insert-from-undo-cursor-move [ + local-scope + # create an editor, type some text, move the cursor, type some more text + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + assume-console [ + type [abc] + left-click 1, 1 + type [d] + ] + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + screen-should-contain [ + . . + .adbc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # last letter typed is deleted + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # undo again + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # no change to screen; cursor moves + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + # undo again + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # screen empty + screen-should-contain [ + . . + . . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # first insert + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + # redo again + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # cursor moves + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # cursor moves + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # redo again + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + # second insert + screen-should-contain [ + . . + .adbc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] +] + +# undo backspace + +scenario editor-can-undo-and-redo-backspace [ + local-scope + # create an editor + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + # insert some text and hit backspace + assume-console [ + type [abc] + press backspace + press backspace + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 3 + ] + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + screen-should-contain [ + . . + .a . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +# save operation to undo +after [ + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset +] +before [ + { + break-unless backspaced-cell # backspace failed; don't add an undo operation + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-row:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + undo:&:list:&:operation <- get *editor, undo:offset + { + # if previous operation was an insert, coalesce this operation with it + break-unless undo + op:&:operation <- first undo + deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant + break-unless is-delete? + previous-coalesce-tag:num <- get deletion, tag:offset + coalesce?:bool <- equal previous-coalesce-tag, 1/coalesce-backspace + break-unless coalesce? + deletion <- put deletion, delete-from:offset, before-cursor + backspaced-so-far:&:duplex-list:char <- get deletion, deleted-text:offset + insert-range backspaced-cell, backspaced-so-far + deletion <- put deletion, deleted-text:offset, backspaced-cell + deletion <- put deletion, after-row:offset, cursor-row + deletion <- put deletion, after-column:offset, cursor-column + deletion <- put deletion, after-top-of-screen:offset, top-after + *op <- merge 2/delete-operation, deletion + break +done-adding-backspace-operation + } + # if not, create a new operation + op:&:operation <- new operation:type + deleted-until:&:duplex-list:char <- next before-cursor + *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, backspaced-cell/deleted, before-cursor/delete-from, deleted-until, 1/coalesce-backspace + editor <- add-operation editor, op + +done-adding-backspace-operation + } +] + +after [ + { + deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant + break-unless is-delete? + anchor:&:duplex-list:char <- get deletion, delete-from:offset + break-unless anchor + deleted:&:duplex-list:char <- get deletion, deleted-text:offset + old-cursor:&:duplex-list:char <- last deleted + insert-range anchor, deleted + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + before-cursor <- copy old-cursor + cursor-row <- get deletion, before-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get deletion, before-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get deletion, before-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +after [ + { + deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant + break-unless is-delete? + start:&:duplex-list:char <- get deletion, delete-from:offset + end:&:duplex-list:char <- get deletion, delete-until:offset + data:&:duplex-list:char <- get *editor, data:offset + remove-between start, end + # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen + cursor-row <- get deletion, after-row:offset + *editor <- put *editor, cursor-row:offset, cursor-row + cursor-column <- get deletion, after-column:offset + *editor <- put *editor, cursor-column:offset, cursor-column + top:&:duplex-list:char <- get deletion, before-top-of-screen:offset + *editor <- put *editor, top-of-screen:offset, top + } +] + +# undo delete + +scenario editor-can-undo-and-redo-delete [ + local-scope + # create an editor + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + # insert some text and hit delete and backspace a few times + assume-console [ + type [abcdef] + left-click 1, 2 + press delete + press backspace + press delete + press delete + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .af . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # undo deletes + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + screen-should-contain [ + . . + .adef . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # undo backspace + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + screen-should-contain [ + . . + .abdef . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # undo first delete + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + screen-should-contain [ + . . + .abcdef . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo first delete + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + screen-should-contain [ + . . + .abdef . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo backspace + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + screen-should-contain [ + . . + .adef . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + # redo deletes + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + screen-should-contain [ + . . + .af . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset +] +before [ + { + break-unless deleted-cell # delete failed; don't add an undo operation + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + undo:&:list:&:operation <- get *editor, undo:offset + { + # if previous operation was an insert, coalesce this operation with it + break-unless undo + op:&:operation <- first undo + deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant + break-unless is-delete? + previous-coalesce-tag:num <- get deletion, tag:offset + coalesce?:bool <- equal previous-coalesce-tag, 2/coalesce-delete + break-unless coalesce? + delete-until:&:duplex-list:char <- next before-cursor + deletion <- put deletion, delete-until:offset, delete-until + deleted-so-far:&:duplex-list:char <- get deletion, deleted-text:offset + deleted-so-far <- append deleted-so-far, deleted-cell + deletion <- put deletion, deleted-text:offset, deleted-so-far + deletion <- put deletion, after-row:offset, cursor-row + deletion <- put deletion, after-column:offset, cursor-column + deletion <- put deletion, after-top-of-screen:offset, top-after + *op <- merge 2/delete-operation, deletion + break +done-adding-delete-operation + } + # if not, create a new operation + op:&:operation <- new operation:type + deleted-until:&:duplex-list:char <- next before-cursor + *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cell/deleted, before-cursor/delete-from, deleted-until, 2/coalesce-delete + editor <- add-operation editor, op + +done-adding-delete-operation + } +] + +# undo ctrl-k + +scenario editor-can-undo-and-redo-ctrl-k [ + local-scope + # create an editor + assume-screen 10/width, 5/height + contents:text <- new [abc +def] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # insert some text and hit delete and backspace a few times + assume-console [ + left-click 1, 1 + press ctrl-k + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .a . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + screen-should-contain [ + . . + .a . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 1 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .a1 . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset +] +before [ + { + break-unless deleted-cells # delete failed; don't add an undo operation + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + deleted-until:&:duplex-list:char <- next before-cursor + op:&:operation <- new operation:type + *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce + editor <- add-operation editor, op + +done-adding-delete-operation + } +] + +# undo ctrl-u + +scenario editor-can-undo-and-redo-ctrl-u [ + local-scope + # create an editor + assume-screen 10/width, 5/height + contents:text <- new [abc +def] + e:&:editor <- new-editor contents, 0/left, 10/right + editor-render screen, e + # insert some text and hit delete and backspace a few times + assume-console [ + left-click 1, 2 + press ctrl-u + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .c . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + # undo + assume-console [ + press ctrl-z + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .abc . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 2 + ] + # redo + assume-console [ + press ctrl-y + ] + run [ + editor-event-loop screen, console, e + ] + # first line inserted + screen-should-contain [ + . . + .c . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + memory-should-contain [ + 3 <- 1 + 4 <- 0 + ] + # cursor should be in the right place + assume-console [ + type [1] + ] + run [ + editor-event-loop screen, console, e + ] + screen-should-contain [ + . . + .1c . + .def . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] + +after [ + top-before:&:duplex-list:char <- get *editor, top-of-screen:offset +] +before [ + { + break-unless deleted-cells # delete failed; don't add an undo operation + top-after:&:duplex-list:char <- get *editor, top-of-screen:offset + op:&:operation <- new operation:type + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + deleted-until:&:duplex-list:char <- next before-cursor + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce + editor <- add-operation editor, op + +done-adding-delete-operation + } +] + +scenario editor-can-undo-and-redo-ctrl-u-2 [ + local-scope + # create an editor + assume-screen 10/width, 5/height + e:&:editor <- new-editor [], 0/left, 10/right + editor-render screen, e + # insert some text and hit delete and backspace a few times + assume-console [ + type [abc] + press ctrl-u + press ctrl-z + ] + editor-event-loop screen, console, e + screen-should-contain [ + . . + .abc . + .┈┈┈┈┈┈┈┈┈┈. + . . + ] +] diff --git a/edit/012-errors.mu b/edit/012-errors.mu deleted file mode 100644 index ad4d8836..00000000 --- a/edit/012-errors.mu +++ /dev/null @@ -1,742 +0,0 @@ -## handling malformed programs - -container environment [ - recipe-errors:text -] - -# copy code from recipe editor, persist to disk, load, save any errors -def! update-recipes env:&:environment, resources:&:resources, screen:&:screen -> errors-found?:bool, env:&:environment, resources:&:resources, screen:&:screen [ - local-scope - load-ingredients - recipes:&:editor <- get *env, recipes:offset - in:text <- editor-contents recipes - resources <- dump resources, [lesson/recipes.mu], in - recipe-errors:text <- reload in - *env <- put *env, recipe-errors:offset, recipe-errors - # if recipe editor has errors, stop - { - break-unless recipe-errors - update-status screen, [errors found ], 1/red - errors-found? <- copy 1/true - return - } - errors-found? <- copy 0/false -] - -before [ - trace 11, [app], [render status] - recipe-errors:text <- get *env, recipe-errors:offset - { - break-unless recipe-errors - update-status screen, [errors found ], 1/red - } -] - -before [ - { - recipe-errors:text <- get *env, recipe-errors:offset - break-unless recipe-errors - row, screen <- render-text screen, recipe-errors, left, right, 1/red, row - } -] - -container environment [ - error-index:num # index of first sandbox with an error (or -1 if none) -] - -after [ - *result <- put *result, error-index:offset, -1 -] - -after [ - *env <- put *env, error-index:offset, -1 -] - -before [ - { - error-index:num <- get *env, error-index:offset - sandboxes-completed-successfully?:bool <- equal error-index, -1 - break-if sandboxes-completed-successfully? - errors-found? <- copy 1/true - } -] - -before [ - { - break-if recipe-errors - error-index:num <- get *env, error-index:offset - sandboxes-completed-successfully?:bool <- equal error-index, -1 - break-if sandboxes-completed-successfully? - error-index-text:text <- to-text error-index - status:text <- interpolate [errors found (_) ], error-index-text - update-status screen, status, 1/red - } -] - -container sandbox [ - errors:text -] - -def! update-sandbox sandbox:&:sandbox, env:&:environment, idx:num -> sandbox:&:sandbox, env:&:environment [ - local-scope - load-ingredients - data:text <- get *sandbox, data:offset - response:text, errors:text, fake-screen:&:screen, trace:text, completed?:bool <- run-sandboxed data - *sandbox <- put *sandbox, response:offset, response - *sandbox <- put *sandbox, errors:offset, errors - *sandbox <- put *sandbox, screen:offset, fake-screen - *sandbox <- put *sandbox, trace:offset, trace - { - break-if errors - break-if completed?:bool - errors <- new [took too long! -] - *sandbox <- put *sandbox, errors:offset, errors - } - { - break-unless errors - error-index:num <- get *env, error-index:offset - error-not-set?:bool <- equal error-index, -1 - break-unless error-not-set? - *env <- put *env, error-index:offset, idx - } -] - -# make sure we render any trace -after [ - { - sandbox-errors:text <- get *sandbox, errors:offset - break-unless sandbox-errors - *sandbox <- put *sandbox, response-starting-row-on-screen:offset, 0 # no response - row, screen <- render-text screen, sandbox-errors, left, right, 1/red, row - # don't try to print anything more for this sandbox - jump +render-sandbox-end - } -] - -scenario run-shows-errors-in-get [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | get 123:num, foo:offset| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - render-all screen, env, render - screen-should-contain [ - . run (F4) . - .recipe foo [ ┊foo . - . get 123:num, foo:offset ┊─────────────────────────────────────────────────. - .] ┊ . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . get 123:num, foo:offset ┊─────────────────────────────────────────────────. - .] ┊ . - . ┊ . - .foo: unknown element 'foo' in container 'number' ┊ . - .foo: first ingredient of 'get' should be a contai↩┊ . - .ner, but got '123:num' ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - screen-should-contain-in-color 1/red, [ - . errors found . - . . - . . - . . - . . - .foo: unknown element 'foo' in container 'number' . - .foo: first ingredient of 'get' should be a contai . - .ner, but got '123:num' . - . . - ] -] - -scenario run-updates-status-with-first-erroneous-sandbox [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - assume-console [ - left-click 3, 80 - # create invalid sandbox 1 - type [get foo, x:offset] - press F4 - # create invalid sandbox 0 - type [get foo, x:offset] - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # status line shows that error is in first sandbox - screen-should-contain [ - . errors found (0) run (F4) . - ] -] - -scenario run-updates-status-with-first-erroneous-sandbox-2 [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [] - assume-console [ - left-click 3, 80 - # create invalid sandbox 2 - type [get foo, x:offset] - press F4 - # create invalid sandbox 1 - type [get foo, x:offset] - press F4 - # create valid sandbox 0 - type [add 2, 2] - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # status line shows that error is in second sandbox - screen-should-contain [ - . errors found (1) run (F4) . - ] -] - -scenario run-hides-errors-from-past-sandboxes [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - ] - env:&:environment <- new-programming-environment resources, screen, [get foo, x:offset] # invalid - assume-console [ - press F4 # generate error - ] - event-loop screen, console, env, resources - assume-console [ - left-click 3, 58 - press ctrl-k - type [add 2, 2] # valid code - press F4 # update sandbox - ] - run [ - event-loop screen, console, env, resources - ] - # error should disappear - screen-should-contain [ - . run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊add 2, 2 . - . ┊4 . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario run-updates-errors-for-shape-shifting-recipes [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - # define a shape-shifting recipe with an error - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo x:_elem -> z:_elem [| - | local-scope| - | load-ingredients| - | y:&:num <- copy 0| - | z <- add x, y| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo 2] - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . errors found (0) run (F4) . - .recipe foo x:_elem -> z:_elem [ ┊ . - . local-scope ┊─────────────────────────────────────────────────. - . load-ingredients ┊0 edit copy delete . - . y:&:num <- copy 0 ┊foo 2 . - . z <- add x, y ┊foo_2: 'add' requires number ingredients, but go↩. - .] ┊t 'y' . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - # now rerun everything - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # error should remain unchanged - screen-should-contain [ - . errors found (0) run (F4) . - .recipe foo x:_elem -> z:_elem [ ┊ . - . local-scope ┊─────────────────────────────────────────────────. - . load-ingredients ┊0 edit copy delete . - . y:&:num <- copy 0 ┊foo 2 . - . z <- add x, y ┊foo_3: 'add' requires number ingredients, but go↩. - .] ┊t 'y' . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-avoids-spurious-errors-on-reloading-shape-shifting-recipes [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - # overload a well-known shape-shifting recipe - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe length l:&:list:_elem -> n:num [| - |]| - ] - ] - # call code that uses other variants of it, but not it itself - test-sandbox:text <- new [x:&:list:num <- copy 0 -to-text x] - env:&:environment <- new-programming-environment resources, screen, test-sandbox - # run it once - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - # no errors anywhere on screen (can't check anything else, since to-text will return an address) - screen-should-contain-in-color 1/red, [ - . . - . . - . . - . . - . <- . - . . - . . - . . - . . - . . - . . - . . - . . - . . - . . - ] - # rerun everything - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # still no errors - screen-should-contain-in-color 1/red, [ - . . - . . - . . - . . - . <- . - . . - . . - . . - . . - . . - . . - . . - . . - . . - . . - ] -] - -scenario run-shows-missing-type-errors [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | x <- copy 0| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . x <- copy 0 ┊─────────────────────────────────────────────────. - .] ┊ . - . ┊ . - .foo: missing type for 'x' in 'x <- copy 0' ┊ . - .foo: can't copy '0' to 'x'; types don't match ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-shows-unbalanced-bracket-errors [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - # recipe is incomplete (unbalanced '[') - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo \\\[| - | x <- copy 0| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo \\[ ┊foo . - . x <- copy 0 ┊─────────────────────────────────────────────────. - . ┊ . - .9: unbalanced '\\[' for recipe ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-shows-get-on-non-container-errors [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | local-scope| - | x:&:point <- new point:type| - | get x:&:point, 1:offset| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . local-scope ┊─────────────────────────────────────────────────. - . x:&:point <- new point:type ┊ . - . get x:&:point, 1:offset ┊ . - .] ┊ . - . ┊ . - .foo: first ingredient of 'get' should be a contai↩┊ . - .ner, but got 'x:&:point' ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-shows-non-literal-get-argument-errors [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | local-scope| - | x:num <- copy 0| - | y:&:point <- new point:type| - | get *y:&:point, x:num| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . local-scope ┊─────────────────────────────────────────────────. - . x:num <- copy 0 ┊ . - . y:&:point <- new point:type ┊ . - . get *y:&:point, x:num ┊ . - .] ┊ . - . ┊ . - .foo: second ingredient of 'get' should have type ↩┊ . - .'offset', but got 'x:num' ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-shows-errors-everytime [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 15/height - # try to run a file with an error - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | local-scope| - | x:num <- copy y:num| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . local-scope ┊─────────────────────────────────────────────────. - . x:num <- copy y:num ┊ . - .] ┊ . - . ┊ . - .foo: tried to read ingredient 'y' in 'x:num <- co↩┊ . - .py y:num' but it hasn't been written to yet ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] - # rerun the file, check for the same error - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found run (F4) . - .recipe foo [ ┊foo . - . local-scope ┊─────────────────────────────────────────────────. - . x:num <- copy y:num ┊ . - .] ┊ . - . ┊ . - .foo: tried to read ingredient 'y' in 'x:num <- co↩┊ . - .py y:num' but it hasn't been written to yet ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario run-instruction-and-print-errors [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - assume-resources [ - ] - # sandbox editor contains an illegal instruction - env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset] - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that screen prints error message in red - screen-should-contain [ - . errors found (0) run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊get 1234:num, foo:offset . - . ┊unknown element 'foo' in container 'number' . - . ┊first ingredient of 'get' should be a container,↩. - . ┊ but got '1234:num' . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] - screen-should-contain-in-color 7/white, [ - . . - . . - . . - . . - . get 1234:num, foo:offset . - . . - . . - . . - ] - screen-should-contain-in-color 1/red, [ - . errors found (0) . - . . - . . - . . - . . - . unknown element 'foo' in container 'number' . - . first ingredient of 'get' should be a container, . - . but got '1234:num' . - . . - ] - screen-should-contain-in-color 245/grey, [ - . . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊ . - . ┊ . - . ┊ . - . ┊ ↩. - . ┊ . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario run-instruction-and-print-errors-only-once [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - assume-resources [ - ] - # sandbox editor contains an illegal instruction - env:&:environment <- new-programming-environment resources, screen, [get 1234:num, foo:offset] - # run the code in the editors multiple times - assume-console [ - press F4 - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - # check that screen prints error message just once - screen-should-contain [ - . errors found (0) run (F4) . - . ┊ . - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊─────────────────────────────────────────────────. - . ┊0 edit copy delete . - . ┊get 1234:num, foo:offset . - . ┊unknown element 'foo' in container 'number' . - . ┊first ingredient of 'get' should be a container,↩. - . ┊ but got '1234:num' . - . ┊─────────────────────────────────────────────────. - . ┊ . - ] -] - -scenario sandbox-can-handle-infinite-loop [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 20/height - # sandbox editor will trigger an infinite loop - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | {| - | loop| - | }| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo] - # run the sandbox - assume-console [ - press F4 - ] - run [ - event-loop screen, console, env, resources - ] - screen-should-contain [ - . errors found (0) run (F4) . - .recipe foo [ ┊ . - . { ┊─────────────────────────────────────────────────. - . loop ┊0 edit copy delete . - . } ┊foo . - .] ┊took too long! . - . ┊─────────────────────────────────────────────────. - .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . - . ┊ . - ] -] - -scenario sandbox-with-errors-shows-trace [ - local-scope - trace-until 100/app # trace too long - assume-screen 100/width, 10/height - # generate a stash and a error - assume-resources [ - [lesson/recipes.mu] <- [ - |recipe foo [| - | local-scope| - | a:num <- next-ingredient| - | b:num <- next-ingredient| - | stash [dividing by], b| - | _, c:num <- divide-with-remainder a, b| - | reply b| - |]| - ] - ] - env:&:environment <- new-programming-environment resources, screen, [foo 4, 0] - # run - assume-console [ - press F4 - ] - event-loop screen, console, env, resources - # screen prints error message - screen-should-contain [ - . errors found (0) run (F4) . - .recipe foo [ ┊ . - . local-scope ┊─────────────────────────────────────────────────. - . a:num <- next-ingredient ┊0 edit copy delete . - . b:num <- next-ingredient ┊foo 4, 0 . - . stash [dividing by], b ┊foo: divide by zero in '_, c:num <- divide-with-↩. - . _, c:num <- divide-with-remainder a, b ┊remainder a, b' . - . reply b ┊─────────────────────────────────────────────────. - .] ┊ . - . ┊ . - ] - # click on the call in the sandbox - assume-console [ - left-click 4, 55 - ] - run [ - event-loop screen, console, env, resources - ] - # screen should expand trace - screen-should-contain [ - . errors found (0) run (F4) . - .recipe foo [ ┊ . - . local-scope ┊─────────────────────────────────────────────────. - . a:num <- next-ingredient ┊0 edit copy delete . - . b:num <- next-ingredient ┊foo 4, 0 . - . stash [dividing by], b ┊dividing by 0 . - . _, c:num <- divide-with-remainder a, b ┊14 instructions run . - . reply b ┊foo: divide by zero in '_, c:num <- divide-with-↩. - .] ┊remainder a, b' . - . ┊─────────────────────────────────────────────────. - ] -] diff --git a/edit/013-editor-undo.mu b/edit/013-editor-undo.mu deleted file mode 100644 index c2f6bca3..00000000 --- a/edit/013-editor-undo.mu +++ /dev/null @@ -1,2109 +0,0 @@ -## undo/redo - -# for every undoable event, create a type of *operation* that contains all the -# information needed to reverse it -exclusive-container operation [ - typing:insert-operation - move:move-operation - delete:delete-operation -] - -container insert-operation [ - before-row:num - before-column:num - before-top-of-screen:&:duplex-list:char - after-row:num - after-column:num - after-top-of-screen:&:duplex-list:char - # inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate - insert-from:&:duplex-list:char - insert-until:&:duplex-list:char - tag:num # event causing this operation; might be used to coalesce runs of similar events - # 0: no coalesce (enter+indent) - # 1: regular alphanumeric characters -] - -container move-operation [ - before-row:num - before-column:num - before-top-of-screen:&:duplex-list:char - after-row:num - after-column:num - after-top-of-screen:&:duplex-list:char - tag:num # event causing this operation; might be used to coalesce runs of similar events - # 0: no coalesce (touch events, etc) - # 1: left arrow - # 2: right arrow - # 3: up arrow - # 4: down arrow -] - -container delete-operation [ - before-row:num - before-column:num - before-top-of-screen:&:duplex-list:char - after-row:num - after-column:num - after-top-of-screen:&:duplex-list:char - deleted-text:&:duplex-list:char - delete-from:&:duplex-list:char - delete-until:&:duplex-list:char - tag:num # event causing this operation; might be used to coalesce runs of similar events - # 0: no coalesce (ctrl-k, ctrl-u) - # 1: backspace - # 2: delete -] - -# every editor accumulates a list of operations to undo/redo -container editor [ - undo:&:list:&:operation - redo:&:list:&:operation -] - -# ctrl-z - undo operation -after [ - { - undo?:bool <- equal c, 26/ctrl-z - break-unless undo? - undo:&:list:&:operation <- get *editor, undo:offset - break-unless undo - op:&:operation <- first undo - undo <- rest undo - *editor <- put *editor, undo:offset, undo - redo:&:list:&:operation <- get *editor, redo:offset - redo <- push op, redo - *editor <- put *editor, redo:offset, redo - - return 1/go-render - } -] - -# ctrl-y - redo operation -after [ - { - redo?:bool <- equal c, 25/ctrl-y - break-unless redo? - redo:&:list:&:operation <- get *editor, redo:offset - break-unless redo - op:&:operation <- first redo - redo <- rest redo - *editor <- put *editor, redo:offset, redo - undo:&:list:&:operation <- get *editor, undo:offset - undo <- push op, undo - *editor <- put *editor, undo:offset, undo - - return 1/go-render - } -] - -# undo typing - -scenario editor-can-undo-typing [ - local-scope - # create an editor and type a character - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - assume-console [ - type [0] - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # character should be gone - screen-should-contain [ - . . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .1 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# save operation to undo -after [ - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset -] -before [ - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - undo:&:list:&:operation <- get *editor, undo:offset - { - # if previous operation was an insert, coalesce this operation with it - break-unless undo - op:&:operation <- first undo - typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant - break-unless is-insert? - previous-coalesce-tag:num <- get typing, tag:offset - break-unless previous-coalesce-tag - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - insert-until:&:duplex-list:char <- next before-cursor - typing <- put typing, insert-until:offset, insert-until - typing <- put typing, after-row:offset, cursor-row - typing <- put typing, after-column:offset, cursor-column - typing <- put typing, after-top-of-screen:offset, top-after - *op <- merge 0/insert-operation, typing - break +done-adding-insert-operation - } - # if not, create a new operation - insert-from:&:duplex-list:char <- next cursor-before - insert-to:&:duplex-list:char <- next insert-from - op:&:operation <- new operation:type - *op <- merge 0/insert-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 1/coalesce - editor <- add-operation editor, op - +done-adding-insert-operation -] - -# enter operations never coalesce with typing before or after -after [ - cursor-row-before:num <- copy cursor-row - cursor-column-before:num <- copy cursor-column - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset -] -before [ - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-row:offset - # never coalesce - insert-from:&:duplex-list:char <- next cursor-before - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - insert-to:&:duplex-list:char <- next before-cursor - op:&:operation <- new operation:type - *op <- merge 0/insert-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, insert-from, insert-to, 0/never-coalesce - editor <- add-operation editor, op -] - -# Everytime you add a new operation to the undo stack, be sure to clear the -# redo stack, because it's now obsolete. -# Beware: since we're counting cursor moves as operations, this means just -# moving the cursor can lose work on the undo stack. -def add-operation editor:&:editor, op:&:operation -> editor:&:editor [ - local-scope - load-ingredients - undo:&:list:&:operation <- get *editor, undo:offset - undo <- push op undo - *editor <- put *editor, undo:offset, undo - redo:&:list:&:operation <- get *editor, redo:offset - redo <- copy 0 - *editor <- put *editor, redo:offset, redo -] - -after [ - { - typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant - break-unless is-insert? - start:&:duplex-list:char <- get typing, insert-from:offset - end:&:duplex-list:char <- get typing, insert-until:offset - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - before-cursor:&:duplex-list:char <- prev start - *editor <- put *editor, before-cursor:offset, before-cursor - remove-between before-cursor, end - cursor-row <- get typing, before-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get typing, before-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get typing, before-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -scenario editor-can-undo-typing-multiple [ - local-scope - # create an editor and type multiple characters - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - assume-console [ - type [012] - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # all characters must be gone - screen-should-contain [ - . . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-can-undo-typing-multiple-2 [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - e:&:editor <- new-editor [a], 0/left, 10/right - editor-render screen, e - # type some characters - assume-console [ - type [012] - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .012a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # back to original text - screen-should-contain [ - . . - .a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor should be in the right place - assume-console [ - type [3] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .3a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-can-undo-typing-enter [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - e:&:editor <- new-editor [ abc], 0/left, 10/right - editor-render screen, e - # new line - assume-console [ - left-click 1, 8 - press enter - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - . abc . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # line is indented - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 2 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 5 - ] - # back to original text - screen-should-contain [ - . . - . abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor should be at end of line - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - . abc1 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# redo typing - -scenario editor-redo-typing [ - local-scope - # create an editor, type something, undo - assume-screen 10/width, 5/height - e:&:editor <- new-editor [a], 0/left, 10/right - editor-render screen, e - assume-console [ - type [012] - press ctrl-z - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # all characters must be back - screen-should-contain [ - . . - .012a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor should be in the right place - assume-console [ - type [3] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .0123a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - { - typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant - break-unless is-insert? - before-cursor <- get *editor, before-cursor:offset - insert-from:&:duplex-list:char <- get typing, insert-from:offset # ignore insert-to because it's already been spliced away - # assert insert-to matches next(before-cursor) - insert-range before-cursor, insert-from - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - cursor-row <- get typing, after-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get typing, after-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get typing, after-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -scenario editor-redo-typing-empty [ - local-scope - # create an editor, type something, undo - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - assume-console [ - type [012] - press ctrl-z - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # all characters must be back - screen-should-contain [ - . . - .012 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor should be in the right place - assume-console [ - type [3] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .0123 . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -scenario editor-work-clears-redo-stack [ - local-scope - # create an editor with some text, do some work, undo - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - assume-console [ - type [1] - press ctrl-z - ] - editor-event-loop screen, console, e - # do some more work - assume-console [ - type [0] - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .0abc . - .def . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # nothing should happen - screen-should-contain [ - . . - .0abc . - .def . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-redo-typing-and-enter-and-tab [ - local-scope - # create an editor - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - # insert some text and tabs, hit enter, some more text and tabs - assume-console [ - press tab - type [ab] - press tab - type [cd] - press enter - press tab - type [efg] - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - . ab cd . - . efg . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 7 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # typing in second line deleted, but not indent - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 2 - ] - screen-should-contain [ - . . - . ab cd . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # undo again - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # indent and newline deleted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 8 - ] - screen-should-contain [ - . . - . ab cd . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # undo again - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # empty screen - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - screen-should-contain [ - . . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 8 - ] - screen-should-contain [ - . . - . ab cd . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo again - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # newline and indent inserted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 2 - ] - screen-should-contain [ - . . - . ab cd . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo again - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # indent and newline deleted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 7 - ] - screen-should-contain [ - . . - . ab cd . - . efg . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# undo cursor movement and scroll - -scenario editor-can-undo-touch [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor - assume-console [ - left-click 3, 1 - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # click undone - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .1abc . - .def . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -after [ - cursor-row-before:num <- get *editor, cursor-row:offset - cursor-column-before:num <- get *editor, cursor-column:offset - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset -] -before [ - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - { - break-unless undo-coalesce-tag - # if previous operation was also a move, and also had the same coalesce - # tag, coalesce with it - undo:&:list:&:operation <- get *editor, undo:offset - break-unless undo - op:&:operation <- first undo - move:move-operation, is-move?:bool <- maybe-convert *op, move:variant - break-unless is-move? - previous-coalesce-tag:num <- get move, tag:offset - coalesce?:bool <- equal undo-coalesce-tag, previous-coalesce-tag - break-unless coalesce? - move <- put move, after-row:offset, cursor-row - move <- put move, after-column:offset, cursor-column - move <- put move, after-top-of-screen:offset, top-after - *op <- merge 1/move-operation, move - break +done-adding-move-operation - } - op:&:operation <- new operation:type - *op <- merge 1/move-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, undo-coalesce-tag - editor <- add-operation editor, op - +done-adding-move-operation -] - -after [ - { - move:move-operation, is-move?:bool <- maybe-convert *op, move:variant - break-unless is-move? - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - cursor-row <- get move, before-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get move, before-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get move, before-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -scenario editor-can-undo-scroll [ - local-scope - # screen has 1 line for menu + 3 lines - assume-screen 5/width, 4/height - # editor contains a wrapped line - contents:text <- new [a -b -cdefgh] - e:&:editor <- new-editor contents, 0/left, 5/right - # position cursor at end of screen and try to move right - assume-console [ - left-click 3, 3 - press right-arrow - ] - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - # screen scrolls - screen-should-contain [ - . . - .b . - .cdef↩. - .gh . - ] - memory-should-contain [ - 3 <- 3 - 4 <- 0 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moved back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 3 - 4 <- 3 - ] - # scroll undone - screen-should-contain [ - . . - .a . - .b . - .cdef↩. - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .b . - .cde1↩. - .fgh . - ] -] - -scenario editor-can-undo-left-arrow [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor - assume-console [ - left-click 3, 1 - press left-arrow - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 3 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def . - .g1hi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-up-arrow [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor - assume-console [ - left-click 3, 1 - press up-arrow - ] - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 3 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def . - .g1hi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-down-arrow [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor - assume-console [ - left-click 2, 1 - press down-arrow - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .d1ef . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-ctrl-f [ - local-scope - # create an editor with multiple pages of text - assume-screen 10/width, 5/height - contents:text <- new [a -b -c -d -e -f] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # scroll the page - assume-console [ - press ctrl-f - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # screen should again show page 1 - screen-should-contain [ - . . - .a . - .b . - .c . - .d . - ] -] - -scenario editor-can-undo-page-down [ - local-scope - # create an editor with multiple pages of text - assume-screen 10/width, 5/height - contents:text <- new [a -b -c -d -e -f] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # scroll the page - assume-console [ - press page-down - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # screen should again show page 1 - screen-should-contain [ - . . - .a . - .b . - .c . - .d . - ] -] - -scenario editor-can-undo-ctrl-b [ - local-scope - # create an editor with multiple pages of text - assume-screen 10/width, 5/height - contents:text <- new [a -b -c -d -e -f] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # scroll the page down and up - assume-console [ - press page-down - press ctrl-b - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # screen should again show page 2 - screen-should-contain [ - . . - .d . - .e . - .f . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-page-up [ - local-scope - # create an editor with multiple pages of text - assume-screen 10/width, 5/height - contents:text <- new [a -b -c -d -e -f] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # scroll the page down and up - assume-console [ - press page-down - press page-up - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # screen should again show page 2 - screen-should-contain [ - . . - .d . - .e . - .f . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-ctrl-a [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor, then to start of line - assume-console [ - left-click 2, 1 - press ctrl-a - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .d1ef . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-home [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor, then to start of line - assume-console [ - left-click 2, 1 - press home - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .d1ef . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-ctrl-e [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor, then to start of line - assume-console [ - left-click 2, 1 - press ctrl-e - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .d1ef . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-end [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor, then to start of line - assume-console [ - left-click 2, 1 - press end - ] - editor-event-loop screen, console, e - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves back - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .d1ef . - .ghi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -scenario editor-can-undo-multiple-arrows-in-the-same-direction [ - local-scope - # create an editor with some text - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # move the cursor - assume-console [ - left-click 2, 1 - press right-arrow - press right-arrow - press up-arrow - ] - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # up-arrow is undone - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 3 - ] - # undo again - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - # both right-arrows are undone - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 2 - 4 <- 1 - ] -] - -# redo cursor movement and scroll - -scenario editor-redo-touch [ - local-scope - # create an editor with some text, click on a character, undo - assume-screen 10/width, 5/height - contents:text <- new [abc -def -ghi] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - assume-console [ - left-click 3, 1 - press ctrl-z - ] - editor-event-loop screen, console, e - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # cursor moves to left-click - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 3 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def . - .g1hi . - .┈┈┈┈┈┈┈┈┈┈. - ] -] - -after [ - { - move:move-operation, is-move?:bool <- maybe-convert *op, move:variant - break-unless is-move? - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - cursor-row <- get move, after-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get move, after-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get move, after-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -scenario editor-separates-undo-insert-from-undo-cursor-move [ - local-scope - # create an editor, type some text, move the cursor, type some more text - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - assume-console [ - type [abc] - left-click 1, 1 - type [d] - ] - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - screen-should-contain [ - . . - .adbc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # last letter typed is deleted - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # undo again - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # no change to screen; cursor moves - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - # undo again - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # screen empty - screen-should-contain [ - . . - . . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # first insert - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - # redo again - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # cursor moves - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # cursor moves - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # redo again - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - ] - # second insert - screen-should-contain [ - . . - .adbc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] -] - -# undo backspace - -scenario editor-can-undo-and-redo-backspace [ - local-scope - # create an editor - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - # insert some text and hit backspace - assume-console [ - type [abc] - press backspace - press backspace - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 3 - ] - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - screen-should-contain [ - . . - .a . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -# save operation to undo -after [ - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset -] -before [ - { - break-unless backspaced-cell # backspace failed; don't add an undo operation - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-row:offset - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - undo:&:list:&:operation <- get *editor, undo:offset - { - # if previous operation was an insert, coalesce this operation with it - break-unless undo - op:&:operation <- first undo - deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant - break-unless is-delete? - previous-coalesce-tag:num <- get deletion, tag:offset - coalesce?:bool <- equal previous-coalesce-tag, 1/coalesce-backspace - break-unless coalesce? - deletion <- put deletion, delete-from:offset, before-cursor - backspaced-so-far:&:duplex-list:char <- get deletion, deleted-text:offset - insert-range backspaced-cell, backspaced-so-far - deletion <- put deletion, deleted-text:offset, backspaced-cell - deletion <- put deletion, after-row:offset, cursor-row - deletion <- put deletion, after-column:offset, cursor-column - deletion <- put deletion, after-top-of-screen:offset, top-after - *op <- merge 2/delete-operation, deletion - break +done-adding-backspace-operation - } - # if not, create a new operation - op:&:operation <- new operation:type - deleted-until:&:duplex-list:char <- next before-cursor - *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, backspaced-cell/deleted, before-cursor/delete-from, deleted-until, 1/coalesce-backspace - editor <- add-operation editor, op - +done-adding-backspace-operation - } -] - -after [ - { - deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant - break-unless is-delete? - anchor:&:duplex-list:char <- get deletion, delete-from:offset - break-unless anchor - deleted:&:duplex-list:char <- get deletion, deleted-text:offset - old-cursor:&:duplex-list:char <- last deleted - insert-range anchor, deleted - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - before-cursor <- copy old-cursor - cursor-row <- get deletion, before-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get deletion, before-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get deletion, before-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -after [ - { - deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant - break-unless is-delete? - start:&:duplex-list:char <- get deletion, delete-from:offset - end:&:duplex-list:char <- get deletion, delete-until:offset - data:&:duplex-list:char <- get *editor, data:offset - remove-between start, end - # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen - cursor-row <- get deletion, after-row:offset - *editor <- put *editor, cursor-row:offset, cursor-row - cursor-column <- get deletion, after-column:offset - *editor <- put *editor, cursor-column:offset, cursor-column - top:&:duplex-list:char <- get deletion, before-top-of-screen:offset - *editor <- put *editor, top-of-screen:offset, top - } -] - -# undo delete - -scenario editor-can-undo-and-redo-delete [ - local-scope - # create an editor - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - # insert some text and hit delete and backspace a few times - assume-console [ - type [abcdef] - left-click 1, 2 - press delete - press backspace - press delete - press delete - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .af . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # undo deletes - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - screen-should-contain [ - . . - .adef . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # undo backspace - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - screen-should-contain [ - . . - .abdef . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # undo first delete - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - screen-should-contain [ - . . - .abcdef . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo first delete - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - screen-should-contain [ - . . - .abdef . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo backspace - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - screen-should-contain [ - . . - .adef . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - # redo deletes - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - screen-should-contain [ - . . - .af . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset -] -before [ - { - break-unless deleted-cell # delete failed; don't add an undo operation - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - undo:&:list:&:operation <- get *editor, undo:offset - { - # if previous operation was an insert, coalesce this operation with it - break-unless undo - op:&:operation <- first undo - deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant - break-unless is-delete? - previous-coalesce-tag:num <- get deletion, tag:offset - coalesce?:bool <- equal previous-coalesce-tag, 2/coalesce-delete - break-unless coalesce? - delete-until:&:duplex-list:char <- next before-cursor - deletion <- put deletion, delete-until:offset, delete-until - deleted-so-far:&:duplex-list:char <- get deletion, deleted-text:offset - deleted-so-far <- append deleted-so-far, deleted-cell - deletion <- put deletion, deleted-text:offset, deleted-so-far - deletion <- put deletion, after-row:offset, cursor-row - deletion <- put deletion, after-column:offset, cursor-column - deletion <- put deletion, after-top-of-screen:offset, top-after - *op <- merge 2/delete-operation, deletion - break +done-adding-delete-operation - } - # if not, create a new operation - op:&:operation <- new operation:type - deleted-until:&:duplex-list:char <- next before-cursor - *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cell/deleted, before-cursor/delete-from, deleted-until, 2/coalesce-delete - editor <- add-operation editor, op - +done-adding-delete-operation - } -] - -# undo ctrl-k - -scenario editor-can-undo-and-redo-ctrl-k [ - local-scope - # create an editor - assume-screen 10/width, 5/height - contents:text <- new [abc -def] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # insert some text and hit delete and backspace a few times - assume-console [ - left-click 1, 1 - press ctrl-k - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .a . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - screen-should-contain [ - . . - .a . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 1 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .a1 . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset -] -before [ - { - break-unless deleted-cells # delete failed; don't add an undo operation - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - deleted-until:&:duplex-list:char <- next before-cursor - op:&:operation <- new operation:type - *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce - editor <- add-operation editor, op - +done-adding-delete-operation - } -] - -# undo ctrl-u - -scenario editor-can-undo-and-redo-ctrl-u [ - local-scope - # create an editor - assume-screen 10/width, 5/height - contents:text <- new [abc -def] - e:&:editor <- new-editor contents, 0/left, 10/right - editor-render screen, e - # insert some text and hit delete and backspace a few times - assume-console [ - left-click 1, 2 - press ctrl-u - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .c . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - # undo - assume-console [ - press ctrl-z - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .abc . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 2 - ] - # redo - assume-console [ - press ctrl-y - ] - run [ - editor-event-loop screen, console, e - ] - # first line inserted - screen-should-contain [ - . . - .c . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] - 3:num/raw <- get *e, cursor-row:offset - 4:num/raw <- get *e, cursor-column:offset - memory-should-contain [ - 3 <- 1 - 4 <- 0 - ] - # cursor should be in the right place - assume-console [ - type [1] - ] - run [ - editor-event-loop screen, console, e - ] - screen-should-contain [ - . . - .1c . - .def . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] - -after [ - top-before:&:duplex-list:char <- get *editor, top-of-screen:offset -] -before [ - { - break-unless deleted-cells # delete failed; don't add an undo operation - top-after:&:duplex-list:char <- get *editor, top-of-screen:offset - op:&:operation <- new operation:type - before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset - deleted-until:&:duplex-list:char <- next before-cursor - cursor-row:num <- get *editor, cursor-row:offset - cursor-column:num <- get *editor, cursor-column:offset - *op <- merge 2/delete-operation, save-row/before, save-column/before, top-before, cursor-row/after, cursor-column/after, top-after, deleted-cells/deleted, before-cursor/delete-from, deleted-until, 0/never-coalesce - editor <- add-operation editor, op - +done-adding-delete-operation - } -] - -scenario editor-can-undo-and-redo-ctrl-u-2 [ - local-scope - # create an editor - assume-screen 10/width, 5/height - e:&:editor <- new-editor [], 0/left, 10/right - editor-render screen, e - # insert some text and hit delete and backspace a few times - assume-console [ - type [abc] - press ctrl-u - press ctrl-z - ] - editor-event-loop screen, console, e - screen-should-contain [ - . . - .abc . - .┈┈┈┈┈┈┈┈┈┈. - . . - ] -] -- cgit 1.4.1-2-gfad0