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