From 204dae921abff0c70e017215bb3c91fa6ca11aff Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 26 Dec 2016 11:44:14 -0800 Subject: 3710 Turns out we don't need to explicitly add anchors for each line. Vim's TOhtml has magic for that out of the box. --- html/edit/012-editor-undo.mu.html | 4218 ++++++++++++++++++------------------- 1 file changed, 2109 insertions(+), 2109 deletions(-) (limited to 'html/edit/012-editor-undo.mu.html') diff --git a/html/edit/012-editor-undo.mu.html b/html/edit/012-editor-undo.mu.html index 7ad0658e..5f230660 100644 --- a/html/edit/012-editor-undo.mu.html +++ b/html/edit/012-editor-undo.mu.html @@ -57,2115 +57,2115 @@ if ('onhashchange' in window) {
-   1 ## undo/redo
-   2 
-   3 # for every undoable event, create a type of *operation* that contains all the
-   4 # information needed to reverse it
-   5 exclusive-container operation [
-   6   typing:insert-operation
-   7   move:move-operation
-   8   delete:delete-operation
-   9 ]
-  10 
-  11 container insert-operation [
-  12   before-row:num
-  13   before-column:num
-  14   before-top-of-screen:&:duplex-list:char
-  15   after-row:num
-  16   after-column:num
-  17   after-top-of-screen:&:duplex-list:char
-  18   # inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate
-  19   insert-from:&:duplex-list:char
-  20   insert-until:&:duplex-list:char
-  21   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  22     # 0: no coalesce (enter+indent)
-  23     # 1: regular alphanumeric characters
-  24 ]
-  25 
-  26 container move-operation [
-  27   before-row:num
-  28   before-column:num
-  29   before-top-of-screen:&:duplex-list:char
-  30   after-row:num
-  31   after-column:num
-  32   after-top-of-screen:&:duplex-list:char
-  33   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  34     # 0: no coalesce (touch events, etc)
-  35     # 1: left arrow
-  36     # 2: right arrow
-  37     # 3: up arrow
-  38     # 4: down arrow
-  39 ]
-  40 
-  41 container delete-operation [
-  42   before-row:num
-  43   before-column:num
-  44   before-top-of-screen:&:duplex-list:char
-  45   after-row:num
-  46   after-column:num
-  47   after-top-of-screen:&:duplex-list:char
-  48   deleted-text:&:duplex-list:char
-  49   delete-from:&:duplex-list:char
-  50   delete-until:&:duplex-list:char
-  51   tag:num  # event causing this operation; might be used to coalesce runs of similar events
-  52     # 0: no coalesce (ctrl-k, ctrl-u)
-  53     # 1: backspace
-  54     # 2: delete
-  55 ]
-  56 
-  57 # every editor accumulates a list of operations to undo/redo
-  58 container editor [
-  59   undo:&:list:&:operation
-  60   redo:&:list:&:operation
-  61 ]
-  62 
-  63 # ctrl-z - undo operation
-  64 after <handle-special-character> [
-  65   {
-  66     undo?:bool <- equal c, 26/ctrl-z
-  67     break-unless undo?
-  68     undo:&:list:&:operation <- get *editor, undo:offset
-  69     break-unless undo
-  70     op:&:operation <- first undo
-  71     undo <- rest undo
-  72     *editor <- put *editor, undo:offset, undo
-  73     redo:&:list:&:operation <- get *editor, redo:offset
-  74     redo <- push op, redo
-  75     *editor <- put *editor, redo:offset, redo
-  76     <handle-undo>
-  77     return 1/go-render
-  78   }
-  79 ]
-  80 
-  81 # ctrl-y - redo operation
-  82 after <handle-special-character> [
-  83   {
-  84     redo?:bool <- equal c, 25/ctrl-y
-  85     break-unless redo?
-  86     redo:&:list:&:operation <- get *editor, redo:offset
-  87     break-unless redo
-  88     op:&:operation <- first redo
-  89     redo <- rest redo
-  90     *editor <- put *editor, redo:offset, redo
-  91     undo:&:list:&:operation <- get *editor, undo:offset
-  92     undo <- push op, undo
-  93     *editor <- put *editor, undo:offset, undo
-  94     <handle-redo>
-  95     return 1/go-render
-  96   }
-  97 ]
-  98 
-  99 # undo typing
- 100 
- 101 scenario editor-can-undo-typing [
- 102   local-scope
- 103   # create an editor and type a character
- 104   assume-screen 10/width, 5/height
- 105   e:&:editor <- new-editor [], 0/left, 10/right
- 106   editor-render screen, e
- 107   assume-console [
- 108     type [0]
- 109   ]
- 110   editor-event-loop screen, console, e
- 111   # undo
- 112   assume-console [
- 113     press ctrl-z
- 114   ]
- 115   run [
- 116     editor-event-loop screen, console, e
- 117   ]
- 118   # character should be gone
- 119   screen-should-contain [
- 120     .          .
- 121     .          .
- 122     .╌╌╌╌╌╌╌╌╌╌.
- 123     .          .
- 124   ]
- 125   # cursor should be in the right place
- 126   assume-console [
- 127     type [1]
- 128   ]
- 129   run [
- 130     editor-event-loop screen, console, e
- 131   ]
- 132   screen-should-contain [
- 133     .          .
- 134     .1         .
- 135     .╌╌╌╌╌╌╌╌╌╌.
- 136     .          .
- 137   ]
- 138 ]
- 139 
- 140 # save operation to undo
- 141 after <insert-character-begin> [
- 142   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 143   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
- 144 ]
- 145 before <insert-character-end> [
- 146   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 147   cursor-row:num <- get *editor, cursor-row:offset
- 148   cursor-column:num <- get *editor, cursor-column:offset
- 149   undo:&:list:&:operation <- get *editor, undo:offset
- 150   {
- 151     # if previous operation was an insert, coalesce this operation with it
- 152     break-unless undo
- 153     op:&:operation <- first undo
- 154     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 155     break-unless is-insert?
- 156     previous-coalesce-tag:num <- get typing, tag:offset
- 157     break-unless previous-coalesce-tag
- 158     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 159     insert-until:&:duplex-list:char <- next before-cursor
- 160     typing <- put typing, insert-until:offset, insert-until
- 161     typing <- put typing, after-row:offset, cursor-row
- 162     typing <- put typing, after-column:offset, cursor-column
- 163     typing <- put typing, after-top-of-screen:offset, top-after
- 164     *op <- merge 0/insert-operation, typing
- 165     break +done-adding-insert-operation
- 166   }
- 167   # if not, create a new operation
- 168   insert-from:&:duplex-list:char <- next cursor-before
- 169   insert-to:&:duplex-list:char <- next insert-from
- 170   op:&:operation <- new operation:type
- 171   *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
- 172   editor <- add-operation editor, op
- 173   +done-adding-insert-operation
- 174 ]
- 175 
- 176 # enter operations never coalesce with typing before or after
- 177 after <insert-enter-begin> [
- 178   cursor-row-before:num <- copy cursor-row
- 179   cursor-column-before:num <- copy cursor-column
- 180   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 181   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
- 182 ]
- 183 before <insert-enter-end> [
- 184   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 185   cursor-row:num <- get *editor, cursor-row:offset
- 186   cursor-column:num <- get *editor, cursor-row:offset
- 187   # never coalesce
- 188   insert-from:&:duplex-list:char <- next cursor-before
- 189   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
- 190   insert-to:&:duplex-list:char <- next before-cursor
- 191   op:&:operation <- new operation:type
- 192   *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
- 193   editor <- add-operation editor, op
- 194 ]
- 195 
- 196 # Everytime you add a new operation to the undo stack, be sure to clear the
- 197 # redo stack, because it's now obsolete.
- 198 # Beware: since we're counting cursor moves as operations, this means just
- 199 # moving the cursor can lose work on the undo stack.
- 200 def add-operation editor:&:editor, op:&:operation -> editor:&:editor [
- 201   local-scope
- 202   load-ingredients
- 203   undo:&:list:&:operation <- get *editor, undo:offset
- 204   undo <- push op undo
- 205   *editor <- put *editor, undo:offset, undo
- 206   redo:&:list:&:operation <- get *editor, redo:offset
- 207   redo <- copy 0
- 208   *editor <- put *editor, redo:offset, redo
- 209 ]
- 210 
- 211 after <handle-undo> [
- 212   {
- 213     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 214     break-unless is-insert?
- 215     start:&:duplex-list:char <- get typing, insert-from:offset
- 216     end:&:duplex-list:char <- get typing, insert-until:offset
- 217     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 218     before-cursor:&:duplex-list:char <- prev start
- 219     *editor <- put *editor, before-cursor:offset, before-cursor
- 220     remove-between before-cursor, end
- 221     cursor-row <- get typing, before-row:offset
- 222     *editor <- put *editor, cursor-row:offset, cursor-row
- 223     cursor-column <- get typing, before-column:offset
- 224     *editor <- put *editor, cursor-column:offset, cursor-column
- 225     top:&:duplex-list:char <- get typing, before-top-of-screen:offset
- 226     *editor <- put *editor, top-of-screen:offset, top
- 227   }
- 228 ]
- 229 
- 230 scenario editor-can-undo-typing-multiple [
- 231   local-scope
- 232   # create an editor and type multiple characters
- 233   assume-screen 10/width, 5/height
- 234   e:&:editor <- new-editor [], 0/left, 10/right
- 235   editor-render screen, e
- 236   assume-console [
- 237     type [012]
- 238   ]
- 239   editor-event-loop screen, console, e
- 240   # undo
- 241   assume-console [
- 242     press ctrl-z
- 243   ]
- 244   run [
- 245     editor-event-loop screen, console, e
- 246   ]
- 247   # all characters must be gone
- 248   screen-should-contain [
- 249     .          .
- 250     .          .
- 251     .╌╌╌╌╌╌╌╌╌╌.
- 252     .          .
- 253   ]
- 254 ]
- 255 
- 256 scenario editor-can-undo-typing-multiple-2 [
- 257   local-scope
- 258   # create an editor with some text
- 259   assume-screen 10/width, 5/height
- 260   e:&:editor <- new-editor [a], 0/left, 10/right
- 261   editor-render screen, e
- 262   # type some characters
- 263   assume-console [
- 264     type [012]
- 265   ]
- 266   editor-event-loop screen, console, e
- 267   screen-should-contain [
- 268     .          .
- 269     .012a      .
- 270     .╌╌╌╌╌╌╌╌╌╌.
- 271     .          .
- 272   ]
- 273   # undo
- 274   assume-console [
- 275     press ctrl-z
- 276   ]
- 277   run [
- 278     editor-event-loop screen, console, e
- 279   ]
- 280   # back to original text
- 281   screen-should-contain [
- 282     .          .
- 283     .a         .
- 284     .╌╌╌╌╌╌╌╌╌╌.
- 285     .          .
- 286   ]
- 287   # cursor should be in the right place
- 288   assume-console [
- 289     type [3]
- 290   ]
- 291   run [
- 292     editor-event-loop screen, console, e
- 293   ]
- 294   screen-should-contain [
- 295     .          .
- 296     .3a        .
- 297     .╌╌╌╌╌╌╌╌╌╌.
- 298     .          .
- 299   ]
- 300 ]
- 301 
- 302 scenario editor-can-undo-typing-enter [
- 303   local-scope
- 304   # create an editor with some text
- 305   assume-screen 10/width, 5/height
- 306   e:&:editor <- new-editor [  abc], 0/left, 10/right
- 307   editor-render screen, e
- 308   # new line
- 309   assume-console [
- 310     left-click 1, 8
- 311     press enter
- 312   ]
- 313   editor-event-loop screen, console, e
- 314   screen-should-contain [
- 315     .          .
- 316     .  abc     .
- 317     .          .
- 318     .╌╌╌╌╌╌╌╌╌╌.
- 319     .          .
- 320   ]
- 321   # line is indented
- 322   3:num/raw <- get *e, cursor-row:offset
- 323   4:num/raw <- get *e, cursor-column:offset
- 324   memory-should-contain [
- 325     3 <- 2
- 326     4 <- 2
- 327   ]
- 328   # undo
- 329   assume-console [
- 330     press ctrl-z
- 331   ]
- 332   run [
- 333     editor-event-loop screen, console, e
- 334   ]
- 335   3:num/raw <- get *e, cursor-row:offset
- 336   4:num/raw <- get *e, cursor-column:offset
- 337   memory-should-contain [
- 338     3 <- 1
- 339     4 <- 5
- 340   ]
- 341   # back to original text
- 342   screen-should-contain [
- 343     .          .
- 344     .  abc     .
- 345     .╌╌╌╌╌╌╌╌╌╌.
- 346     .          .
- 347   ]
- 348   # cursor should be at end of line
- 349   assume-console [
- 350     type [1]
- 351   ]
- 352   run [
- 353     editor-event-loop screen, console, e
- 354   ]
- 355   screen-should-contain [
- 356     .          .
- 357     .  abc1    .
- 358     .╌╌╌╌╌╌╌╌╌╌.
- 359     .          .
- 360   ]
- 361 ]
- 362 
- 363 # redo typing
- 364 
- 365 scenario editor-redo-typing [
- 366   local-scope
- 367   # create an editor, type something, undo
- 368   assume-screen 10/width, 5/height
- 369   e:&:editor <- new-editor [a], 0/left, 10/right
- 370   editor-render screen, e
- 371   assume-console [
- 372     type [012]
- 373     press ctrl-z
- 374   ]
- 375   editor-event-loop screen, console, e
- 376   screen-should-contain [
- 377     .          .
- 378     .a         .
- 379     .╌╌╌╌╌╌╌╌╌╌.
- 380     .          .
- 381   ]
- 382   # redo
- 383   assume-console [
- 384     press ctrl-y
- 385   ]
- 386   run [
- 387     editor-event-loop screen, console, e
- 388   ]
- 389   # all characters must be back
- 390   screen-should-contain [
- 391     .          .
- 392     .012a      .
- 393     .╌╌╌╌╌╌╌╌╌╌.
- 394     .          .
- 395   ]
- 396   # cursor should be in the right place
- 397   assume-console [
- 398     type [3]
- 399   ]
- 400   run [
- 401     editor-event-loop screen, console, e
- 402   ]
- 403   screen-should-contain [
- 404     .          .
- 405     .0123a     .
- 406     .╌╌╌╌╌╌╌╌╌╌.
- 407     .          .
- 408   ]
- 409 ]
- 410 
- 411 after <handle-redo> [
- 412   {
- 413     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
- 414     break-unless is-insert?
- 415     before-cursor <- get *editor, before-cursor:offset
- 416     insert-from:&:duplex-list:char <- get typing, insert-from:offset  # ignore insert-to because it's already been spliced away
- 417     # assert insert-to matches next(before-cursor)
- 418     insert-range before-cursor, insert-from
- 419     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 420     cursor-row <- get typing, after-row:offset
- 421     *editor <- put *editor, cursor-row:offset, cursor-row
- 422     cursor-column <- get typing, after-column:offset
- 423     *editor <- put *editor, cursor-column:offset, cursor-column
- 424     top:&:duplex-list:char <- get typing, after-top-of-screen:offset
- 425     *editor <- put *editor, top-of-screen:offset, top
- 426   }
- 427 ]
- 428 
- 429 scenario editor-redo-typing-empty [
- 430   local-scope
- 431   # create an editor, type something, undo
- 432   assume-screen 10/width, 5/height
- 433   e:&:editor <- new-editor [], 0/left, 10/right
- 434   editor-render screen, e
- 435   assume-console [
- 436     type [012]
- 437     press ctrl-z
- 438   ]
- 439   editor-event-loop screen, console, e
- 440   screen-should-contain [
- 441     .          .
- 442     .          .
- 443     .╌╌╌╌╌╌╌╌╌╌.
- 444     .          .
- 445   ]
- 446   # redo
- 447   assume-console [
- 448     press ctrl-y
- 449   ]
- 450   run [
- 451     editor-event-loop screen, console, e
- 452   ]
- 453   # all characters must be back
- 454   screen-should-contain [
- 455     .          .
- 456     .012       .
- 457     .╌╌╌╌╌╌╌╌╌╌.
- 458     .          .
- 459   ]
- 460   # cursor should be in the right place
- 461   assume-console [
- 462     type [3]
- 463   ]
- 464   run [
- 465     editor-event-loop screen, console, e
- 466   ]
- 467   screen-should-contain [
- 468     .          .
- 469     .0123      .
- 470     .╌╌╌╌╌╌╌╌╌╌.
- 471     .          .
- 472   ]
- 473 ]
- 474 
- 475 scenario editor-work-clears-redo-stack [
- 476   local-scope
- 477   # create an editor with some text, do some work, undo
- 478   assume-screen 10/width, 5/height
- 479   contents:text <- new [abc
- 480 def
- 481 ghi]
- 482   e:&:editor <- new-editor contents, 0/left, 10/right
- 483   editor-render screen, e
- 484   assume-console [
- 485     type [1]
- 486     press ctrl-z
- 487   ]
- 488   editor-event-loop screen, console, e
- 489   # do some more work
- 490   assume-console [
- 491     type [0]
- 492   ]
- 493   editor-event-loop screen, console, e
- 494   screen-should-contain [
- 495     .          .
- 496     .0abc      .
- 497     .def       .
- 498     .ghi       .
- 499     .╌╌╌╌╌╌╌╌╌╌.
- 500   ]
- 501   # redo
- 502   assume-console [
- 503     press ctrl-y
- 504   ]
- 505   run [
- 506     editor-event-loop screen, console, e
- 507   ]
- 508   # nothing should happen
- 509   screen-should-contain [
- 510     .          .
- 511     .0abc      .
- 512     .def       .
- 513     .ghi       .
- 514     .╌╌╌╌╌╌╌╌╌╌.
- 515   ]
- 516 ]
- 517 
- 518 scenario editor-can-redo-typing-and-enter-and-tab [
- 519   local-scope
- 520   # create an editor
- 521   assume-screen 10/width, 5/height
- 522   e:&:editor <- new-editor [], 0/left, 10/right
- 523   editor-render screen, e
- 524   # insert some text and tabs, hit enter, some more text and tabs
- 525   assume-console [
- 526     press tab
- 527     type [ab]
- 528     press tab
- 529     type [cd]
- 530     press enter
- 531     press tab
- 532     type [efg]
- 533   ]
- 534   editor-event-loop screen, console, e
- 535   screen-should-contain [
- 536     .          .
- 537     .  ab  cd  .
- 538     .    efg   .
- 539     .╌╌╌╌╌╌╌╌╌╌.
- 540     .          .
- 541   ]
- 542   3:num/raw <- get *e, cursor-row:offset
- 543   4:num/raw <- get *e, cursor-column:offset
- 544   memory-should-contain [
- 545     3 <- 2
- 546     4 <- 7
- 547   ]
- 548   # undo
- 549   assume-console [
- 550     press ctrl-z
- 551   ]
- 552   run [
- 553     editor-event-loop screen, console, e
- 554   ]
- 555   # typing in second line deleted, but not indent
- 556   3:num/raw <- get *e, cursor-row:offset
- 557   4:num/raw <- get *e, cursor-column:offset
- 558   memory-should-contain [
- 559     3 <- 2
- 560     4 <- 2
- 561   ]
- 562   screen-should-contain [
- 563     .          .
- 564     .  ab  cd  .
- 565     .          .
- 566     .╌╌╌╌╌╌╌╌╌╌.
- 567     .          .
- 568   ]
- 569   # undo again
- 570   assume-console [
- 571     press ctrl-z
- 572   ]
- 573   run [
- 574     editor-event-loop screen, console, e
- 575   ]
- 576   # indent and newline deleted
- 577   3:num/raw <- get *e, cursor-row:offset
- 578   4:num/raw <- get *e, cursor-column:offset
- 579   memory-should-contain [
- 580     3 <- 1
- 581     4 <- 8
- 582   ]
- 583   screen-should-contain [
- 584     .          .
- 585     .  ab  cd  .
- 586     .╌╌╌╌╌╌╌╌╌╌.
- 587     .          .
- 588   ]
- 589   # undo again
- 590   assume-console [
- 591     press ctrl-z
- 592   ]
- 593   run [
- 594     editor-event-loop screen, console, e
- 595   ]
- 596   # empty screen
- 597   3:num/raw <- get *e, cursor-row:offset
- 598   4:num/raw <- get *e, cursor-column:offset
- 599   memory-should-contain [
- 600     3 <- 1
- 601     4 <- 0
- 602   ]
- 603   screen-should-contain [
- 604     .          .
- 605     .          .
- 606     .╌╌╌╌╌╌╌╌╌╌.
- 607     .          .
- 608   ]
- 609   # redo
- 610   assume-console [
- 611     press ctrl-y
- 612   ]
- 613   run [
- 614     editor-event-loop screen, console, e
- 615   ]
- 616   # first line inserted
- 617   3:num/raw <- get *e, cursor-row:offset
- 618   4:num/raw <- get *e, cursor-column:offset
- 619   memory-should-contain [
- 620     3 <- 1
- 621     4 <- 8
- 622   ]
- 623   screen-should-contain [
- 624     .          .
- 625     .  ab  cd  .
- 626     .╌╌╌╌╌╌╌╌╌╌.
- 627     .          .
- 628   ]
- 629   # redo again
- 630   assume-console [
- 631     press ctrl-y
- 632   ]
- 633   run [
- 634     editor-event-loop screen, console, e
- 635   ]
- 636   # newline and indent inserted
- 637   3:num/raw <- get *e, cursor-row:offset
- 638   4:num/raw <- get *e, cursor-column:offset
- 639   memory-should-contain [
- 640     3 <- 2
- 641     4 <- 2
- 642   ]
- 643   screen-should-contain [
- 644     .          .
- 645     .  ab  cd  .
- 646     .          .
- 647     .╌╌╌╌╌╌╌╌╌╌.
- 648     .          .
- 649   ]
- 650   # redo again
- 651   assume-console [
- 652     press ctrl-y
- 653   ]
- 654   run [
- 655     editor-event-loop screen, console, e
- 656   ]
- 657   # indent and newline deleted
- 658   3:num/raw <- get *e, cursor-row:offset
- 659   4:num/raw <- get *e, cursor-column:offset
- 660   memory-should-contain [
- 661     3 <- 2
- 662     4 <- 7
- 663   ]
- 664   screen-should-contain [
- 665     .          .
- 666     .  ab  cd  .
- 667     .    efg   .
- 668     .╌╌╌╌╌╌╌╌╌╌.
- 669     .          .
- 670   ]
- 671 ]
- 672 
- 673 # undo cursor movement and scroll
- 674 
- 675 scenario editor-can-undo-touch [
- 676   local-scope
- 677   # create an editor with some text
- 678   assume-screen 10/width, 5/height
- 679   contents:text <- new [abc
- 680 def
- 681 ghi]
- 682   e:&:editor <- new-editor contents, 0/left, 10/right
- 683   editor-render screen, e
- 684   # move the cursor
- 685   assume-console [
- 686     left-click 3, 1
- 687   ]
- 688   editor-event-loop screen, console, e
- 689   # undo
- 690   assume-console [
- 691     press ctrl-z
- 692   ]
- 693   run [
- 694     editor-event-loop screen, console, e
- 695   ]
- 696   # click undone
- 697   3:num/raw <- get *e, cursor-row:offset
- 698   4:num/raw <- get *e, cursor-column:offset
- 699   memory-should-contain [
- 700     3 <- 1
- 701     4 <- 0
- 702   ]
- 703   # cursor should be in the right place
- 704   assume-console [
- 705     type [1]
- 706   ]
- 707   run [
- 708     editor-event-loop screen, console, e
- 709   ]
- 710   screen-should-contain [
- 711     .          .
- 712     .1abc      .
- 713     .def       .
- 714     .ghi       .
- 715     .╌╌╌╌╌╌╌╌╌╌.
- 716   ]
- 717 ]
- 718 
- 719 after <move-cursor-begin> [
- 720   cursor-row-before:num <- get *editor, cursor-row:offset
- 721   cursor-column-before:num <- get *editor, cursor-column:offset
- 722   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
- 723 ]
- 724 before <move-cursor-end> [
- 725   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
- 726   cursor-row:num <- get *editor, cursor-row:offset
- 727   cursor-column:num <- get *editor, cursor-column:offset
- 728   {
- 729     break-unless undo-coalesce-tag
- 730     # if previous operation was also a move, and also had the same coalesce
- 731     # tag, coalesce with it
- 732     undo:&:list:&:operation <- get *editor, undo:offset
- 733     break-unless undo
- 734     op:&:operation <- first undo
- 735     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
- 736     break-unless is-move?
- 737     previous-coalesce-tag:num <- get move, tag:offset
- 738     coalesce?:bool <- equal undo-coalesce-tag, previous-coalesce-tag
- 739     break-unless coalesce?
- 740     move <- put move, after-row:offset, cursor-row
- 741     move <- put move, after-column:offset, cursor-column
- 742     move <- put move, after-top-of-screen:offset, top-after
- 743     *op <- merge 1/move-operation, move
- 744     break +done-adding-move-operation
- 745   }
- 746   op:&:operation <- new operation:type
- 747   *op <- merge 1/move-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, undo-coalesce-tag
- 748   editor <- add-operation editor, op
- 749   +done-adding-move-operation
- 750 ]
- 751 
- 752 after <handle-undo> [
- 753   {
- 754     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
- 755     break-unless is-move?
- 756     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
- 757     cursor-row <- get move, before-row:offset
- 758     *editor <- put *editor, cursor-row:offset, cursor-row
- 759     cursor-column <- get move, before-column:offset
- 760     *editor <- put *editor, cursor-column:offset, cursor-column
- 761     top:&:duplex-list:char <- get move, before-top-of-screen:offset
- 762     *editor <- put *editor, top-of-screen:offset, top
- 763   }
- 764 ]
- 765 
- 766 scenario editor-can-undo-scroll [
- 767   local-scope
- 768   # screen has 1 line for menu + 3 lines
- 769   assume-screen 5/width, 4/height
- 770   # editor contains a wrapped line
- 771   contents:text <- new [a
- 772 b
- 773 cdefgh]
- 774   e:&:editor <- new-editor contents, 0/left, 5/right
- 775   # position cursor at end of screen and try to move right
- 776   assume-console [
- 777     left-click 3, 3
- 778     press right-arrow
- 779   ]
- 780   editor-event-loop screen, console, e
- 781   3:num/raw <- get *e, cursor-row:offset
- 782   4:num/raw <- get *e, cursor-column:offset
- 783   # screen scrolls
- 784   screen-should-contain [
- 785     .     .
- 786     .b    .
- 787     .cdef↩.
- 788     .gh   .
- 789   ]
- 790   memory-should-contain [
- 791     3 <- 3
- 792     4 <- 0
- 793   ]
- 794   # undo
- 795   assume-console [
- 796     press ctrl-z
- 797   ]
- 798   run [
- 799     editor-event-loop screen, console, e
- 800   ]
- 801   # cursor moved back
- 802   3:num/raw <- get *e, cursor-row:offset
- 803   4:num/raw <- get *e, cursor-column:offset
- 804   memory-should-contain [
- 805     3 <- 3
- 806     4 <- 3
- 807   ]
- 808   # scroll undone
- 809   screen-should-contain [
- 810     .     .
- 811     .a    .
- 812     .b    .
- 813     .cdef↩.
- 814   ]
- 815   # cursor should be in the right place
- 816   assume-console [
- 817     type [1]
- 818   ]
- 819   run [
- 820     editor-event-loop screen, console, e
- 821   ]
- 822   screen-should-contain [
- 823     .     .
- 824     .b    .
- 825     .cde1↩.
- 826     .fgh  .
- 827   ]
- 828 ]
- 829 
- 830 scenario editor-can-undo-left-arrow [
- 831   local-scope
- 832   # create an editor with some text
- 833   assume-screen 10/width, 5/height
- 834   contents:text <- new [abc
- 835 def
- 836 ghi]
- 837   e:&:editor <- new-editor contents, 0/left, 10/right
- 838   editor-render screen, e
- 839   # move the cursor
- 840   assume-console [
- 841     left-click 3, 1
- 842     press left-arrow
- 843   ]
- 844   editor-event-loop screen, console, e
- 845   # undo
- 846   assume-console [
- 847     press ctrl-z
- 848   ]
- 849   run [
- 850     editor-event-loop screen, console, e
- 851   ]
- 852   # cursor moves back
- 853   3:num/raw <- get *e, cursor-row:offset
- 854   4:num/raw <- get *e, cursor-column:offset
- 855   memory-should-contain [
- 856     3 <- 3
- 857     4 <- 1
- 858   ]
- 859   # cursor should be in the right place
- 860   assume-console [
- 861     type [1]
- 862   ]
- 863   run [
- 864     editor-event-loop screen, console, e
- 865   ]
- 866   screen-should-contain [
- 867     .          .
- 868     .abc       .
- 869     .def       .
- 870     .g1hi      .
- 871     .╌╌╌╌╌╌╌╌╌╌.
- 872   ]
- 873 ]
- 874 
- 875 scenario editor-can-undo-up-arrow [
- 876   local-scope
- 877   # create an editor with some text
- 878   assume-screen 10/width, 5/height
- 879   contents:text <- new [abc
- 880 def
- 881 ghi]
- 882   e:&:editor <- new-editor contents, 0/left, 10/right
- 883   editor-render screen, e
- 884   # move the cursor
- 885   assume-console [
- 886     left-click 3, 1
- 887     press up-arrow
- 888   ]
- 889   editor-event-loop screen, console, e
- 890   3:num/raw <- get *e, cursor-row:offset
- 891   4:num/raw <- get *e, cursor-column:offset
- 892   memory-should-contain [
- 893     3 <- 2
- 894     4 <- 1
- 895   ]
- 896   # undo
- 897   assume-console [
- 898     press ctrl-z
- 899   ]
- 900   run [
- 901     editor-event-loop screen, console, e
- 902   ]
- 903   # cursor moves back
- 904   3:num/raw <- get *e, cursor-row:offset
- 905   4:num/raw <- get *e, cursor-column:offset
- 906   memory-should-contain [
- 907     3 <- 3
- 908     4 <- 1
- 909   ]
- 910   # cursor should be in the right place
- 911   assume-console [
- 912     type [1]
- 913   ]
- 914   run [
- 915     editor-event-loop screen, console, e
- 916   ]
- 917   screen-should-contain [
- 918     .          .
- 919     .abc       .
- 920     .def       .
- 921     .g1hi      .
- 922     .╌╌╌╌╌╌╌╌╌╌.
- 923   ]
- 924 ]
- 925 
- 926 scenario editor-can-undo-down-arrow [
- 927   local-scope
- 928   # create an editor with some text
- 929   assume-screen 10/width, 5/height
- 930   contents:text <- new [abc
- 931 def
- 932 ghi]
- 933   e:&:editor <- new-editor contents, 0/left, 10/right
- 934   editor-render screen, e
- 935   # move the cursor
- 936   assume-console [
- 937     left-click 2, 1
- 938     press down-arrow
- 939   ]
- 940   editor-event-loop screen, console, e
- 941   # undo
- 942   assume-console [
- 943     press ctrl-z
- 944   ]
- 945   run [
- 946     editor-event-loop screen, console, e
- 947   ]
- 948   # cursor moves back
- 949   3:num/raw <- get *e, cursor-row:offset
- 950   4:num/raw <- get *e, cursor-column:offset
- 951   memory-should-contain [
- 952     3 <- 2
- 953     4 <- 1
- 954   ]
- 955   # cursor should be in the right place
- 956   assume-console [
- 957     type [1]
- 958   ]
- 959   run [
- 960     editor-event-loop screen, console, e
- 961   ]
- 962   screen-should-contain [
- 963     .          .
- 964     .abc       .
- 965     .d1ef      .
- 966     .ghi       .
- 967     .╌╌╌╌╌╌╌╌╌╌.
- 968   ]
- 969 ]
- 970 
- 971 scenario editor-can-undo-ctrl-f [
- 972   local-scope
- 973   # create an editor with multiple pages of text
- 974   assume-screen 10/width, 5/height
- 975   contents:text <- new [a
- 976 b
- 977 c
- 978 d
- 979 e
- 980 f]
- 981   e:&:editor <- new-editor contents, 0/left, 10/right
- 982   editor-render screen, e
- 983   # scroll the page
- 984   assume-console [
- 985     press ctrl-f
- 986   ]
- 987   editor-event-loop screen, console, e
- 988   # undo
- 989   assume-console [
- 990     press ctrl-z
- 991   ]
- 992   run [
- 993     editor-event-loop screen, console, e
- 994   ]
- 995   # screen should again show page 1
- 996   screen-should-contain [
- 997     .          .
- 998     .a         .
- 999     .b         .
-1000     .c         .
-1001     .d         .
-1002   ]
-1003 ]
-1004 
-1005 scenario editor-can-undo-page-down [
-1006   local-scope
-1007   # create an editor with multiple pages of text
-1008   assume-screen 10/width, 5/height
-1009   contents:text <- new [a
-1010 b
-1011 c
-1012 d
-1013 e
-1014 f]
-1015   e:&:editor <- new-editor contents, 0/left, 10/right
-1016   editor-render screen, e
-1017   # scroll the page
-1018   assume-console [
-1019     press page-down
-1020   ]
-1021   editor-event-loop screen, console, e
-1022   # undo
-1023   assume-console [
-1024     press ctrl-z
-1025   ]
-1026   run [
-1027     editor-event-loop screen, console, e
-1028   ]
-1029   # screen should again show page 1
-1030   screen-should-contain [
-1031     .          .
-1032     .a         .
-1033     .b         .
-1034     .c         .
-1035     .d         .
-1036   ]
-1037 ]
-1038 
-1039 scenario editor-can-undo-ctrl-b [
-1040   local-scope
-1041   # create an editor with multiple pages of text
-1042   assume-screen 10/width, 5/height
-1043   contents:text <- new [a
-1044 b
-1045 c
-1046 d
-1047 e
-1048 f]
-1049   e:&:editor <- new-editor contents, 0/left, 10/right
-1050   editor-render screen, e
-1051   # scroll the page down and up
-1052   assume-console [
-1053     press page-down
-1054     press ctrl-b
-1055   ]
-1056   editor-event-loop screen, console, e
-1057   # undo
-1058   assume-console [
-1059     press ctrl-z
-1060   ]
-1061   run [
-1062     editor-event-loop screen, console, e
-1063   ]
-1064   # screen should again show page 2
-1065   screen-should-contain [
-1066     .          .
-1067     .d         .
-1068     .e         .
-1069     .f         .
-1070     .╌╌╌╌╌╌╌╌╌╌.
-1071   ]
-1072 ]
-1073 
-1074 scenario editor-can-undo-page-up [
-1075   local-scope
-1076   # create an editor with multiple pages of text
-1077   assume-screen 10/width, 5/height
-1078   contents:text <- new [a
-1079 b
-1080 c
-1081 d
-1082 e
-1083 f]
-1084   e:&:editor <- new-editor contents, 0/left, 10/right
-1085   editor-render screen, e
-1086   # scroll the page down and up
-1087   assume-console [
-1088     press page-down
-1089     press page-up
-1090   ]
-1091   editor-event-loop screen, console, e
-1092   # undo
-1093   assume-console [
-1094     press ctrl-z
-1095   ]
-1096   run [
-1097     editor-event-loop screen, console, e
-1098   ]
-1099   # screen should again show page 2
-1100   screen-should-contain [
-1101     .          .
-1102     .d         .
-1103     .e         .
-1104     .f         .
-1105     .╌╌╌╌╌╌╌╌╌╌.
-1106   ]
-1107 ]
-1108 
-1109 scenario editor-can-undo-ctrl-a [
-1110   local-scope
-1111   # create an editor with some text
-1112   assume-screen 10/width, 5/height
-1113   contents:text <- new [abc
-1114 def
-1115 ghi]
-1116   e:&:editor <- new-editor contents, 0/left, 10/right
-1117   editor-render screen, e
-1118   # move the cursor, then to start of line
-1119   assume-console [
-1120     left-click 2, 1
-1121     press ctrl-a
-1122   ]
-1123   editor-event-loop screen, console, e
-1124   # undo
-1125   assume-console [
-1126     press ctrl-z
-1127   ]
-1128   run [
-1129     editor-event-loop screen, console, e
-1130   ]
-1131   # cursor moves back
-1132   3:num/raw <- get *e, cursor-row:offset
-1133   4:num/raw <- get *e, cursor-column:offset
-1134   memory-should-contain [
-1135     3 <- 2
-1136     4 <- 1
-1137   ]
-1138   # cursor should be in the right place
-1139   assume-console [
-1140     type [1]
-1141   ]
-1142   run [
-1143     editor-event-loop screen, console, e
-1144   ]
-1145   screen-should-contain [
-1146     .          .
-1147     .abc       .
-1148     .d1ef      .
-1149     .ghi       .
-1150     .╌╌╌╌╌╌╌╌╌╌.
-1151   ]
-1152 ]
-1153 
-1154 scenario editor-can-undo-home [
-1155   local-scope
-1156   # create an editor with some text
-1157   assume-screen 10/width, 5/height
-1158   contents:text <- new [abc
-1159 def
-1160 ghi]
-1161   e:&:editor <- new-editor contents, 0/left, 10/right
-1162   editor-render screen, e
-1163   # move the cursor, then to start of line
-1164   assume-console [
-1165     left-click 2, 1
-1166     press home
-1167   ]
-1168   editor-event-loop screen, console, e
-1169   # undo
-1170   assume-console [
-1171     press ctrl-z
-1172   ]
-1173   run [
-1174     editor-event-loop screen, console, e
-1175   ]
-1176   # cursor moves back
-1177   3:num/raw <- get *e, cursor-row:offset
-1178   4:num/raw <- get *e, cursor-column:offset
-1179   memory-should-contain [
-1180     3 <- 2
-1181     4 <- 1
-1182   ]
-1183   # cursor should be in the right place
-1184   assume-console [
-1185     type [1]
-1186   ]
-1187   run [
-1188     editor-event-loop screen, console, e
-1189   ]
-1190   screen-should-contain [
-1191     .          .
-1192     .abc       .
-1193     .d1ef      .
-1194     .ghi       .
-1195     .╌╌╌╌╌╌╌╌╌╌.
-1196   ]
-1197 ]
-1198 
-1199 scenario editor-can-undo-ctrl-e [
-1200   local-scope
-1201   # create an editor with some text
-1202   assume-screen 10/width, 5/height
-1203   contents:text <- new [abc
-1204 def
-1205 ghi]
-1206   e:&:editor <- new-editor contents, 0/left, 10/right
-1207   editor-render screen, e
-1208   # move the cursor, then to start of line
-1209   assume-console [
-1210     left-click 2, 1
-1211     press ctrl-e
-1212   ]
-1213   editor-event-loop screen, console, e
-1214   # undo
-1215   assume-console [
-1216     press ctrl-z
-1217   ]
-1218   run [
-1219     editor-event-loop screen, console, e
-1220   ]
-1221   # cursor moves back
-1222   3:num/raw <- get *e, cursor-row:offset
-1223   4:num/raw <- get *e, cursor-column:offset
-1224   memory-should-contain [
-1225     3 <- 2
-1226     4 <- 1
-1227   ]
-1228   # cursor should be in the right place
-1229   assume-console [
-1230     type [1]
-1231   ]
-1232   run [
-1233     editor-event-loop screen, console, e
-1234   ]
-1235   screen-should-contain [
-1236     .          .
-1237     .abc       .
-1238     .d1ef      .
-1239     .ghi       .
-1240     .╌╌╌╌╌╌╌╌╌╌.
-1241   ]
-1242 ]
-1243 
-1244 scenario editor-can-undo-end [
-1245   local-scope
-1246   # create an editor with some text
-1247   assume-screen 10/width, 5/height
-1248   contents:text <- new [abc
-1249 def
-1250 ghi]
-1251   e:&:editor <- new-editor contents, 0/left, 10/right
-1252   editor-render screen, e
-1253   # move the cursor, then to start of line
-1254   assume-console [
-1255     left-click 2, 1
-1256     press end
-1257   ]
-1258   editor-event-loop screen, console, e
-1259   # undo
-1260   assume-console [
-1261     press ctrl-z
-1262   ]
-1263   run [
-1264     editor-event-loop screen, console, e
-1265   ]
-1266   # cursor moves back
-1267   3:num/raw <- get *e, cursor-row:offset
-1268   4:num/raw <- get *e, cursor-column:offset
-1269   memory-should-contain [
-1270     3 <- 2
-1271     4 <- 1
-1272   ]
-1273   # cursor should be in the right place
-1274   assume-console [
-1275     type [1]
-1276   ]
-1277   run [
-1278     editor-event-loop screen, console, e
-1279   ]
-1280   screen-should-contain [
-1281     .          .
-1282     .abc       .
-1283     .d1ef      .
-1284     .ghi       .
-1285     .╌╌╌╌╌╌╌╌╌╌.
-1286   ]
-1287 ]
-1288 
-1289 scenario editor-can-undo-multiple-arrows-in-the-same-direction [
-1290   local-scope
-1291   # create an editor with some text
-1292   assume-screen 10/width, 5/height
-1293   contents:text <- new [abc
-1294 def
-1295 ghi]
-1296   e:&:editor <- new-editor contents, 0/left, 10/right
-1297   editor-render screen, e
-1298   # move the cursor
-1299   assume-console [
-1300     left-click 2, 1
-1301     press right-arrow
-1302     press right-arrow
-1303     press up-arrow
-1304   ]
-1305   editor-event-loop screen, console, e
-1306   3:num/raw <- get *e, cursor-row:offset
-1307   4:num/raw <- get *e, cursor-column:offset
-1308   memory-should-contain [
-1309     3 <- 1
-1310     4 <- 3
-1311   ]
-1312   # undo
-1313   assume-console [
-1314     press ctrl-z
-1315   ]
-1316   run [
-1317     editor-event-loop screen, console, e
-1318   ]
-1319   # up-arrow is undone
-1320   3:num/raw <- get *e, cursor-row:offset
-1321   4:num/raw <- get *e, cursor-column:offset
-1322   memory-should-contain [
-1323     3 <- 2
-1324     4 <- 3
-1325   ]
-1326   # undo again
-1327   assume-console [
-1328     press ctrl-z
-1329   ]
-1330   run [
-1331     editor-event-loop screen, console, e
-1332   ]
-1333   # both right-arrows are undone
-1334   3:num/raw <- get *e, cursor-row:offset
-1335   4:num/raw <- get *e, cursor-column:offset
-1336   memory-should-contain [
-1337     3 <- 2
-1338     4 <- 1
-1339   ]
-1340 ]
-1341 
-1342 # redo cursor movement and scroll
-1343 
-1344 scenario editor-redo-touch [
-1345   local-scope
-1346   # create an editor with some text, click on a character, undo
-1347   assume-screen 10/width, 5/height
-1348   contents:text <- new [abc
-1349 def
-1350 ghi]
-1351   e:&:editor <- new-editor contents, 0/left, 10/right
-1352   editor-render screen, e
-1353   assume-console [
-1354     left-click 3, 1
-1355     press ctrl-z
-1356   ]
-1357   editor-event-loop screen, console, e
-1358   # redo
-1359   assume-console [
-1360     press ctrl-y
-1361   ]
-1362   run [
-1363     editor-event-loop screen, console, e
-1364   ]
-1365   # cursor moves to left-click
-1366   3:num/raw <- get *e, cursor-row:offset
-1367   4:num/raw <- get *e, cursor-column:offset
-1368   memory-should-contain [
-1369     3 <- 3
-1370     4 <- 1
-1371   ]
-1372   # cursor should be in the right place
-1373   assume-console [
-1374     type [1]
-1375   ]
-1376   run [
-1377     editor-event-loop screen, console, e
-1378   ]
-1379   screen-should-contain [
-1380     .          .
-1381     .abc       .
-1382     .def       .
-1383     .g1hi      .
-1384     .╌╌╌╌╌╌╌╌╌╌.
-1385   ]
-1386 ]
-1387 
-1388 after <handle-redo> [
-1389   {
-1390     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
-1391     break-unless is-move?
-1392     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1393     cursor-row <- get move, after-row:offset
-1394     *editor <- put *editor, cursor-row:offset, cursor-row
-1395     cursor-column <- get move, after-column:offset
-1396     *editor <- put *editor, cursor-column:offset, cursor-column
-1397     top:&:duplex-list:char <- get move, after-top-of-screen:offset
-1398     *editor <- put *editor, top-of-screen:offset, top
-1399   }
-1400 ]
-1401 
-1402 scenario editor-separates-undo-insert-from-undo-cursor-move [
-1403   local-scope
-1404   # create an editor, type some text, move the cursor, type some more text
-1405   assume-screen 10/width, 5/height
-1406   e:&:editor <- new-editor [], 0/left, 10/right
-1407   editor-render screen, e
-1408   assume-console [
-1409     type [abc]
-1410     left-click 1, 1
-1411     type [d]
-1412   ]
-1413   editor-event-loop screen, console, e
-1414   3:num/raw <- get *e, cursor-row:offset
-1415   4:num/raw <- get *e, cursor-column:offset
-1416   screen-should-contain [
-1417     .          .
-1418     .adbc      .
-1419     .╌╌╌╌╌╌╌╌╌╌.
-1420     .          .
-1421   ]
-1422   memory-should-contain [
-1423     3 <- 1
-1424     4 <- 2
-1425   ]
-1426   # undo
-1427   assume-console [
-1428     press ctrl-z
-1429   ]
-1430   run [
-1431     editor-event-loop screen, console, e
-1432     3:num/raw <- get *e, cursor-row:offset
-1433     4:num/raw <- get *e, cursor-column:offset
-1434   ]
-1435   # last letter typed is deleted
-1436   screen-should-contain [
-1437     .          .
-1438     .abc       .
-1439     .╌╌╌╌╌╌╌╌╌╌.
-1440     .          .
-1441   ]
-1442   memory-should-contain [
-1443     3 <- 1
-1444     4 <- 1
-1445   ]
-1446   # undo again
-1447   assume-console [
-1448     press ctrl-z
-1449   ]
-1450   run [
-1451     editor-event-loop screen, console, e
-1452     3:num/raw <- get *e, cursor-row:offset
-1453     4:num/raw <- get *e, cursor-column:offset
-1454   ]
-1455   # no change to screen; cursor moves
-1456   screen-should-contain [
-1457     .          .
-1458     .abc       .
-1459     .╌╌╌╌╌╌╌╌╌╌.
-1460     .          .
-1461   ]
-1462   memory-should-contain [
-1463     3 <- 1
-1464     4 <- 3
-1465   ]
-1466   # undo again
-1467   assume-console [
-1468     press ctrl-z
-1469   ]
-1470   run [
-1471     editor-event-loop screen, console, e
-1472     3:num/raw <- get *e, cursor-row:offset
-1473     4:num/raw <- get *e, cursor-column:offset
-1474   ]
-1475   # screen empty
-1476   screen-should-contain [
-1477     .          .
-1478     .          .
-1479     .╌╌╌╌╌╌╌╌╌╌.
-1480     .          .
-1481   ]
-1482   memory-should-contain [
-1483     3 <- 1
-1484     4 <- 0
-1485   ]
-1486   # redo
-1487   assume-console [
-1488     press ctrl-y
-1489   ]
-1490   run [
-1491     editor-event-loop screen, console, e
-1492     3:num/raw <- get *e, cursor-row:offset
-1493     4:num/raw <- get *e, cursor-column:offset
-1494   ]
-1495   # first insert
-1496   screen-should-contain [
-1497     .          .
-1498     .abc       .
-1499     .╌╌╌╌╌╌╌╌╌╌.
-1500     .          .
-1501   ]
-1502   memory-should-contain [
-1503     3 <- 1
-1504     4 <- 3
-1505   ]
-1506   # redo again
-1507   assume-console [
-1508     press ctrl-y
-1509   ]
-1510   run [
-1511     editor-event-loop screen, console, e
-1512     3:num/raw <- get *e, cursor-row:offset
-1513     4:num/raw <- get *e, cursor-column:offset
-1514   ]
-1515   # cursor moves
-1516   screen-should-contain [
-1517     .          .
-1518     .abc       .
-1519     .╌╌╌╌╌╌╌╌╌╌.
-1520     .          .
-1521   ]
-1522   # cursor moves
-1523   memory-should-contain [
-1524     3 <- 1
-1525     4 <- 1
-1526   ]
-1527   # redo again
-1528   assume-console [
-1529     press ctrl-y
-1530   ]
-1531   run [
-1532     editor-event-loop screen, console, e
-1533     3:num/raw <- get *e, cursor-row:offset
-1534     4:num/raw <- get *e, cursor-column:offset
-1535   ]
-1536   # second insert
-1537   screen-should-contain [
-1538     .          .
-1539     .adbc      .
-1540     .╌╌╌╌╌╌╌╌╌╌.
-1541     .          .
-1542   ]
-1543   memory-should-contain [
-1544     3 <- 1
-1545     4 <- 2
-1546   ]
-1547 ]
-1548 
-1549 # undo backspace
-1550 
-1551 scenario editor-can-undo-and-redo-backspace [
-1552   local-scope
-1553   # create an editor
-1554   assume-screen 10/width, 5/height
-1555   e:&:editor <- new-editor [], 0/left, 10/right
-1556   editor-render screen, e
-1557   # insert some text and hit backspace
-1558   assume-console [
-1559     type [abc]
-1560     press backspace
-1561     press backspace
-1562   ]
-1563   editor-event-loop screen, console, e
-1564   screen-should-contain [
-1565     .          .
-1566     .a         .
-1567     .╌╌╌╌╌╌╌╌╌╌.
-1568     .          .
-1569   ]
-1570   3:num/raw <- get *e, cursor-row:offset
-1571   4:num/raw <- get *e, cursor-column:offset
-1572   memory-should-contain [
-1573     3 <- 1
-1574     4 <- 1
-1575   ]
-1576   # undo
-1577   assume-console [
-1578     press ctrl-z
-1579   ]
-1580   run [
-1581     editor-event-loop screen, console, e
-1582   ]
-1583   3:num/raw <- get *e, cursor-row:offset
-1584   4:num/raw <- get *e, cursor-column:offset
-1585   memory-should-contain [
-1586     3 <- 1
-1587     4 <- 3
-1588   ]
-1589   screen-should-contain [
-1590     .          .
-1591     .abc       .
-1592     .╌╌╌╌╌╌╌╌╌╌.
-1593     .          .
-1594   ]
-1595   # redo
-1596   assume-console [
-1597     press ctrl-y
-1598   ]
-1599   run [
-1600     editor-event-loop screen, console, e
-1601   ]
-1602   3:num/raw <- get *e, cursor-row:offset
-1603   4:num/raw <- get *e, cursor-column:offset
-1604   memory-should-contain [
-1605     3 <- 1
-1606     4 <- 1
-1607   ]
-1608   screen-should-contain [
-1609     .          .
-1610     .a         .
-1611     .╌╌╌╌╌╌╌╌╌╌.
-1612     .          .
-1613   ]
-1614 ]
-1615 
-1616 # save operation to undo
-1617 after <backspace-character-begin> [
-1618   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1619 ]
-1620 before <backspace-character-end> [
-1621   {
-1622     break-unless backspaced-cell  # backspace failed; don't add an undo operation
-1623     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1624     cursor-row:num <- get *editor, cursor-row:offset
-1625     cursor-column:num <- get *editor, cursor-row:offset
-1626     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1627     undo:&:list:&:operation <- get *editor, undo:offset
-1628     {
-1629       # if previous operation was an insert, coalesce this operation with it
-1630       break-unless undo
-1631       op:&:operation <- first undo
-1632       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1633       break-unless is-delete?
-1634       previous-coalesce-tag:num <- get deletion, tag:offset
-1635       coalesce?:bool <- equal previous-coalesce-tag, 1/coalesce-backspace
-1636       break-unless coalesce?
-1637       deletion <- put deletion, delete-from:offset, before-cursor
-1638       backspaced-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
-1639       insert-range backspaced-cell, backspaced-so-far
-1640       deletion <- put deletion, deleted-text:offset, backspaced-cell
-1641       deletion <- put deletion, after-row:offset, cursor-row
-1642       deletion <- put deletion, after-column:offset, cursor-column
-1643       deletion <- put deletion, after-top-of-screen:offset, top-after
-1644       *op <- merge 2/delete-operation, deletion
-1645       break +done-adding-backspace-operation
-1646     }
-1647     # if not, create a new operation
-1648     op:&:operation <- new operation:type
-1649     deleted-until:&:duplex-list:char <- next before-cursor
-1650     *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
-1651     editor <- add-operation editor, op
-1652     +done-adding-backspace-operation
-1653   }
-1654 ]
-1655 
-1656 after <handle-undo> [
-1657   {
-1658     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1659     break-unless is-delete?
-1660     anchor:&:duplex-list:char <- get deletion, delete-from:offset
-1661     break-unless anchor
-1662     deleted:&:duplex-list:char <- get deletion, deleted-text:offset
-1663     old-cursor:&:duplex-list:char <- last deleted
-1664     insert-range anchor, deleted
-1665     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1666     before-cursor <- copy old-cursor
-1667     cursor-row <- get deletion, before-row:offset
-1668     *editor <- put *editor, cursor-row:offset, cursor-row
-1669     cursor-column <- get deletion, before-column:offset
-1670     *editor <- put *editor, cursor-column:offset, cursor-column
-1671     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
-1672     *editor <- put *editor, top-of-screen:offset, top
-1673   }
-1674 ]
-1675 
-1676 after <handle-redo> [
-1677   {
-1678     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1679     break-unless is-delete?
-1680     start:&:duplex-list:char <- get deletion, delete-from:offset
-1681     end:&:duplex-list:char <- get deletion, delete-until:offset
-1682     data:&:duplex-list:char <- get *editor, data:offset
-1683     remove-between start, end
-1684     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
-1685     cursor-row <- get deletion, after-row:offset
-1686     *editor <- put *editor, cursor-row:offset, cursor-row
-1687     cursor-column <- get deletion, after-column:offset
-1688     *editor <- put *editor, cursor-column:offset, cursor-column
-1689     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
-1690     *editor <- put *editor, top-of-screen:offset, top
-1691   }
-1692 ]
-1693 
-1694 # undo delete
-1695 
-1696 scenario editor-can-undo-and-redo-delete [
-1697   local-scope
-1698   # create an editor
-1699   assume-screen 10/width, 5/height
-1700   e:&:editor <- new-editor [], 0/left, 10/right
-1701   editor-render screen, e
-1702   # insert some text and hit delete and backspace a few times
-1703   assume-console [
-1704     type [abcdef]
-1705     left-click 1, 2
-1706     press delete
-1707     press backspace
-1708     press delete
-1709     press delete
-1710   ]
-1711   editor-event-loop screen, console, e
-1712   screen-should-contain [
-1713     .          .
-1714     .af        .
-1715     .╌╌╌╌╌╌╌╌╌╌.
-1716     .          .
-1717   ]
-1718   3:num/raw <- get *e, cursor-row:offset
-1719   4:num/raw <- get *e, cursor-column:offset
-1720   memory-should-contain [
-1721     3 <- 1
-1722     4 <- 1
-1723   ]
-1724   # undo deletes
-1725   assume-console [
-1726     press ctrl-z
-1727   ]
-1728   run [
-1729     editor-event-loop screen, console, e
-1730   ]
-1731   3:num/raw <- get *e, cursor-row:offset
-1732   4:num/raw <- get *e, cursor-column:offset
-1733   memory-should-contain [
-1734     3 <- 1
-1735     4 <- 1
-1736   ]
-1737   screen-should-contain [
-1738     .          .
-1739     .adef      .
-1740     .╌╌╌╌╌╌╌╌╌╌.
-1741     .          .
-1742   ]
-1743   # undo backspace
-1744   assume-console [
-1745     press ctrl-z
-1746   ]
-1747   run [
-1748     editor-event-loop screen, console, e
-1749   ]
-1750   3:num/raw <- get *e, cursor-row:offset
-1751   4:num/raw <- get *e, cursor-column:offset
-1752   memory-should-contain [
-1753     3 <- 1
-1754     4 <- 2
-1755   ]
-1756   screen-should-contain [
-1757     .          .
-1758     .abdef     .
-1759     .╌╌╌╌╌╌╌╌╌╌.
-1760     .          .
-1761   ]
-1762   # undo first delete
-1763   assume-console [
-1764     press ctrl-z
-1765   ]
-1766   run [
-1767     editor-event-loop screen, console, e
-1768   ]
-1769   3:num/raw <- get *e, cursor-row:offset
-1770   4:num/raw <- get *e, cursor-column:offset
-1771   memory-should-contain [
-1772     3 <- 1
-1773     4 <- 2
-1774   ]
-1775   screen-should-contain [
-1776     .          .
-1777     .abcdef    .
-1778     .╌╌╌╌╌╌╌╌╌╌.
-1779     .          .
-1780   ]
-1781   # redo first delete
-1782   assume-console [
-1783     press ctrl-y
-1784   ]
-1785   run [
-1786     editor-event-loop screen, console, e
-1787   ]
-1788   # first line inserted
-1789   3:num/raw <- get *e, cursor-row:offset
-1790   4:num/raw <- get *e, cursor-column:offset
-1791   memory-should-contain [
-1792     3 <- 1
-1793     4 <- 2
-1794   ]
-1795   screen-should-contain [
-1796     .          .
-1797     .abdef     .
-1798     .╌╌╌╌╌╌╌╌╌╌.
-1799     .          .
-1800   ]
-1801   # redo backspace
-1802   assume-console [
-1803     press ctrl-y
-1804   ]
-1805   run [
-1806     editor-event-loop screen, console, e
-1807   ]
-1808   # first line inserted
-1809   3:num/raw <- get *e, cursor-row:offset
-1810   4:num/raw <- get *e, cursor-column:offset
-1811   memory-should-contain [
-1812     3 <- 1
-1813     4 <- 1
-1814   ]
-1815   screen-should-contain [
-1816     .          .
-1817     .adef      .
-1818     .╌╌╌╌╌╌╌╌╌╌.
-1819     .          .
-1820   ]
-1821   # redo deletes
-1822   assume-console [
-1823     press ctrl-y
-1824   ]
-1825   run [
-1826     editor-event-loop screen, console, e
-1827   ]
-1828   # first line inserted
-1829   3:num/raw <- get *e, cursor-row:offset
-1830   4:num/raw <- get *e, cursor-column:offset
-1831   memory-should-contain [
-1832     3 <- 1
-1833     4 <- 1
-1834   ]
-1835   screen-should-contain [
-1836     .          .
-1837     .af        .
-1838     .╌╌╌╌╌╌╌╌╌╌.
-1839     .          .
-1840   ]
-1841 ]
-1842 
-1843 after <delete-character-begin> [
-1844   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1845 ]
-1846 before <delete-character-end> [
-1847   {
-1848     break-unless deleted-cell  # delete failed; don't add an undo operation
-1849     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1850     cursor-row:num <- get *editor, cursor-row:offset
-1851     cursor-column:num <- get *editor, cursor-column:offset
-1852     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-1853     undo:&:list:&:operation <- get *editor, undo:offset
-1854     {
-1855       # if previous operation was an insert, coalesce this operation with it
-1856       break-unless undo
-1857       op:&:operation <- first undo
-1858       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
-1859       break-unless is-delete?
-1860       previous-coalesce-tag:num <- get deletion, tag:offset
-1861       coalesce?:bool <- equal previous-coalesce-tag, 2/coalesce-delete
-1862       break-unless coalesce?
-1863       delete-until:&:duplex-list:char <- next before-cursor
-1864       deletion <- put deletion, delete-until:offset, delete-until
-1865       deleted-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
-1866       deleted-so-far <- append deleted-so-far, deleted-cell
-1867       deletion <- put deletion, deleted-text:offset, deleted-so-far
-1868       deletion <- put deletion, after-row:offset, cursor-row
-1869       deletion <- put deletion, after-column:offset, cursor-column
-1870       deletion <- put deletion, after-top-of-screen:offset, top-after
-1871       *op <- merge 2/delete-operation, deletion
-1872       break +done-adding-delete-operation
-1873     }
-1874     # if not, create a new operation
-1875     op:&:operation <- new operation:type
-1876     deleted-until:&:duplex-list:char <- next before-cursor
-1877     *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
-1878     editor <- add-operation editor, op
-1879     +done-adding-delete-operation
-1880   }
-1881 ]
-1882 
-1883 # undo ctrl-k
-1884 
-1885 scenario editor-can-undo-and-redo-ctrl-k [
-1886   local-scope
-1887   # create an editor
-1888   assume-screen 10/width, 5/height
-1889   contents:text <- new [abc
-1890 def]
-1891   e:&:editor <- new-editor contents, 0/left, 10/right
-1892   editor-render screen, e
-1893   # insert some text and hit delete and backspace a few times
-1894   assume-console [
-1895     left-click 1, 1
-1896     press ctrl-k
-1897   ]
-1898   editor-event-loop screen, console, e
-1899   screen-should-contain [
-1900     .          .
-1901     .a         .
-1902     .def       .
-1903     .╌╌╌╌╌╌╌╌╌╌.
-1904     .          .
-1905   ]
-1906   3:num/raw <- get *e, cursor-row:offset
-1907   4:num/raw <- get *e, cursor-column:offset
-1908   memory-should-contain [
-1909     3 <- 1
-1910     4 <- 1
-1911   ]
-1912   # undo
-1913   assume-console [
-1914     press ctrl-z
-1915   ]
-1916   run [
-1917     editor-event-loop screen, console, e
-1918   ]
-1919   screen-should-contain [
-1920     .          .
-1921     .abc       .
-1922     .def       .
-1923     .╌╌╌╌╌╌╌╌╌╌.
-1924     .          .
-1925   ]
-1926   3:num/raw <- get *e, cursor-row:offset
-1927   4:num/raw <- get *e, cursor-column:offset
-1928   memory-should-contain [
-1929     3 <- 1
-1930     4 <- 1
-1931   ]
-1932   # redo
-1933   assume-console [
-1934     press ctrl-y
-1935   ]
-1936   run [
-1937     editor-event-loop screen, console, e
-1938   ]
-1939   # first line inserted
-1940   screen-should-contain [
-1941     .          .
-1942     .a         .
-1943     .def       .
-1944     .╌╌╌╌╌╌╌╌╌╌.
-1945     .          .
-1946   ]
-1947   3:num/raw <- get *e, cursor-row:offset
-1948   4:num/raw <- get *e, cursor-column:offset
-1949   memory-should-contain [
-1950     3 <- 1
-1951     4 <- 1
-1952   ]
-1953   # cursor should be in the right place
-1954   assume-console [
-1955     type [1]
-1956   ]
-1957   run [
-1958     editor-event-loop screen, console, e
-1959   ]
-1960   screen-should-contain [
-1961     .          .
-1962     .a1        .
-1963     .def       .
-1964     .╌╌╌╌╌╌╌╌╌╌.
-1965     .          .
-1966   ]
-1967 ]
-1968 
-1969 after <delete-to-end-of-line-begin> [
-1970   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-1971 ]
-1972 before <delete-to-end-of-line-end> [
-1973   {
-1974     break-unless deleted-cells  # delete failed; don't add an undo operation
-1975     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-1976     cursor-row:num <- get *editor, cursor-row:offset
-1977     cursor-column:num <- get *editor, cursor-column:offset
-1978     deleted-until:&:duplex-list:char <- next before-cursor
-1979     op:&:operation <- new operation:type
-1980     *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
-1981     editor <- add-operation editor, op
-1982     +done-adding-delete-operation
-1983   }
-1984 ]
-1985 
-1986 # undo ctrl-u
-1987 
-1988 scenario editor-can-undo-and-redo-ctrl-u [
-1989   local-scope
-1990   # create an editor
-1991   assume-screen 10/width, 5/height
-1992   contents:text <- new [abc
-1993 def]
-1994   e:&:editor <- new-editor contents, 0/left, 10/right
-1995   editor-render screen, e
-1996   # insert some text and hit delete and backspace a few times
-1997   assume-console [
-1998     left-click 1, 2
-1999     press ctrl-u
-2000   ]
-2001   editor-event-loop screen, console, e
-2002   screen-should-contain [
-2003     .          .
-2004     .c         .
-2005     .def       .
-2006     .╌╌╌╌╌╌╌╌╌╌.
-2007     .          .
-2008   ]
-2009   3:num/raw <- get *e, cursor-row:offset
-2010   4:num/raw <- get *e, cursor-column:offset
-2011   memory-should-contain [
-2012     3 <- 1
-2013     4 <- 0
-2014   ]
-2015   # undo
-2016   assume-console [
-2017     press ctrl-z
-2018   ]
-2019   run [
-2020     editor-event-loop screen, console, e
-2021   ]
-2022   screen-should-contain [
-2023     .          .
-2024     .abc       .
-2025     .def       .
-2026     .╌╌╌╌╌╌╌╌╌╌.
-2027     .          .
-2028   ]
-2029   3:num/raw <- get *e, cursor-row:offset
-2030   4:num/raw <- get *e, cursor-column:offset
-2031   memory-should-contain [
-2032     3 <- 1
-2033     4 <- 2
-2034   ]
-2035   # redo
-2036   assume-console [
-2037     press ctrl-y
-2038   ]
-2039   run [
-2040     editor-event-loop screen, console, e
-2041   ]
-2042   # first line inserted
-2043   screen-should-contain [
-2044     .          .
-2045     .c         .
-2046     .def       .
-2047     .╌╌╌╌╌╌╌╌╌╌.
-2048     .          .
-2049   ]
-2050   3:num/raw <- get *e, cursor-row:offset
-2051   4:num/raw <- get *e, cursor-column:offset
-2052   memory-should-contain [
-2053     3 <- 1
-2054     4 <- 0
-2055   ]
-2056   # cursor should be in the right place
-2057   assume-console [
-2058     type [1]
-2059   ]
-2060   run [
-2061     editor-event-loop screen, console, e
-2062   ]
-2063   screen-should-contain [
-2064     .          .
-2065     .1c        .
-2066     .def       .
-2067     .╌╌╌╌╌╌╌╌╌╌.
-2068     .          .
-2069   ]
-2070 ]
-2071 
-2072 after <delete-to-start-of-line-begin> [
-2073   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
-2074 ]
-2075 before <delete-to-start-of-line-end> [
-2076   {
-2077     break-unless deleted-cells  # delete failed; don't add an undo operation
-2078     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
-2079     op:&:operation <- new operation:type
-2080     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
-2081     deleted-until:&:duplex-list:char <- next before-cursor
-2082     cursor-row:num <- get *editor, cursor-row:offset
-2083     cursor-column:num <- get *editor, cursor-column:offset
-2084     *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
-2085     editor <- add-operation editor, op
-2086     +done-adding-delete-operation
-2087   }
-2088 ]
-2089 
-2090 scenario editor-can-undo-and-redo-ctrl-u-2 [
-2091   local-scope
-2092   # create an editor
-2093   assume-screen 10/width, 5/height
-2094   e:&:editor <- new-editor [], 0/left, 10/right
-2095   editor-render screen, e
-2096   # insert some text and hit delete and backspace a few times
-2097   assume-console [
-2098     type [abc]
-2099     press ctrl-u
-2100     press ctrl-z
-2101   ]
-2102   editor-event-loop screen, console, e
-2103   screen-should-contain [
-2104     .          .
-2105     .abc       .
-2106     .╌╌╌╌╌╌╌╌╌╌.
-2107     .          .
-2108   ]
-2109 ]
+   1 ## undo/redo
+   2 
+   3 # for every undoable event, create a type of *operation* that contains all the
+   4 # information needed to reverse it
+   5 exclusive-container operation [
+   6   typing:insert-operation
+   7   move:move-operation
+   8   delete:delete-operation
+   9 ]
+  10 
+  11 container insert-operation [
+  12   before-row:num
+  13   before-column:num
+  14   before-top-of-screen:&:duplex-list:char
+  15   after-row:num
+  16   after-column:num
+  17   after-top-of-screen:&:duplex-list:char
+  18   # inserted text is from 'insert-from' until 'insert-until'; list doesn't have to terminate
+  19   insert-from:&:duplex-list:char
+  20   insert-until:&:duplex-list:char
+  21   tag:num  # event causing this operation; might be used to coalesce runs of similar events
+  22     # 0: no coalesce (enter+indent)
+  23     # 1: regular alphanumeric characters
+  24 ]
+  25 
+  26 container move-operation [
+  27   before-row:num
+  28   before-column:num
+  29   before-top-of-screen:&:duplex-list:char
+  30   after-row:num
+  31   after-column:num
+  32   after-top-of-screen:&:duplex-list:char
+  33   tag:num  # event causing this operation; might be used to coalesce runs of similar events
+  34     # 0: no coalesce (touch events, etc)
+  35     # 1: left arrow
+  36     # 2: right arrow
+  37     # 3: up arrow
+  38     # 4: down arrow
+  39 ]
+  40 
+  41 container delete-operation [
+  42   before-row:num
+  43   before-column:num
+  44   before-top-of-screen:&:duplex-list:char
+  45   after-row:num
+  46   after-column:num
+  47   after-top-of-screen:&:duplex-list:char
+  48   deleted-text:&:duplex-list:char
+  49   delete-from:&:duplex-list:char
+  50   delete-until:&:duplex-list:char
+  51   tag:num  # event causing this operation; might be used to coalesce runs of similar events
+  52     # 0: no coalesce (ctrl-k, ctrl-u)
+  53     # 1: backspace
+  54     # 2: delete
+  55 ]
+  56 
+  57 # every editor accumulates a list of operations to undo/redo
+  58 container editor [
+  59   undo:&:list:&:operation
+  60   redo:&:list:&:operation
+  61 ]
+  62 
+  63 # ctrl-z - undo operation
+  64 after <handle-special-character> [
+  65   {
+  66     undo?:bool <- equal c, 26/ctrl-z
+  67     break-unless undo?
+  68     undo:&:list:&:operation <- get *editor, undo:offset
+  69     break-unless undo
+  70     op:&:operation <- first undo
+  71     undo <- rest undo
+  72     *editor <- put *editor, undo:offset, undo
+  73     redo:&:list:&:operation <- get *editor, redo:offset
+  74     redo <- push op, redo
+  75     *editor <- put *editor, redo:offset, redo
+  76     <handle-undo>
+  77     return 1/go-render
+  78   }
+  79 ]
+  80 
+  81 # ctrl-y - redo operation
+  82 after <handle-special-character> [
+  83   {
+  84     redo?:bool <- equal c, 25/ctrl-y
+  85     break-unless redo?
+  86     redo:&:list:&:operation <- get *editor, redo:offset
+  87     break-unless redo
+  88     op:&:operation <- first redo
+  89     redo <- rest redo
+  90     *editor <- put *editor, redo:offset, redo
+  91     undo:&:list:&:operation <- get *editor, undo:offset
+  92     undo <- push op, undo
+  93     *editor <- put *editor, undo:offset, undo
+  94     <handle-redo>
+  95     return 1/go-render
+  96   }
+  97 ]
+  98 
+  99 # undo typing
+ 100 
+ 101 scenario editor-can-undo-typing [
+ 102   local-scope
+ 103   # create an editor and type a character
+ 104   assume-screen 10/width, 5/height
+ 105   e:&:editor <- new-editor [], 0/left, 10/right
+ 106   editor-render screen, e
+ 107   assume-console [
+ 108     type [0]
+ 109   ]
+ 110   editor-event-loop screen, console, e
+ 111   # undo
+ 112   assume-console [
+ 113     press ctrl-z
+ 114   ]
+ 115   run [
+ 116     editor-event-loop screen, console, e
+ 117   ]
+ 118   # character should be gone
+ 119   screen-should-contain [
+ 120     .          .
+ 121     .          .
+ 122     .╌╌╌╌╌╌╌╌╌╌.
+ 123     .          .
+ 124   ]
+ 125   # cursor should be in the right place
+ 126   assume-console [
+ 127     type [1]
+ 128   ]
+ 129   run [
+ 130     editor-event-loop screen, console, e
+ 131   ]
+ 132   screen-should-contain [
+ 133     .          .
+ 134     .1         .
+ 135     .╌╌╌╌╌╌╌╌╌╌.
+ 136     .          .
+ 137   ]
+ 138 ]
+ 139 
+ 140 # save operation to undo
+ 141 after <insert-character-begin> [
+ 142   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 143   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
+ 144 ]
+ 145 before <insert-character-end> [
+ 146   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 147   cursor-row:num <- get *editor, cursor-row:offset
+ 148   cursor-column:num <- get *editor, cursor-column:offset
+ 149   undo:&:list:&:operation <- get *editor, undo:offset
+ 150   {
+ 151     # if previous operation was an insert, coalesce this operation with it
+ 152     break-unless undo
+ 153     op:&:operation <- first undo
+ 154     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
+ 155     break-unless is-insert?
+ 156     previous-coalesce-tag:num <- get typing, tag:offset
+ 157     break-unless previous-coalesce-tag
+ 158     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
+ 159     insert-until:&:duplex-list:char <- next before-cursor
+ 160     typing <- put typing, insert-until:offset, insert-until
+ 161     typing <- put typing, after-row:offset, cursor-row
+ 162     typing <- put typing, after-column:offset, cursor-column
+ 163     typing <- put typing, after-top-of-screen:offset, top-after
+ 164     *op <- merge 0/insert-operation, typing
+ 165     break +done-adding-insert-operation
+ 166   }
+ 167   # if not, create a new operation
+ 168   insert-from:&:duplex-list:char <- next cursor-before
+ 169   insert-to:&:duplex-list:char <- next insert-from
+ 170   op:&:operation <- new operation:type
+ 171   *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
+ 172   editor <- add-operation editor, op
+ 173   +done-adding-insert-operation
+ 174 ]
+ 175 
+ 176 # enter operations never coalesce with typing before or after
+ 177 after <insert-enter-begin> [
+ 178   cursor-row-before:num <- copy cursor-row
+ 179   cursor-column-before:num <- copy cursor-column
+ 180   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 181   cursor-before:&:duplex-list:char <- get *editor, before-cursor:offset
+ 182 ]
+ 183 before <insert-enter-end> [
+ 184   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 185   cursor-row:num <- get *editor, cursor-row:offset
+ 186   cursor-column:num <- get *editor, cursor-row:offset
+ 187   # never coalesce
+ 188   insert-from:&:duplex-list:char <- next cursor-before
+ 189   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
+ 190   insert-to:&:duplex-list:char <- next before-cursor
+ 191   op:&:operation <- new operation:type
+ 192   *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
+ 193   editor <- add-operation editor, op
+ 194 ]
+ 195 
+ 196 # Everytime you add a new operation to the undo stack, be sure to clear the
+ 197 # redo stack, because it's now obsolete.
+ 198 # Beware: since we're counting cursor moves as operations, this means just
+ 199 # moving the cursor can lose work on the undo stack.
+ 200 def add-operation editor:&:editor, op:&:operation -> editor:&:editor [
+ 201   local-scope
+ 202   load-ingredients
+ 203   undo:&:list:&:operation <- get *editor, undo:offset
+ 204   undo <- push op undo
+ 205   *editor <- put *editor, undo:offset, undo
+ 206   redo:&:list:&:operation <- get *editor, redo:offset
+ 207   redo <- copy 0
+ 208   *editor <- put *editor, redo:offset, redo
+ 209 ]
+ 210 
+ 211 after <handle-undo> [
+ 212   {
+ 213     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
+ 214     break-unless is-insert?
+ 215     start:&:duplex-list:char <- get typing, insert-from:offset
+ 216     end:&:duplex-list:char <- get typing, insert-until:offset
+ 217     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+ 218     before-cursor:&:duplex-list:char <- prev start
+ 219     *editor <- put *editor, before-cursor:offset, before-cursor
+ 220     remove-between before-cursor, end
+ 221     cursor-row <- get typing, before-row:offset
+ 222     *editor <- put *editor, cursor-row:offset, cursor-row
+ 223     cursor-column <- get typing, before-column:offset
+ 224     *editor <- put *editor, cursor-column:offset, cursor-column
+ 225     top:&:duplex-list:char <- get typing, before-top-of-screen:offset
+ 226     *editor <- put *editor, top-of-screen:offset, top
+ 227   }
+ 228 ]
+ 229 
+ 230 scenario editor-can-undo-typing-multiple [
+ 231   local-scope
+ 232   # create an editor and type multiple characters
+ 233   assume-screen 10/width, 5/height
+ 234   e:&:editor <- new-editor [], 0/left, 10/right
+ 235   editor-render screen, e
+ 236   assume-console [
+ 237     type [012]
+ 238   ]
+ 239   editor-event-loop screen, console, e
+ 240   # undo
+ 241   assume-console [
+ 242     press ctrl-z
+ 243   ]
+ 244   run [
+ 245     editor-event-loop screen, console, e
+ 246   ]
+ 247   # all characters must be gone
+ 248   screen-should-contain [
+ 249     .          .
+ 250     .          .
+ 251     .╌╌╌╌╌╌╌╌╌╌.
+ 252     .          .
+ 253   ]
+ 254 ]
+ 255 
+ 256 scenario editor-can-undo-typing-multiple-2 [
+ 257   local-scope
+ 258   # create an editor with some text
+ 259   assume-screen 10/width, 5/height
+ 260   e:&:editor <- new-editor [a], 0/left, 10/right
+ 261   editor-render screen, e
+ 262   # type some characters
+ 263   assume-console [
+ 264     type [012]
+ 265   ]
+ 266   editor-event-loop screen, console, e
+ 267   screen-should-contain [
+ 268     .          .
+ 269     .012a      .
+ 270     .╌╌╌╌╌╌╌╌╌╌.
+ 271     .          .
+ 272   ]
+ 273   # undo
+ 274   assume-console [
+ 275     press ctrl-z
+ 276   ]
+ 277   run [
+ 278     editor-event-loop screen, console, e
+ 279   ]
+ 280   # back to original text
+ 281   screen-should-contain [
+ 282     .          .
+ 283     .a         .
+ 284     .╌╌╌╌╌╌╌╌╌╌.
+ 285     .          .
+ 286   ]
+ 287   # cursor should be in the right place
+ 288   assume-console [
+ 289     type [3]
+ 290   ]
+ 291   run [
+ 292     editor-event-loop screen, console, e
+ 293   ]
+ 294   screen-should-contain [
+ 295     .          .
+ 296     .3a        .
+ 297     .╌╌╌╌╌╌╌╌╌╌.
+ 298     .          .
+ 299   ]
+ 300 ]
+ 301 
+ 302 scenario editor-can-undo-typing-enter [
+ 303   local-scope
+ 304   # create an editor with some text
+ 305   assume-screen 10/width, 5/height
+ 306   e:&:editor <- new-editor [  abc], 0/left, 10/right
+ 307   editor-render screen, e
+ 308   # new line
+ 309   assume-console [
+ 310     left-click 1, 8
+ 311     press enter
+ 312   ]
+ 313   editor-event-loop screen, console, e
+ 314   screen-should-contain [
+ 315     .          .
+ 316     .  abc     .
+ 317     .          .
+ 318     .╌╌╌╌╌╌╌╌╌╌.
+ 319     .          .
+ 320   ]
+ 321   # line is indented
+ 322   3:num/raw <- get *e, cursor-row:offset
+ 323   4:num/raw <- get *e, cursor-column:offset
+ 324   memory-should-contain [
+ 325     3 <- 2
+ 326     4 <- 2
+ 327   ]
+ 328   # undo
+ 329   assume-console [
+ 330     press ctrl-z
+ 331   ]
+ 332   run [
+ 333     editor-event-loop screen, console, e
+ 334   ]
+ 335   3:num/raw <- get *e, cursor-row:offset
+ 336   4:num/raw <- get *e, cursor-column:offset
+ 337   memory-should-contain [
+ 338     3 <- 1
+ 339     4 <- 5
+ 340   ]
+ 341   # back to original text
+ 342   screen-should-contain [
+ 343     .          .
+ 344     .  abc     .
+ 345     .╌╌╌╌╌╌╌╌╌╌.
+ 346     .          .
+ 347   ]
+ 348   # cursor should be at end of line
+ 349   assume-console [
+ 350     type [1]
+ 351   ]
+ 352   run [
+ 353     editor-event-loop screen, console, e
+ 354   ]
+ 355   screen-should-contain [
+ 356     .          .
+ 357     .  abc1    .
+ 358     .╌╌╌╌╌╌╌╌╌╌.
+ 359     .          .
+ 360   ]
+ 361 ]
+ 362 
+ 363 # redo typing
+ 364 
+ 365 scenario editor-redo-typing [
+ 366   local-scope
+ 367   # create an editor, type something, undo
+ 368   assume-screen 10/width, 5/height
+ 369   e:&:editor <- new-editor [a], 0/left, 10/right
+ 370   editor-render screen, e
+ 371   assume-console [
+ 372     type [012]
+ 373     press ctrl-z
+ 374   ]
+ 375   editor-event-loop screen, console, e
+ 376   screen-should-contain [
+ 377     .          .
+ 378     .a         .
+ 379     .╌╌╌╌╌╌╌╌╌╌.
+ 380     .          .
+ 381   ]
+ 382   # redo
+ 383   assume-console [
+ 384     press ctrl-y
+ 385   ]
+ 386   run [
+ 387     editor-event-loop screen, console, e
+ 388   ]
+ 389   # all characters must be back
+ 390   screen-should-contain [
+ 391     .          .
+ 392     .012a      .
+ 393     .╌╌╌╌╌╌╌╌╌╌.
+ 394     .          .
+ 395   ]
+ 396   # cursor should be in the right place
+ 397   assume-console [
+ 398     type [3]
+ 399   ]
+ 400   run [
+ 401     editor-event-loop screen, console, e
+ 402   ]
+ 403   screen-should-contain [
+ 404     .          .
+ 405     .0123a     .
+ 406     .╌╌╌╌╌╌╌╌╌╌.
+ 407     .          .
+ 408   ]
+ 409 ]
+ 410 
+ 411 after <handle-redo> [
+ 412   {
+ 413     typing:insert-operation, is-insert?:bool <- maybe-convert *op, typing:variant
+ 414     break-unless is-insert?
+ 415     before-cursor <- get *editor, before-cursor:offset
+ 416     insert-from:&:duplex-list:char <- get typing, insert-from:offset  # ignore insert-to because it's already been spliced away
+ 417     # assert insert-to matches next(before-cursor)
+ 418     insert-range before-cursor, insert-from
+ 419     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+ 420     cursor-row <- get typing, after-row:offset
+ 421     *editor <- put *editor, cursor-row:offset, cursor-row
+ 422     cursor-column <- get typing, after-column:offset
+ 423     *editor <- put *editor, cursor-column:offset, cursor-column
+ 424     top:&:duplex-list:char <- get typing, after-top-of-screen:offset
+ 425     *editor <- put *editor, top-of-screen:offset, top
+ 426   }
+ 427 ]
+ 428 
+ 429 scenario editor-redo-typing-empty [
+ 430   local-scope
+ 431   # create an editor, type something, undo
+ 432   assume-screen 10/width, 5/height
+ 433   e:&:editor <- new-editor [], 0/left, 10/right
+ 434   editor-render screen, e
+ 435   assume-console [
+ 436     type [012]
+ 437     press ctrl-z
+ 438   ]
+ 439   editor-event-loop screen, console, e
+ 440   screen-should-contain [
+ 441     .          .
+ 442     .          .
+ 443     .╌╌╌╌╌╌╌╌╌╌.
+ 444     .          .
+ 445   ]
+ 446   # redo
+ 447   assume-console [
+ 448     press ctrl-y
+ 449   ]
+ 450   run [
+ 451     editor-event-loop screen, console, e
+ 452   ]
+ 453   # all characters must be back
+ 454   screen-should-contain [
+ 455     .          .
+ 456     .012       .
+ 457     .╌╌╌╌╌╌╌╌╌╌.
+ 458     .          .
+ 459   ]
+ 460   # cursor should be in the right place
+ 461   assume-console [
+ 462     type [3]
+ 463   ]
+ 464   run [
+ 465     editor-event-loop screen, console, e
+ 466   ]
+ 467   screen-should-contain [
+ 468     .          .
+ 469     .0123      .
+ 470     .╌╌╌╌╌╌╌╌╌╌.
+ 471     .          .
+ 472   ]
+ 473 ]
+ 474 
+ 475 scenario editor-work-clears-redo-stack [
+ 476   local-scope
+ 477   # create an editor with some text, do some work, undo
+ 478   assume-screen 10/width, 5/height
+ 479   contents:text <- new [abc
+ 480 def
+ 481 ghi]
+ 482   e:&:editor <- new-editor contents, 0/left, 10/right
+ 483   editor-render screen, e
+ 484   assume-console [
+ 485     type [1]
+ 486     press ctrl-z
+ 487   ]
+ 488   editor-event-loop screen, console, e
+ 489   # do some more work
+ 490   assume-console [
+ 491     type [0]
+ 492   ]
+ 493   editor-event-loop screen, console, e
+ 494   screen-should-contain [
+ 495     .          .
+ 496     .0abc      .
+ 497     .def       .
+ 498     .ghi       .
+ 499     .╌╌╌╌╌╌╌╌╌╌.
+ 500   ]
+ 501   # redo
+ 502   assume-console [
+ 503     press ctrl-y
+ 504   ]
+ 505   run [
+ 506     editor-event-loop screen, console, e
+ 507   ]
+ 508   # nothing should happen
+ 509   screen-should-contain [
+ 510     .          .
+ 511     .0abc      .
+ 512     .def       .
+ 513     .ghi       .
+ 514     .╌╌╌╌╌╌╌╌╌╌.
+ 515   ]
+ 516 ]
+ 517 
+ 518 scenario editor-can-redo-typing-and-enter-and-tab [
+ 519   local-scope
+ 520   # create an editor
+ 521   assume-screen 10/width, 5/height
+ 522   e:&:editor <- new-editor [], 0/left, 10/right
+ 523   editor-render screen, e
+ 524   # insert some text and tabs, hit enter, some more text and tabs
+ 525   assume-console [
+ 526     press tab
+ 527     type [ab]
+ 528     press tab
+ 529     type [cd]
+ 530     press enter
+ 531     press tab
+ 532     type [efg]
+ 533   ]
+ 534   editor-event-loop screen, console, e
+ 535   screen-should-contain [
+ 536     .          .
+ 537     .  ab  cd  .
+ 538     .    efg   .
+ 539     .╌╌╌╌╌╌╌╌╌╌.
+ 540     .          .
+ 541   ]
+ 542   3:num/raw <- get *e, cursor-row:offset
+ 543   4:num/raw <- get *e, cursor-column:offset
+ 544   memory-should-contain [
+ 545     3 <- 2
+ 546     4 <- 7
+ 547   ]
+ 548   # undo
+ 549   assume-console [
+ 550     press ctrl-z
+ 551   ]
+ 552   run [
+ 553     editor-event-loop screen, console, e
+ 554   ]
+ 555   # typing in second line deleted, but not indent
+ 556   3:num/raw <- get *e, cursor-row:offset
+ 557   4:num/raw <- get *e, cursor-column:offset
+ 558   memory-should-contain [
+ 559     3 <- 2
+ 560     4 <- 2
+ 561   ]
+ 562   screen-should-contain [
+ 563     .          .
+ 564     .  ab  cd  .
+ 565     .          .
+ 566     .╌╌╌╌╌╌╌╌╌╌.
+ 567     .          .
+ 568   ]
+ 569   # undo again
+ 570   assume-console [
+ 571     press ctrl-z
+ 572   ]
+ 573   run [
+ 574     editor-event-loop screen, console, e
+ 575   ]
+ 576   # indent and newline deleted
+ 577   3:num/raw <- get *e, cursor-row:offset
+ 578   4:num/raw <- get *e, cursor-column:offset
+ 579   memory-should-contain [
+ 580     3 <- 1
+ 581     4 <- 8
+ 582   ]
+ 583   screen-should-contain [
+ 584     .          .
+ 585     .  ab  cd  .
+ 586     .╌╌╌╌╌╌╌╌╌╌.
+ 587     .          .
+ 588   ]
+ 589   # undo again
+ 590   assume-console [
+ 591     press ctrl-z
+ 592   ]
+ 593   run [
+ 594     editor-event-loop screen, console, e
+ 595   ]
+ 596   # empty screen
+ 597   3:num/raw <- get *e, cursor-row:offset
+ 598   4:num/raw <- get *e, cursor-column:offset
+ 599   memory-should-contain [
+ 600     3 <- 1
+ 601     4 <- 0
+ 602   ]
+ 603   screen-should-contain [
+ 604     .          .
+ 605     .          .
+ 606     .╌╌╌╌╌╌╌╌╌╌.
+ 607     .          .
+ 608   ]
+ 609   # redo
+ 610   assume-console [
+ 611     press ctrl-y
+ 612   ]
+ 613   run [
+ 614     editor-event-loop screen, console, e
+ 615   ]
+ 616   # first line inserted
+ 617   3:num/raw <- get *e, cursor-row:offset
+ 618   4:num/raw <- get *e, cursor-column:offset
+ 619   memory-should-contain [
+ 620     3 <- 1
+ 621     4 <- 8
+ 622   ]
+ 623   screen-should-contain [
+ 624     .          .
+ 625     .  ab  cd  .
+ 626     .╌╌╌╌╌╌╌╌╌╌.
+ 627     .          .
+ 628   ]
+ 629   # redo again
+ 630   assume-console [
+ 631     press ctrl-y
+ 632   ]
+ 633   run [
+ 634     editor-event-loop screen, console, e
+ 635   ]
+ 636   # newline and indent inserted
+ 637   3:num/raw <- get *e, cursor-row:offset
+ 638   4:num/raw <- get *e, cursor-column:offset
+ 639   memory-should-contain [
+ 640     3 <- 2
+ 641     4 <- 2
+ 642   ]
+ 643   screen-should-contain [
+ 644     .          .
+ 645     .  ab  cd  .
+ 646     .          .
+ 647     .╌╌╌╌╌╌╌╌╌╌.
+ 648     .          .
+ 649   ]
+ 650   # redo again
+ 651   assume-console [
+ 652     press ctrl-y
+ 653   ]
+ 654   run [
+ 655     editor-event-loop screen, console, e
+ 656   ]
+ 657   # indent and newline deleted
+ 658   3:num/raw <- get *e, cursor-row:offset
+ 659   4:num/raw <- get *e, cursor-column:offset
+ 660   memory-should-contain [
+ 661     3 <- 2
+ 662     4 <- 7
+ 663   ]
+ 664   screen-should-contain [
+ 665     .          .
+ 666     .  ab  cd  .
+ 667     .    efg   .
+ 668     .╌╌╌╌╌╌╌╌╌╌.
+ 669     .          .
+ 670   ]
+ 671 ]
+ 672 
+ 673 # undo cursor movement and scroll
+ 674 
+ 675 scenario editor-can-undo-touch [
+ 676   local-scope
+ 677   # create an editor with some text
+ 678   assume-screen 10/width, 5/height
+ 679   contents:text <- new [abc
+ 680 def
+ 681 ghi]
+ 682   e:&:editor <- new-editor contents, 0/left, 10/right
+ 683   editor-render screen, e
+ 684   # move the cursor
+ 685   assume-console [
+ 686     left-click 3, 1
+ 687   ]
+ 688   editor-event-loop screen, console, e
+ 689   # undo
+ 690   assume-console [
+ 691     press ctrl-z
+ 692   ]
+ 693   run [
+ 694     editor-event-loop screen, console, e
+ 695   ]
+ 696   # click undone
+ 697   3:num/raw <- get *e, cursor-row:offset
+ 698   4:num/raw <- get *e, cursor-column:offset
+ 699   memory-should-contain [
+ 700     3 <- 1
+ 701     4 <- 0
+ 702   ]
+ 703   # cursor should be in the right place
+ 704   assume-console [
+ 705     type [1]
+ 706   ]
+ 707   run [
+ 708     editor-event-loop screen, console, e
+ 709   ]
+ 710   screen-should-contain [
+ 711     .          .
+ 712     .1abc      .
+ 713     .def       .
+ 714     .ghi       .
+ 715     .╌╌╌╌╌╌╌╌╌╌.
+ 716   ]
+ 717 ]
+ 718 
+ 719 after <move-cursor-begin> [
+ 720   cursor-row-before:num <- get *editor, cursor-row:offset
+ 721   cursor-column-before:num <- get *editor, cursor-column:offset
+ 722   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 723 ]
+ 724 before <move-cursor-end> [
+ 725   top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+ 726   cursor-row:num <- get *editor, cursor-row:offset
+ 727   cursor-column:num <- get *editor, cursor-column:offset
+ 728   {
+ 729     break-unless undo-coalesce-tag
+ 730     # if previous operation was also a move, and also had the same coalesce
+ 731     # tag, coalesce with it
+ 732     undo:&:list:&:operation <- get *editor, undo:offset
+ 733     break-unless undo
+ 734     op:&:operation <- first undo
+ 735     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
+ 736     break-unless is-move?
+ 737     previous-coalesce-tag:num <- get move, tag:offset
+ 738     coalesce?:bool <- equal undo-coalesce-tag, previous-coalesce-tag
+ 739     break-unless coalesce?
+ 740     move <- put move, after-row:offset, cursor-row
+ 741     move <- put move, after-column:offset, cursor-column
+ 742     move <- put move, after-top-of-screen:offset, top-after
+ 743     *op <- merge 1/move-operation, move
+ 744     break +done-adding-move-operation
+ 745   }
+ 746   op:&:operation <- new operation:type
+ 747   *op <- merge 1/move-operation, cursor-row-before, cursor-column-before, top-before, cursor-row/after, cursor-column/after, top-after, undo-coalesce-tag
+ 748   editor <- add-operation editor, op
+ 749   +done-adding-move-operation
+ 750 ]
+ 751 
+ 752 after <handle-undo> [
+ 753   {
+ 754     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
+ 755     break-unless is-move?
+ 756     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+ 757     cursor-row <- get move, before-row:offset
+ 758     *editor <- put *editor, cursor-row:offset, cursor-row
+ 759     cursor-column <- get move, before-column:offset
+ 760     *editor <- put *editor, cursor-column:offset, cursor-column
+ 761     top:&:duplex-list:char <- get move, before-top-of-screen:offset
+ 762     *editor <- put *editor, top-of-screen:offset, top
+ 763   }
+ 764 ]
+ 765 
+ 766 scenario editor-can-undo-scroll [
+ 767   local-scope
+ 768   # screen has 1 line for menu + 3 lines
+ 769   assume-screen 5/width, 4/height
+ 770   # editor contains a wrapped line
+ 771   contents:text <- new [a
+ 772 b
+ 773 cdefgh]
+ 774   e:&:editor <- new-editor contents, 0/left, 5/right
+ 775   # position cursor at end of screen and try to move right
+ 776   assume-console [
+ 777     left-click 3, 3
+ 778     press right-arrow
+ 779   ]
+ 780   editor-event-loop screen, console, e
+ 781   3:num/raw <- get *e, cursor-row:offset
+ 782   4:num/raw <- get *e, cursor-column:offset
+ 783   # screen scrolls
+ 784   screen-should-contain [
+ 785     .     .
+ 786     .b    .
+ 787     .cdef↩.
+ 788     .gh   .
+ 789   ]
+ 790   memory-should-contain [
+ 791     3 <- 3
+ 792     4 <- 0
+ 793   ]
+ 794   # undo
+ 795   assume-console [
+ 796     press ctrl-z
+ 797   ]
+ 798   run [
+ 799     editor-event-loop screen, console, e
+ 800   ]
+ 801   # cursor moved back
+ 802   3:num/raw <- get *e, cursor-row:offset
+ 803   4:num/raw <- get *e, cursor-column:offset
+ 804   memory-should-contain [
+ 805     3 <- 3
+ 806     4 <- 3
+ 807   ]
+ 808   # scroll undone
+ 809   screen-should-contain [
+ 810     .     .
+ 811     .a    .
+ 812     .b    .
+ 813     .cdef↩.
+ 814   ]
+ 815   # cursor should be in the right place
+ 816   assume-console [
+ 817     type [1]
+ 818   ]
+ 819   run [
+ 820     editor-event-loop screen, console, e
+ 821   ]
+ 822   screen-should-contain [
+ 823     .     .
+ 824     .b    .
+ 825     .cde1↩.
+ 826     .fgh  .
+ 827   ]
+ 828 ]
+ 829 
+ 830 scenario editor-can-undo-left-arrow [
+ 831   local-scope
+ 832   # create an editor with some text
+ 833   assume-screen 10/width, 5/height
+ 834   contents:text <- new [abc
+ 835 def
+ 836 ghi]
+ 837   e:&:editor <- new-editor contents, 0/left, 10/right
+ 838   editor-render screen, e
+ 839   # move the cursor
+ 840   assume-console [
+ 841     left-click 3, 1
+ 842     press left-arrow
+ 843   ]
+ 844   editor-event-loop screen, console, e
+ 845   # undo
+ 846   assume-console [
+ 847     press ctrl-z
+ 848   ]
+ 849   run [
+ 850     editor-event-loop screen, console, e
+ 851   ]
+ 852   # cursor moves back
+ 853   3:num/raw <- get *e, cursor-row:offset
+ 854   4:num/raw <- get *e, cursor-column:offset
+ 855   memory-should-contain [
+ 856     3 <- 3
+ 857     4 <- 1
+ 858   ]
+ 859   # cursor should be in the right place
+ 860   assume-console [
+ 861     type [1]
+ 862   ]
+ 863   run [
+ 864     editor-event-loop screen, console, e
+ 865   ]
+ 866   screen-should-contain [
+ 867     .          .
+ 868     .abc       .
+ 869     .def       .
+ 870     .g1hi      .
+ 871     .╌╌╌╌╌╌╌╌╌╌.
+ 872   ]
+ 873 ]
+ 874 
+ 875 scenario editor-can-undo-up-arrow [
+ 876   local-scope
+ 877   # create an editor with some text
+ 878   assume-screen 10/width, 5/height
+ 879   contents:text <- new [abc
+ 880 def
+ 881 ghi]
+ 882   e:&:editor <- new-editor contents, 0/left, 10/right
+ 883   editor-render screen, e
+ 884   # move the cursor
+ 885   assume-console [
+ 886     left-click 3, 1
+ 887     press up-arrow
+ 888   ]
+ 889   editor-event-loop screen, console, e
+ 890   3:num/raw <- get *e, cursor-row:offset
+ 891   4:num/raw <- get *e, cursor-column:offset
+ 892   memory-should-contain [
+ 893     3 <- 2
+ 894     4 <- 1
+ 895   ]
+ 896   # undo
+ 897   assume-console [
+ 898     press ctrl-z
+ 899   ]
+ 900   run [
+ 901     editor-event-loop screen, console, e
+ 902   ]
+ 903   # cursor moves back
+ 904   3:num/raw <- get *e, cursor-row:offset
+ 905   4:num/raw <- get *e, cursor-column:offset
+ 906   memory-should-contain [
+ 907     3 <- 3
+ 908     4 <- 1
+ 909   ]
+ 910   # cursor should be in the right place
+ 911   assume-console [
+ 912     type [1]
+ 913   ]
+ 914   run [
+ 915     editor-event-loop screen, console, e
+ 916   ]
+ 917   screen-should-contain [
+ 918     .          .
+ 919     .abc       .
+ 920     .def       .
+ 921     .g1hi      .
+ 922     .╌╌╌╌╌╌╌╌╌╌.
+ 923   ]
+ 924 ]
+ 925 
+ 926 scenario editor-can-undo-down-arrow [
+ 927   local-scope
+ 928   # create an editor with some text
+ 929   assume-screen 10/width, 5/height
+ 930   contents:text <- new [abc
+ 931 def
+ 932 ghi]
+ 933   e:&:editor <- new-editor contents, 0/left, 10/right
+ 934   editor-render screen, e
+ 935   # move the cursor
+ 936   assume-console [
+ 937     left-click 2, 1
+ 938     press down-arrow
+ 939   ]
+ 940   editor-event-loop screen, console, e
+ 941   # undo
+ 942   assume-console [
+ 943     press ctrl-z
+ 944   ]
+ 945   run [
+ 946     editor-event-loop screen, console, e
+ 947   ]
+ 948   # cursor moves back
+ 949   3:num/raw <- get *e, cursor-row:offset
+ 950   4:num/raw <- get *e, cursor-column:offset
+ 951   memory-should-contain [
+ 952     3 <- 2
+ 953     4 <- 1
+ 954   ]
+ 955   # cursor should be in the right place
+ 956   assume-console [
+ 957     type [1]
+ 958   ]
+ 959   run [
+ 960     editor-event-loop screen, console, e
+ 961   ]
+ 962   screen-should-contain [
+ 963     .          .
+ 964     .abc       .
+ 965     .d1ef      .
+ 966     .ghi       .
+ 967     .╌╌╌╌╌╌╌╌╌╌.
+ 968   ]
+ 969 ]
+ 970 
+ 971 scenario editor-can-undo-ctrl-f [
+ 972   local-scope
+ 973   # create an editor with multiple pages of text
+ 974   assume-screen 10/width, 5/height
+ 975   contents:text <- new [a
+ 976 b
+ 977 c
+ 978 d
+ 979 e
+ 980 f]
+ 981   e:&:editor <- new-editor contents, 0/left, 10/right
+ 982   editor-render screen, e
+ 983   # scroll the page
+ 984   assume-console [
+ 985     press ctrl-f
+ 986   ]
+ 987   editor-event-loop screen, console, e
+ 988   # undo
+ 989   assume-console [
+ 990     press ctrl-z
+ 991   ]
+ 992   run [
+ 993     editor-event-loop screen, console, e
+ 994   ]
+ 995   # screen should again show page 1
+ 996   screen-should-contain [
+ 997     .          .
+ 998     .a         .
+ 999     .b         .
+1000     .c         .
+1001     .d         .
+1002   ]
+1003 ]
+1004 
+1005 scenario editor-can-undo-page-down [
+1006   local-scope
+1007   # create an editor with multiple pages of text
+1008   assume-screen 10/width, 5/height
+1009   contents:text <- new [a
+1010 b
+1011 c
+1012 d
+1013 e
+1014 f]
+1015   e:&:editor <- new-editor contents, 0/left, 10/right
+1016   editor-render screen, e
+1017   # scroll the page
+1018   assume-console [
+1019     press page-down
+1020   ]
+1021   editor-event-loop screen, console, e
+1022   # undo
+1023   assume-console [
+1024     press ctrl-z
+1025   ]
+1026   run [
+1027     editor-event-loop screen, console, e
+1028   ]
+1029   # screen should again show page 1
+1030   screen-should-contain [
+1031     .          .
+1032     .a         .
+1033     .b         .
+1034     .c         .
+1035     .d         .
+1036   ]
+1037 ]
+1038 
+1039 scenario editor-can-undo-ctrl-b [
+1040   local-scope
+1041   # create an editor with multiple pages of text
+1042   assume-screen 10/width, 5/height
+1043   contents:text <- new [a
+1044 b
+1045 c
+1046 d
+1047 e
+1048 f]
+1049   e:&:editor <- new-editor contents, 0/left, 10/right
+1050   editor-render screen, e
+1051   # scroll the page down and up
+1052   assume-console [
+1053     press page-down
+1054     press ctrl-b
+1055   ]
+1056   editor-event-loop screen, console, e
+1057   # undo
+1058   assume-console [
+1059     press ctrl-z
+1060   ]
+1061   run [
+1062     editor-event-loop screen, console, e
+1063   ]
+1064   # screen should again show page 2
+1065   screen-should-contain [
+1066     .          .
+1067     .d         .
+1068     .e         .
+1069     .f         .
+1070     .╌╌╌╌╌╌╌╌╌╌.
+1071   ]
+1072 ]
+1073 
+1074 scenario editor-can-undo-page-up [
+1075   local-scope
+1076   # create an editor with multiple pages of text
+1077   assume-screen 10/width, 5/height
+1078   contents:text <- new [a
+1079 b
+1080 c
+1081 d
+1082 e
+1083 f]
+1084   e:&:editor <- new-editor contents, 0/left, 10/right
+1085   editor-render screen, e
+1086   # scroll the page down and up
+1087   assume-console [
+1088     press page-down
+1089     press page-up
+1090   ]
+1091   editor-event-loop screen, console, e
+1092   # undo
+1093   assume-console [
+1094     press ctrl-z
+1095   ]
+1096   run [
+1097     editor-event-loop screen, console, e
+1098   ]
+1099   # screen should again show page 2
+1100   screen-should-contain [
+1101     .          .
+1102     .d         .
+1103     .e         .
+1104     .f         .
+1105     .╌╌╌╌╌╌╌╌╌╌.
+1106   ]
+1107 ]
+1108 
+1109 scenario editor-can-undo-ctrl-a [
+1110   local-scope
+1111   # create an editor with some text
+1112   assume-screen 10/width, 5/height
+1113   contents:text <- new [abc
+1114 def
+1115 ghi]
+1116   e:&:editor <- new-editor contents, 0/left, 10/right
+1117   editor-render screen, e
+1118   # move the cursor, then to start of line
+1119   assume-console [
+1120     left-click 2, 1
+1121     press ctrl-a
+1122   ]
+1123   editor-event-loop screen, console, e
+1124   # undo
+1125   assume-console [
+1126     press ctrl-z
+1127   ]
+1128   run [
+1129     editor-event-loop screen, console, e
+1130   ]
+1131   # cursor moves back
+1132   3:num/raw <- get *e, cursor-row:offset
+1133   4:num/raw <- get *e, cursor-column:offset
+1134   memory-should-contain [
+1135     3 <- 2
+1136     4 <- 1
+1137   ]
+1138   # cursor should be in the right place
+1139   assume-console [
+1140     type [1]
+1141   ]
+1142   run [
+1143     editor-event-loop screen, console, e
+1144   ]
+1145   screen-should-contain [
+1146     .          .
+1147     .abc       .
+1148     .d1ef      .
+1149     .ghi       .
+1150     .╌╌╌╌╌╌╌╌╌╌.
+1151   ]
+1152 ]
+1153 
+1154 scenario editor-can-undo-home [
+1155   local-scope
+1156   # create an editor with some text
+1157   assume-screen 10/width, 5/height
+1158   contents:text <- new [abc
+1159 def
+1160 ghi]
+1161   e:&:editor <- new-editor contents, 0/left, 10/right
+1162   editor-render screen, e
+1163   # move the cursor, then to start of line
+1164   assume-console [
+1165     left-click 2, 1
+1166     press home
+1167   ]
+1168   editor-event-loop screen, console, e
+1169   # undo
+1170   assume-console [
+1171     press ctrl-z
+1172   ]
+1173   run [
+1174     editor-event-loop screen, console, e
+1175   ]
+1176   # cursor moves back
+1177   3:num/raw <- get *e, cursor-row:offset
+1178   4:num/raw <- get *e, cursor-column:offset
+1179   memory-should-contain [
+1180     3 <- 2
+1181     4 <- 1
+1182   ]
+1183   # cursor should be in the right place
+1184   assume-console [
+1185     type [1]
+1186   ]
+1187   run [
+1188     editor-event-loop screen, console, e
+1189   ]
+1190   screen-should-contain [
+1191     .          .
+1192     .abc       .
+1193     .d1ef      .
+1194     .ghi       .
+1195     .╌╌╌╌╌╌╌╌╌╌.
+1196   ]
+1197 ]
+1198 
+1199 scenario editor-can-undo-ctrl-e [
+1200   local-scope
+1201   # create an editor with some text
+1202   assume-screen 10/width, 5/height
+1203   contents:text <- new [abc
+1204 def
+1205 ghi]
+1206   e:&:editor <- new-editor contents, 0/left, 10/right
+1207   editor-render screen, e
+1208   # move the cursor, then to start of line
+1209   assume-console [
+1210     left-click 2, 1
+1211     press ctrl-e
+1212   ]
+1213   editor-event-loop screen, console, e
+1214   # undo
+1215   assume-console [
+1216     press ctrl-z
+1217   ]
+1218   run [
+1219     editor-event-loop screen, console, e
+1220   ]
+1221   # cursor moves back
+1222   3:num/raw <- get *e, cursor-row:offset
+1223   4:num/raw <- get *e, cursor-column:offset
+1224   memory-should-contain [
+1225     3 <- 2
+1226     4 <- 1
+1227   ]
+1228   # cursor should be in the right place
+1229   assume-console [
+1230     type [1]
+1231   ]
+1232   run [
+1233     editor-event-loop screen, console, e
+1234   ]
+1235   screen-should-contain [
+1236     .          .
+1237     .abc       .
+1238     .d1ef      .
+1239     .ghi       .
+1240     .╌╌╌╌╌╌╌╌╌╌.
+1241   ]
+1242 ]
+1243 
+1244 scenario editor-can-undo-end [
+1245   local-scope
+1246   # create an editor with some text
+1247   assume-screen 10/width, 5/height
+1248   contents:text <- new [abc
+1249 def
+1250 ghi]
+1251   e:&:editor <- new-editor contents, 0/left, 10/right
+1252   editor-render screen, e
+1253   # move the cursor, then to start of line
+1254   assume-console [
+1255     left-click 2, 1
+1256     press end
+1257   ]
+1258   editor-event-loop screen, console, e
+1259   # undo
+1260   assume-console [
+1261     press ctrl-z
+1262   ]
+1263   run [
+1264     editor-event-loop screen, console, e
+1265   ]
+1266   # cursor moves back
+1267   3:num/raw <- get *e, cursor-row:offset
+1268   4:num/raw <- get *e, cursor-column:offset
+1269   memory-should-contain [
+1270     3 <- 2
+1271     4 <- 1
+1272   ]
+1273   # cursor should be in the right place
+1274   assume-console [
+1275     type [1]
+1276   ]
+1277   run [
+1278     editor-event-loop screen, console, e
+1279   ]
+1280   screen-should-contain [
+1281     .          .
+1282     .abc       .
+1283     .d1ef      .
+1284     .ghi       .
+1285     .╌╌╌╌╌╌╌╌╌╌.
+1286   ]
+1287 ]
+1288 
+1289 scenario editor-can-undo-multiple-arrows-in-the-same-direction [
+1290   local-scope
+1291   # create an editor with some text
+1292   assume-screen 10/width, 5/height
+1293   contents:text <- new [abc
+1294 def
+1295 ghi]
+1296   e:&:editor <- new-editor contents, 0/left, 10/right
+1297   editor-render screen, e
+1298   # move the cursor
+1299   assume-console [
+1300     left-click 2, 1
+1301     press right-arrow
+1302     press right-arrow
+1303     press up-arrow
+1304   ]
+1305   editor-event-loop screen, console, e
+1306   3:num/raw <- get *e, cursor-row:offset
+1307   4:num/raw <- get *e, cursor-column:offset
+1308   memory-should-contain [
+1309     3 <- 1
+1310     4 <- 3
+1311   ]
+1312   # undo
+1313   assume-console [
+1314     press ctrl-z
+1315   ]
+1316   run [
+1317     editor-event-loop screen, console, e
+1318   ]
+1319   # up-arrow is undone
+1320   3:num/raw <- get *e, cursor-row:offset
+1321   4:num/raw <- get *e, cursor-column:offset
+1322   memory-should-contain [
+1323     3 <- 2
+1324     4 <- 3
+1325   ]
+1326   # undo again
+1327   assume-console [
+1328     press ctrl-z
+1329   ]
+1330   run [
+1331     editor-event-loop screen, console, e
+1332   ]
+1333   # both right-arrows are undone
+1334   3:num/raw <- get *e, cursor-row:offset
+1335   4:num/raw <- get *e, cursor-column:offset
+1336   memory-should-contain [
+1337     3 <- 2
+1338     4 <- 1
+1339   ]
+1340 ]
+1341 
+1342 # redo cursor movement and scroll
+1343 
+1344 scenario editor-redo-touch [
+1345   local-scope
+1346   # create an editor with some text, click on a character, undo
+1347   assume-screen 10/width, 5/height
+1348   contents:text <- new [abc
+1349 def
+1350 ghi]
+1351   e:&:editor <- new-editor contents, 0/left, 10/right
+1352   editor-render screen, e
+1353   assume-console [
+1354     left-click 3, 1
+1355     press ctrl-z
+1356   ]
+1357   editor-event-loop screen, console, e
+1358   # redo
+1359   assume-console [
+1360     press ctrl-y
+1361   ]
+1362   run [
+1363     editor-event-loop screen, console, e
+1364   ]
+1365   # cursor moves to left-click
+1366   3:num/raw <- get *e, cursor-row:offset
+1367   4:num/raw <- get *e, cursor-column:offset
+1368   memory-should-contain [
+1369     3 <- 3
+1370     4 <- 1
+1371   ]
+1372   # cursor should be in the right place
+1373   assume-console [
+1374     type [1]
+1375   ]
+1376   run [
+1377     editor-event-loop screen, console, e
+1378   ]
+1379   screen-should-contain [
+1380     .          .
+1381     .abc       .
+1382     .def       .
+1383     .g1hi      .
+1384     .╌╌╌╌╌╌╌╌╌╌.
+1385   ]
+1386 ]
+1387 
+1388 after <handle-redo> [
+1389   {
+1390     move:move-operation, is-move?:bool <- maybe-convert *op, move:variant
+1391     break-unless is-move?
+1392     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+1393     cursor-row <- get move, after-row:offset
+1394     *editor <- put *editor, cursor-row:offset, cursor-row
+1395     cursor-column <- get move, after-column:offset
+1396     *editor <- put *editor, cursor-column:offset, cursor-column
+1397     top:&:duplex-list:char <- get move, after-top-of-screen:offset
+1398     *editor <- put *editor, top-of-screen:offset, top
+1399   }
+1400 ]
+1401 
+1402 scenario editor-separates-undo-insert-from-undo-cursor-move [
+1403   local-scope
+1404   # create an editor, type some text, move the cursor, type some more text
+1405   assume-screen 10/width, 5/height
+1406   e:&:editor <- new-editor [], 0/left, 10/right
+1407   editor-render screen, e
+1408   assume-console [
+1409     type [abc]
+1410     left-click 1, 1
+1411     type [d]
+1412   ]
+1413   editor-event-loop screen, console, e
+1414   3:num/raw <- get *e, cursor-row:offset
+1415   4:num/raw <- get *e, cursor-column:offset
+1416   screen-should-contain [
+1417     .          .
+1418     .adbc      .
+1419     .╌╌╌╌╌╌╌╌╌╌.
+1420     .          .
+1421   ]
+1422   memory-should-contain [
+1423     3 <- 1
+1424     4 <- 2
+1425   ]
+1426   # undo
+1427   assume-console [
+1428     press ctrl-z
+1429   ]
+1430   run [
+1431     editor-event-loop screen, console, e
+1432     3:num/raw <- get *e, cursor-row:offset
+1433     4:num/raw <- get *e, cursor-column:offset
+1434   ]
+1435   # last letter typed is deleted
+1436   screen-should-contain [
+1437     .          .
+1438     .abc       .
+1439     .╌╌╌╌╌╌╌╌╌╌.
+1440     .          .
+1441   ]
+1442   memory-should-contain [
+1443     3 <- 1
+1444     4 <- 1
+1445   ]
+1446   # undo again
+1447   assume-console [
+1448     press ctrl-z
+1449   ]
+1450   run [
+1451     editor-event-loop screen, console, e
+1452     3:num/raw <- get *e, cursor-row:offset
+1453     4:num/raw <- get *e, cursor-column:offset
+1454   ]
+1455   # no change to screen; cursor moves
+1456   screen-should-contain [
+1457     .          .
+1458     .abc       .
+1459     .╌╌╌╌╌╌╌╌╌╌.
+1460     .          .
+1461   ]
+1462   memory-should-contain [
+1463     3 <- 1
+1464     4 <- 3
+1465   ]
+1466   # undo again
+1467   assume-console [
+1468     press ctrl-z
+1469   ]
+1470   run [
+1471     editor-event-loop screen, console, e
+1472     3:num/raw <- get *e, cursor-row:offset
+1473     4:num/raw <- get *e, cursor-column:offset
+1474   ]
+1475   # screen empty
+1476   screen-should-contain [
+1477     .          .
+1478     .          .
+1479     .╌╌╌╌╌╌╌╌╌╌.
+1480     .          .
+1481   ]
+1482   memory-should-contain [
+1483     3 <- 1
+1484     4 <- 0
+1485   ]
+1486   # redo
+1487   assume-console [
+1488     press ctrl-y
+1489   ]
+1490   run [
+1491     editor-event-loop screen, console, e
+1492     3:num/raw <- get *e, cursor-row:offset
+1493     4:num/raw <- get *e, cursor-column:offset
+1494   ]
+1495   # first insert
+1496   screen-should-contain [
+1497     .          .
+1498     .abc       .
+1499     .╌╌╌╌╌╌╌╌╌╌.
+1500     .          .
+1501   ]
+1502   memory-should-contain [
+1503     3 <- 1
+1504     4 <- 3
+1505   ]
+1506   # redo again
+1507   assume-console [
+1508     press ctrl-y
+1509   ]
+1510   run [
+1511     editor-event-loop screen, console, e
+1512     3:num/raw <- get *e, cursor-row:offset
+1513     4:num/raw <- get *e, cursor-column:offset
+1514   ]
+1515   # cursor moves
+1516   screen-should-contain [
+1517     .          .
+1518     .abc       .
+1519     .╌╌╌╌╌╌╌╌╌╌.
+1520     .          .
+1521   ]
+1522   # cursor moves
+1523   memory-should-contain [
+1524     3 <- 1
+1525     4 <- 1
+1526   ]
+1527   # redo again
+1528   assume-console [
+1529     press ctrl-y
+1530   ]
+1531   run [
+1532     editor-event-loop screen, console, e
+1533     3:num/raw <- get *e, cursor-row:offset
+1534     4:num/raw <- get *e, cursor-column:offset
+1535   ]
+1536   # second insert
+1537   screen-should-contain [
+1538     .          .
+1539     .adbc      .
+1540     .╌╌╌╌╌╌╌╌╌╌.
+1541     .          .
+1542   ]
+1543   memory-should-contain [
+1544     3 <- 1
+1545     4 <- 2
+1546   ]
+1547 ]
+1548 
+1549 # undo backspace
+1550 
+1551 scenario editor-can-undo-and-redo-backspace [
+1552   local-scope
+1553   # create an editor
+1554   assume-screen 10/width, 5/height
+1555   e:&:editor <- new-editor [], 0/left, 10/right
+1556   editor-render screen, e
+1557   # insert some text and hit backspace
+1558   assume-console [
+1559     type [abc]
+1560     press backspace
+1561     press backspace
+1562   ]
+1563   editor-event-loop screen, console, e
+1564   screen-should-contain [
+1565     .          .
+1566     .a         .
+1567     .╌╌╌╌╌╌╌╌╌╌.
+1568     .          .
+1569   ]
+1570   3:num/raw <- get *e, cursor-row:offset
+1571   4:num/raw <- get *e, cursor-column:offset
+1572   memory-should-contain [
+1573     3 <- 1
+1574     4 <- 1
+1575   ]
+1576   # undo
+1577   assume-console [
+1578     press ctrl-z
+1579   ]
+1580   run [
+1581     editor-event-loop screen, console, e
+1582   ]
+1583   3:num/raw <- get *e, cursor-row:offset
+1584   4:num/raw <- get *e, cursor-column:offset
+1585   memory-should-contain [
+1586     3 <- 1
+1587     4 <- 3
+1588   ]
+1589   screen-should-contain [
+1590     .          .
+1591     .abc       .
+1592     .╌╌╌╌╌╌╌╌╌╌.
+1593     .          .
+1594   ]
+1595   # redo
+1596   assume-console [
+1597     press ctrl-y
+1598   ]
+1599   run [
+1600     editor-event-loop screen, console, e
+1601   ]
+1602   3:num/raw <- get *e, cursor-row:offset
+1603   4:num/raw <- get *e, cursor-column:offset
+1604   memory-should-contain [
+1605     3 <- 1
+1606     4 <- 1
+1607   ]
+1608   screen-should-contain [
+1609     .          .
+1610     .a         .
+1611     .╌╌╌╌╌╌╌╌╌╌.
+1612     .          .
+1613   ]
+1614 ]
+1615 
+1616 # save operation to undo
+1617 after <backspace-character-begin> [
+1618   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+1619 ]
+1620 before <backspace-character-end> [
+1621   {
+1622     break-unless backspaced-cell  # backspace failed; don't add an undo operation
+1623     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+1624     cursor-row:num <- get *editor, cursor-row:offset
+1625     cursor-column:num <- get *editor, cursor-row:offset
+1626     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
+1627     undo:&:list:&:operation <- get *editor, undo:offset
+1628     {
+1629       # if previous operation was an insert, coalesce this operation with it
+1630       break-unless undo
+1631       op:&:operation <- first undo
+1632       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
+1633       break-unless is-delete?
+1634       previous-coalesce-tag:num <- get deletion, tag:offset
+1635       coalesce?:bool <- equal previous-coalesce-tag, 1/coalesce-backspace
+1636       break-unless coalesce?
+1637       deletion <- put deletion, delete-from:offset, before-cursor
+1638       backspaced-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
+1639       insert-range backspaced-cell, backspaced-so-far
+1640       deletion <- put deletion, deleted-text:offset, backspaced-cell
+1641       deletion <- put deletion, after-row:offset, cursor-row
+1642       deletion <- put deletion, after-column:offset, cursor-column
+1643       deletion <- put deletion, after-top-of-screen:offset, top-after
+1644       *op <- merge 2/delete-operation, deletion
+1645       break +done-adding-backspace-operation
+1646     }
+1647     # if not, create a new operation
+1648     op:&:operation <- new operation:type
+1649     deleted-until:&:duplex-list:char <- next before-cursor
+1650     *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
+1651     editor <- add-operation editor, op
+1652     +done-adding-backspace-operation
+1653   }
+1654 ]
+1655 
+1656 after <handle-undo> [
+1657   {
+1658     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
+1659     break-unless is-delete?
+1660     anchor:&:duplex-list:char <- get deletion, delete-from:offset
+1661     break-unless anchor
+1662     deleted:&:duplex-list:char <- get deletion, deleted-text:offset
+1663     old-cursor:&:duplex-list:char <- last deleted
+1664     insert-range anchor, deleted
+1665     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+1666     before-cursor <- copy old-cursor
+1667     cursor-row <- get deletion, before-row:offset
+1668     *editor <- put *editor, cursor-row:offset, cursor-row
+1669     cursor-column <- get deletion, before-column:offset
+1670     *editor <- put *editor, cursor-column:offset, cursor-column
+1671     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
+1672     *editor <- put *editor, top-of-screen:offset, top
+1673   }
+1674 ]
+1675 
+1676 after <handle-redo> [
+1677   {
+1678     deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
+1679     break-unless is-delete?
+1680     start:&:duplex-list:char <- get deletion, delete-from:offset
+1681     end:&:duplex-list:char <- get deletion, delete-until:offset
+1682     data:&:duplex-list:char <- get *editor, data:offset
+1683     remove-between start, end
+1684     # assert cursor-row/cursor-column/top-of-screen match after-row/after-column/after-top-of-screen
+1685     cursor-row <- get deletion, after-row:offset
+1686     *editor <- put *editor, cursor-row:offset, cursor-row
+1687     cursor-column <- get deletion, after-column:offset
+1688     *editor <- put *editor, cursor-column:offset, cursor-column
+1689     top:&:duplex-list:char <- get deletion, before-top-of-screen:offset
+1690     *editor <- put *editor, top-of-screen:offset, top
+1691   }
+1692 ]
+1693 
+1694 # undo delete
+1695 
+1696 scenario editor-can-undo-and-redo-delete [
+1697   local-scope
+1698   # create an editor
+1699   assume-screen 10/width, 5/height
+1700   e:&:editor <- new-editor [], 0/left, 10/right
+1701   editor-render screen, e
+1702   # insert some text and hit delete and backspace a few times
+1703   assume-console [
+1704     type [abcdef]
+1705     left-click 1, 2
+1706     press delete
+1707     press backspace
+1708     press delete
+1709     press delete
+1710   ]
+1711   editor-event-loop screen, console, e
+1712   screen-should-contain [
+1713     .          .
+1714     .af        .
+1715     .╌╌╌╌╌╌╌╌╌╌.
+1716     .          .
+1717   ]
+1718   3:num/raw <- get *e, cursor-row:offset
+1719   4:num/raw <- get *e, cursor-column:offset
+1720   memory-should-contain [
+1721     3 <- 1
+1722     4 <- 1
+1723   ]
+1724   # undo deletes
+1725   assume-console [
+1726     press ctrl-z
+1727   ]
+1728   run [
+1729     editor-event-loop screen, console, e
+1730   ]
+1731   3:num/raw <- get *e, cursor-row:offset
+1732   4:num/raw <- get *e, cursor-column:offset
+1733   memory-should-contain [
+1734     3 <- 1
+1735     4 <- 1
+1736   ]
+1737   screen-should-contain [
+1738     .          .
+1739     .adef      .
+1740     .╌╌╌╌╌╌╌╌╌╌.
+1741     .          .
+1742   ]
+1743   # undo backspace
+1744   assume-console [
+1745     press ctrl-z
+1746   ]
+1747   run [
+1748     editor-event-loop screen, console, e
+1749   ]
+1750   3:num/raw <- get *e, cursor-row:offset
+1751   4:num/raw <- get *e, cursor-column:offset
+1752   memory-should-contain [
+1753     3 <- 1
+1754     4 <- 2
+1755   ]
+1756   screen-should-contain [
+1757     .          .
+1758     .abdef     .
+1759     .╌╌╌╌╌╌╌╌╌╌.
+1760     .          .
+1761   ]
+1762   # undo first delete
+1763   assume-console [
+1764     press ctrl-z
+1765   ]
+1766   run [
+1767     editor-event-loop screen, console, e
+1768   ]
+1769   3:num/raw <- get *e, cursor-row:offset
+1770   4:num/raw <- get *e, cursor-column:offset
+1771   memory-should-contain [
+1772     3 <- 1
+1773     4 <- 2
+1774   ]
+1775   screen-should-contain [
+1776     .          .
+1777     .abcdef    .
+1778     .╌╌╌╌╌╌╌╌╌╌.
+1779     .          .
+1780   ]
+1781   # redo first delete
+1782   assume-console [
+1783     press ctrl-y
+1784   ]
+1785   run [
+1786     editor-event-loop screen, console, e
+1787   ]
+1788   # first line inserted
+1789   3:num/raw <- get *e, cursor-row:offset
+1790   4:num/raw <- get *e, cursor-column:offset
+1791   memory-should-contain [
+1792     3 <- 1
+1793     4 <- 2
+1794   ]
+1795   screen-should-contain [
+1796     .          .
+1797     .abdef     .
+1798     .╌╌╌╌╌╌╌╌╌╌.
+1799     .          .
+1800   ]
+1801   # redo backspace
+1802   assume-console [
+1803     press ctrl-y
+1804   ]
+1805   run [
+1806     editor-event-loop screen, console, e
+1807   ]
+1808   # first line inserted
+1809   3:num/raw <- get *e, cursor-row:offset
+1810   4:num/raw <- get *e, cursor-column:offset
+1811   memory-should-contain [
+1812     3 <- 1
+1813     4 <- 1
+1814   ]
+1815   screen-should-contain [
+1816     .          .
+1817     .adef      .
+1818     .╌╌╌╌╌╌╌╌╌╌.
+1819     .          .
+1820   ]
+1821   # redo deletes
+1822   assume-console [
+1823     press ctrl-y
+1824   ]
+1825   run [
+1826     editor-event-loop screen, console, e
+1827   ]
+1828   # first line inserted
+1829   3:num/raw <- get *e, cursor-row:offset
+1830   4:num/raw <- get *e, cursor-column:offset
+1831   memory-should-contain [
+1832     3 <- 1
+1833     4 <- 1
+1834   ]
+1835   screen-should-contain [
+1836     .          .
+1837     .af        .
+1838     .╌╌╌╌╌╌╌╌╌╌.
+1839     .          .
+1840   ]
+1841 ]
+1842 
+1843 after <delete-character-begin> [
+1844   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+1845 ]
+1846 before <delete-character-end> [
+1847   {
+1848     break-unless deleted-cell  # delete failed; don't add an undo operation
+1849     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+1850     cursor-row:num <- get *editor, cursor-row:offset
+1851     cursor-column:num <- get *editor, cursor-column:offset
+1852     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
+1853     undo:&:list:&:operation <- get *editor, undo:offset
+1854     {
+1855       # if previous operation was an insert, coalesce this operation with it
+1856       break-unless undo
+1857       op:&:operation <- first undo
+1858       deletion:delete-operation, is-delete?:bool <- maybe-convert *op, delete:variant
+1859       break-unless is-delete?
+1860       previous-coalesce-tag:num <- get deletion, tag:offset
+1861       coalesce?:bool <- equal previous-coalesce-tag, 2/coalesce-delete
+1862       break-unless coalesce?
+1863       delete-until:&:duplex-list:char <- next before-cursor
+1864       deletion <- put deletion, delete-until:offset, delete-until
+1865       deleted-so-far:&:duplex-list:char <- get deletion, deleted-text:offset
+1866       deleted-so-far <- append deleted-so-far, deleted-cell
+1867       deletion <- put deletion, deleted-text:offset, deleted-so-far
+1868       deletion <- put deletion, after-row:offset, cursor-row
+1869       deletion <- put deletion, after-column:offset, cursor-column
+1870       deletion <- put deletion, after-top-of-screen:offset, top-after
+1871       *op <- merge 2/delete-operation, deletion
+1872       break +done-adding-delete-operation
+1873     }
+1874     # if not, create a new operation
+1875     op:&:operation <- new operation:type
+1876     deleted-until:&:duplex-list:char <- next before-cursor
+1877     *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
+1878     editor <- add-operation editor, op
+1879     +done-adding-delete-operation
+1880   }
+1881 ]
+1882 
+1883 # undo ctrl-k
+1884 
+1885 scenario editor-can-undo-and-redo-ctrl-k [
+1886   local-scope
+1887   # create an editor
+1888   assume-screen 10/width, 5/height
+1889   contents:text <- new [abc
+1890 def]
+1891   e:&:editor <- new-editor contents, 0/left, 10/right
+1892   editor-render screen, e
+1893   # insert some text and hit delete and backspace a few times
+1894   assume-console [
+1895     left-click 1, 1
+1896     press ctrl-k
+1897   ]
+1898   editor-event-loop screen, console, e
+1899   screen-should-contain [
+1900     .          .
+1901     .a         .
+1902     .def       .
+1903     .╌╌╌╌╌╌╌╌╌╌.
+1904     .          .
+1905   ]
+1906   3:num/raw <- get *e, cursor-row:offset
+1907   4:num/raw <- get *e, cursor-column:offset
+1908   memory-should-contain [
+1909     3 <- 1
+1910     4 <- 1
+1911   ]
+1912   # undo
+1913   assume-console [
+1914     press ctrl-z
+1915   ]
+1916   run [
+1917     editor-event-loop screen, console, e
+1918   ]
+1919   screen-should-contain [
+1920     .          .
+1921     .abc       .
+1922     .def       .
+1923     .╌╌╌╌╌╌╌╌╌╌.
+1924     .          .
+1925   ]
+1926   3:num/raw <- get *e, cursor-row:offset
+1927   4:num/raw <- get *e, cursor-column:offset
+1928   memory-should-contain [
+1929     3 <- 1
+1930     4 <- 1
+1931   ]
+1932   # redo
+1933   assume-console [
+1934     press ctrl-y
+1935   ]
+1936   run [
+1937     editor-event-loop screen, console, e
+1938   ]
+1939   # first line inserted
+1940   screen-should-contain [
+1941     .          .
+1942     .a         .
+1943     .def       .
+1944     .╌╌╌╌╌╌╌╌╌╌.
+1945     .          .
+1946   ]
+1947   3:num/raw <- get *e, cursor-row:offset
+1948   4:num/raw <- get *e, cursor-column:offset
+1949   memory-should-contain [
+1950     3 <- 1
+1951     4 <- 1
+1952   ]
+1953   # cursor should be in the right place
+1954   assume-console [
+1955     type [1]
+1956   ]
+1957   run [
+1958     editor-event-loop screen, console, e
+1959   ]
+1960   screen-should-contain [
+1961     .          .
+1962     .a1        .
+1963     .def       .
+1964     .╌╌╌╌╌╌╌╌╌╌.
+1965     .          .
+1966   ]
+1967 ]
+1968 
+1969 after <delete-to-end-of-line-begin> [
+1970   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+1971 ]
+1972 before <delete-to-end-of-line-end> [
+1973   {
+1974     break-unless deleted-cells  # delete failed; don't add an undo operation
+1975     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+1976     cursor-row:num <- get *editor, cursor-row:offset
+1977     cursor-column:num <- get *editor, cursor-column:offset
+1978     deleted-until:&:duplex-list:char <- next before-cursor
+1979     op:&:operation <- new operation:type
+1980     *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
+1981     editor <- add-operation editor, op
+1982     +done-adding-delete-operation
+1983   }
+1984 ]
+1985 
+1986 # undo ctrl-u
+1987 
+1988 scenario editor-can-undo-and-redo-ctrl-u [
+1989   local-scope
+1990   # create an editor
+1991   assume-screen 10/width, 5/height
+1992   contents:text <- new [abc
+1993 def]
+1994   e:&:editor <- new-editor contents, 0/left, 10/right
+1995   editor-render screen, e
+1996   # insert some text and hit delete and backspace a few times
+1997   assume-console [
+1998     left-click 1, 2
+1999     press ctrl-u
+2000   ]
+2001   editor-event-loop screen, console, e
+2002   screen-should-contain [
+2003     .          .
+2004     .c         .
+2005     .def       .
+2006     .╌╌╌╌╌╌╌╌╌╌.
+2007     .          .
+2008   ]
+2009   3:num/raw <- get *e, cursor-row:offset
+2010   4:num/raw <- get *e, cursor-column:offset
+2011   memory-should-contain [
+2012     3 <- 1
+2013     4 <- 0
+2014   ]
+2015   # undo
+2016   assume-console [
+2017     press ctrl-z
+2018   ]
+2019   run [
+2020     editor-event-loop screen, console, e
+2021   ]
+2022   screen-should-contain [
+2023     .          .
+2024     .abc       .
+2025     .def       .
+2026     .╌╌╌╌╌╌╌╌╌╌.
+2027     .          .
+2028   ]
+2029   3:num/raw <- get *e, cursor-row:offset
+2030   4:num/raw <- get *e, cursor-column:offset
+2031   memory-should-contain [
+2032     3 <- 1
+2033     4 <- 2
+2034   ]
+2035   # redo
+2036   assume-console [
+2037     press ctrl-y
+2038   ]
+2039   run [
+2040     editor-event-loop screen, console, e
+2041   ]
+2042   # first line inserted
+2043   screen-should-contain [
+2044     .          .
+2045     .c         .
+2046     .def       .
+2047     .╌╌╌╌╌╌╌╌╌╌.
+2048     .          .
+2049   ]
+2050   3:num/raw <- get *e, cursor-row:offset
+2051   4:num/raw <- get *e, cursor-column:offset
+2052   memory-should-contain [
+2053     3 <- 1
+2054     4 <- 0
+2055   ]
+2056   # cursor should be in the right place
+2057   assume-console [
+2058     type [1]
+2059   ]
+2060   run [
+2061     editor-event-loop screen, console, e
+2062   ]
+2063   screen-should-contain [
+2064     .          .
+2065     .1c        .
+2066     .def       .
+2067     .╌╌╌╌╌╌╌╌╌╌.
+2068     .          .
+2069   ]
+2070 ]
+2071 
+2072 after <delete-to-start-of-line-begin> [
+2073   top-before:&:duplex-list:char <- get *editor, top-of-screen:offset
+2074 ]
+2075 before <delete-to-start-of-line-end> [
+2076   {
+2077     break-unless deleted-cells  # delete failed; don't add an undo operation
+2078     top-after:&:duplex-list:char <- get *editor, top-of-screen:offset
+2079     op:&:operation <- new operation:type
+2080     before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
+2081     deleted-until:&:duplex-list:char <- next before-cursor
+2082     cursor-row:num <- get *editor, cursor-row:offset
+2083     cursor-column:num <- get *editor, cursor-column:offset
+2084     *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
+2085     editor <- add-operation editor, op
+2086     +done-adding-delete-operation
+2087   }
+2088 ]
+2089 
+2090 scenario editor-can-undo-and-redo-ctrl-u-2 [
+2091   local-scope
+2092   # create an editor
+2093   assume-screen 10/width, 5/height
+2094   e:&:editor <- new-editor [], 0/left, 10/right
+2095   editor-render screen, e
+2096   # insert some text and hit delete and backspace a few times
+2097   assume-console [
+2098     type [abc]
+2099     press ctrl-u
+2100     press ctrl-z
+2101   ]
+2102   editor-event-loop screen, console, e
+2103   screen-should-contain [
+2104     .          .
+2105     .abc       .
+2106     .╌╌╌╌╌╌╌╌╌╌.
+2107     .          .
+2108   ]
+2109 ]
 
-- cgit 1.4.1-2-gfad0