From 2a4088119cf41175457414dfa59bd4064b8f0562 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Wed, 1 Jan 2020 17:04:37 -0800 Subject: 5852 --- archive/1.vm/edit/012-editor-undo.mu | 2111 ++++++++++++++++++++++++++++++++++ 1 file changed, 2111 insertions(+) create mode 100644 archive/1.vm/edit/012-editor-undo.mu (limited to 'archive/1.vm/edit/012-editor-undo.mu') diff --git a/archive/1.vm/edit/012-editor-undo.mu b/archive/1.vm/edit/012-editor-undo.mu new file mode 100644 index 00000000..871f6c74 --- /dev/null +++ b/archive/1.vm/edit/012-editor-undo.mu @@ -0,0 +1,2111 @@ +## 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 + # 5: line up + # 6: line down +] + +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 true/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 true/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-inputs + 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 null + *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) + splice 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 + splice 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 + splice 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