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 ]