1 ## special shortcuts for manipulating the editor
   2 # Some keys on the keyboard generate unicode characters, others generate
   3 # terminfo key codes. We need to modify different places in the two cases.
   4 
   5 # tab - insert two spaces
   6 
   7 scenario editor-inserts-two-spaces-on-tab [
   8   local-scope
   9   assume-screen 10/width, 5/height
  10   s:text <- new [ab
  11 cd]
  12   e:&:editor <- new-editor s, 0/left, 5/right
  13   editor-render screen, e
  14   $clear-trace
  15   assume-console [
  16     press tab
  17   ]
  18   run [
  19     editor-event-loop screen, console, e
  20   ]
  21   screen-should-contain [
  22     .          .
  23     .  ab      .
  24     .cd        .
  25   ]
  26   # we render at most two editor rows worth (one row for each space)
  27   check-trace-count-for-label-lesser-than 10, [print-character]
  28 ]
  29 
  30 scenario editor-inserts-two-spaces-and-wraps-line-on-tab [
  31   local-scope
  32   assume-screen 10/width, 5/height
  33   e:&:editor <- new-editor [abcd], 0/left, 5/right
  34   editor-render screen, e
  35   $clear-trace
  36   assume-console [
  37     press tab
  38   ]
  39   run [
  40     editor-event-loop screen, console, e
  41   ]
  42   screen-should-contain [
  43     .          .
  44     .  ab↩     .
  45     .cd        .
  46   ]
  47   # we re-render the whole editor
  48   check-trace-count-for-label-greater-than 10, [print-character]
  49 ]
  50 
  51 after <handle-special-character> [
  52   {
  53     tab?:bool <- equal c, 9/tab
  54     break-unless tab?
  55     <begin-insert-character>
  56     # todo: decompose insert-at-cursor into editor update and screen update,
  57     # so that 'tab' doesn't render the current line multiple times
  58     insert-at-cursor editor, 32/space, screen
  59     go-render? <- insert-at-cursor editor, 32/space, screen
  60     <end-insert-character>
  61     return
  62   }
  63 ]
  64 
  65 # backspace - delete character before cursor
  66 
  67 scenario editor-handles-backspace-key [
  68   local-scope
  69   assume-screen 10/width, 5/height
  70   e:&:editor <- new-editor [abc], 0/left, 10/right
  71   editor-render screen, e
  72   $clear-trace
  73   assume-console [
  74     left-click 1, 1
  75     press backspace
  76   ]
  77   run [
  78     editor-event-loop screen, console, e
  79     4:num/raw <- get *e, cursor-row:offset
  80     5:num/raw <- get *e, cursor-column:offset
  81   ]
  82   screen-should-contain [
  83     .          .
  84     .bc        .
  85     .╌╌╌╌╌╌╌╌╌╌.
  86     .          .
  87   ]
  88   memory-should-contain [
  89     4 <- 1
  90     5 <- 0
  91   ]
  92   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
  93 ]
  94 
  95 after <handle-special-character> [
  96   {
  97     delete-previous-character?:bool <- equal c, 8/backspace
  98     break-unless delete-previous-character?
  99     <begin-backspace-character>
 100     go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen
 101     <end-backspace-character>
 102     return
 103   }
 104 ]
 105 
 106 # return values:
 107 #   go-render? - whether caller needs to update the screen
 108 #   backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc.
 109 def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
 110   local-scope
 111   load-inputs
 112   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 113   data:&:duplex-list:char <- get *editor, data:offset
 114   # if at start of text (before-cursor at § sentinel), return
 115   prev:&:duplex-list:char <- prev before-cursor
 116   return-unless prev, 0/no-more-render, 0/nothing-deleted
 117   trace 10, [app], [delete-before-cursor]
 118   original-row:num <- get *editor, cursor-row:offset
 119   scroll?:bool <- move-cursor-coordinates-left editor
 120   backspaced-cell:&:duplex-list:char <- copy before-cursor
 121   data <- remove before-cursor, data  # will also neatly trim next/prev pointers in backspaced-cell/before-cursor
 122   before-cursor <- copy prev
 123   *editor <- put *editor, before-cursor:offset, before-cursor
 124   return-if scroll?, 1/go-render
 125   screen-width:num <- screen-width screen
 126   cursor-row:num <- get *editor, cursor-row:offset
 127   cursor-column:num <- get *editor, cursor-column:offset
 128   # did we just backspace over a newline?
 129   same-row?:bool <- equal cursor-row, original-row
 130   return-unless same-row?, 1/go-render
 131   left:num <- get *editor, left:offset
 132   right:num <- get *editor, right:offset
 133   curr:&:duplex-list:char <- next before-cursor
 134   screen <- move-cursor screen, cursor-row, cursor-column
 135   curr-column:num <- copy cursor-column
 136   {
 137     # hit right margin? give up and let caller render
 138     at-right?:bool <- greater-or-equal curr-column, right
 139     return-if at-right?, 1/go-render
 140     break-unless curr
 141     # newline? done.
 142     currc:char <- get *curr, value:offset
 143     at-newline?:bool <- equal currc, 10/newline
 144     break-if at-newline?
 145     screen <- print screen, currc
 146     curr-column <- add curr-column, 1
 147     curr <- next curr
 148     loop
 149   }
 150   # we're guaranteed not to be at the right margin
 151   space:char <- copy 32/space
 152   screen <- print screen, space
 153   go-render? <- copy 0/false
 154 ]
 155 
 156 def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [
 157   local-scope
 158   load-inputs
 159   go-render?:bool <- copy 0/false
 160   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 161   cursor-row:num <- get *editor, cursor-row:offset
 162   cursor-column:num <- get *editor, cursor-column:offset
 163   left:num <- get *editor, left:offset
 164   # if not at left margin, move one character left
 165   {
 166     at-left-margin?:bool <- equal cursor-column, left
 167     break-if at-left-margin?
 168     trace 10, [app], [decrementing cursor column]
 169     cursor-column <- subtract cursor-column, 1
 170     *editor <- put *editor, cursor-column:offset, cursor-column
 171     return
 172   }
 173   # if at left margin, we must move to previous row:
 174   top-of-screen?:bool <- equal cursor-row, 1  # exclude menu bar
 175   {
 176     break-if top-of-screen?
 177     cursor-row <- subtract cursor-row, 1
 178     *editor <- put *editor, cursor-row:offset, cursor-row
 179   }
 180   {
 181     break-unless top-of-screen?
 182     <scroll-up>
 183     go-render? <- copy 1/true
 184   }
 185   {
 186     # case 1: if previous character was newline, figure out how long the previous line is
 187     previous-character:char <- get *before-cursor, value:offset
 188     previous-character-is-newline?:bool <- equal previous-character, 10/newline
 189     break-unless previous-character-is-newline?
 190     # compute length of previous line
 191     trace 10, [app], [switching to previous line]
 192     d:&:duplex-list:char <- get *editor, data:offset
 193     end-of-line:num <- previous-line-length before-cursor, d
 194     right:num <- get *editor, right:offset
 195     width:num <- subtract right, left
 196     wrap?:bool <- greater-than end-of-line, width
 197     {
 198       break-unless wrap?
 199       _, column-offset:num <- divide-with-remainder end-of-line, width
 200       cursor-column <- add left, column-offset
 201       *editor <- put *editor, cursor-column:offset, cursor-column
 202     }
 203     {
 204       break-if wrap?
 205       cursor-column <- add left, end-of-line
 206       *editor <- put *editor, cursor-column:offset, cursor-column
 207     }
 208     return
 209   }
 210   # case 2: if previous-character was not newline, we're just at a wrapped line
 211   trace 10, [app], [wrapping to previous line]
 212   right:num <- get *editor, right:offset
 213   cursor-column <- subtract right, 1  # leave room for wrap icon
 214   *editor <- put *editor, cursor-column:offset, cursor-column
 215 ]
 216 
 217 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts
 218 # the length of the previous line before the 'curr' pointer.
 219 def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [
 220   local-scope
 221   load-inputs
 222   result:num <- copy 0
 223   return-unless curr
 224   at-start?:bool <- equal curr, start
 225   return-if at-start?
 226   {
 227     curr <- prev curr
 228     break-unless curr
 229     at-start?:bool <- equal curr, start
 230     break-if at-start?
 231     c:char <- get *curr, value:offset
 232     at-newline?:bool <- equal c, 10/newline
 233     break-if at-newline?
 234     result <- add result, 1
 235     loop
 236   }
 237 ]
 238 
 239 scenario editor-clears-last-line-on-backspace [
 240   local-scope
 241   assume-screen 10/width, 5/height
 242   s:text <- new [ab
 243 cd]
 244   e:&:editor <- new-editor s, 0/left, 10/right
 245   assume-console [
 246     left-click 2, 0
 247     press backspace
 248   ]
 249   run [
 250     editor-event-loop screen, console, e
 251     4:num/raw <- get *e, cursor-row:offset
 252     5:num/raw <- get *e, cursor-column:offset
 253   ]
 254   screen-should-contain [
 255     .          .
 256     .abcd      .
 257     .╌╌╌╌╌╌╌╌╌╌.
 258     .          .
 259   ]
 260   memory-should-contain [
 261     4 <- 1
 262     5 <- 2
 263   ]
 264 ]
 265 
 266 scenario editor-joins-and-wraps-lines-on-backspace [
 267   local-scope
 268   assume-screen 10/width, 5/height
 269   # initialize editor with two long-ish but non-wrapping lines
 270   s:text <- new [abc def
 271 ghi jkl]
 272   e:&:editor <- new-editor s, 0/left, 10/right
 273   editor-render screen, e
 274   $clear-trace
 275   # position the cursor at the start of the second and hit backspace
 276   assume-console [
 277     left-click 2, 0
 278     press backspace
 279   ]
 280   run [
 281     editor-event-loop screen, console, e
 282   ]
 283   # resulting single line should wrap correctly
 284   screen-should-contain [
 285     .          .
 286     .abc defgh↩.
 287     .i jkl     .
 288     .╌╌╌╌╌╌╌╌╌╌.
 289     .          .
 290   ]
 291 ]
 292 
 293 scenario editor-wraps-long-lines-on-backspace [
 294   local-scope
 295   assume-screen 10/width, 5/height
 296   # initialize editor in part of the screen with a long line
 297   e:&:editor <- new-editor [abc def ghij], 0/left, 8/right
 298   editor-render screen, e
 299   # confirm that it wraps
 300   screen-should-contain [
 301     .          .
 302     .abc def↩  .
 303     . ghij     .
 304     .╌╌╌╌╌╌╌╌  .
 305   ]
 306   $clear-trace
 307   # position the cursor somewhere in the middle of the top screen line and hit backspace
 308   assume-console [
 309     left-click 1, 4
 310     press backspace
 311   ]
 312   run [
 313     editor-event-loop screen, console, e
 314   ]
 315   # resulting single line should wrap correctly and not overflow its bounds
 316   screen-should-contain [
 317     .          .
 318     .abcdef ↩  .
 319     .ghij      .
 320     .╌╌╌╌╌╌╌╌  .
 321     .          .
 322   ]
 323 ]
 324 
 325 # delete - delete character at cursor
 326 
 327 scenario editor-handles-delete-key [
 328   local-scope
 329   assume-screen 10/width, 5/height
 330   e:&:editor <- new-editor [abc], 0/left, 10/right
 331   editor-render screen, e
 332   $clear-trace
 333   assume-console [
 334     press delete
 335   ]
 336   run [
 337     editor-event-loop screen, console, e
 338   ]
 339   screen-should-contain [
 340     .          .
 341     .bc        .
 342     .╌╌╌╌╌╌╌╌╌╌.
 343     .          .
 344   ]
 345   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
 346   $clear-trace
 347   assume-console [
 348     press delete
 349   ]
 350   run [
 351     editor-event-loop screen, console, e
 352   ]
 353   screen-should-contain [
 354     .          .
 355     .c         .
 356     .╌╌╌╌╌╌╌╌╌╌.
 357     .          .
 358   ]
 359   check-trace-count-for-label 2, [print-character]  # new length to overwrite
 360 ]
 361 
 362 after <handle-special-key> [
 363   {
 364     delete-next-character?:bool <- equal k, 65522/delete
 365     break-unless delete-next-character?
 366     <begin-delete-character>
 367     go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen
 368     <end-delete-character>
 369     return
 370   }
 371 ]
 372 
 373 def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
 374   local-scope
 375   load-inputs
 376   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 377   data:&:duplex-list:char <- get *editor, data:offset
 378   deleted-cell:&:duplex-list:char <- next before-cursor
 379   return-unless deleted-cell, 0/don't-render
 380   currc:char <- get *deleted-cell, value:offset
 381   data <- remove deleted-cell, data
 382   deleted-newline?:bool <- equal currc, 10/newline
 383   return-if deleted-newline?, 1/go-render
 384   # wasn't a newline? render rest of line
 385   curr:&:duplex-list:char <- next before-cursor  # refresh after remove above
 386   cursor-row:num <- get *editor, cursor-row:offset
 387   cursor-column:num <- get *editor, cursor-column:offset
 388   screen <- move-cursor screen, cursor-row, cursor-column
 389   curr-column:num <- copy cursor-column
 390   screen-width:num <- screen-width screen
 391   {
 392     # hit right margin? give up and let caller render
 393     at-right?:bool <- greater-or-equal curr-column, screen-width
 394     return-if at-right?, 1/go-render
 395     break-unless curr
 396     currc:char <- get *curr, value:offset
 397     at-newline?:bool <- equal currc, 10/newline
 398     break-if at-newline?
 399     screen <- print screen, currc
 400     curr-column <- add curr-column, 1
 401     curr <- next curr
 402     loop
 403   }
 404   # we're guaranteed not to be at the right margin
 405   space:char <- copy 32/space
 406   screen <- print screen, space
 407   go-render? <- copy 0/false
 408 ]
 409 
 410 # right arrow
 411 
 412 scenario editor-moves-cursor-right-with-key [
 413   local-scope
 414   assume-screen 10/width, 5/height
 415   e:&:editor <- new-editor [abc], 0/left, 10/right
 416   editor-render screen, e
 417   $clear-trace
 418   assume-console [
 419     press right-arrow
 420     type [0]
 421   ]
 422   run [
 423     editor-event-loop screen, console, e
 424   ]
 425   screen-should-contain [
 426     .          .
 427     .a0bc      .
 428     .╌╌╌╌╌╌╌╌╌╌.
 429     .          .
 430   ]
 431   check-trace-count-for-label 3, [print-character]  # 0 and following characters
 432 ]
 433 
 434 after <handle-special-key> [
 435   {
 436     move-to-next-character?:bool <- equal k, 65514/right-arrow
 437     break-unless move-to-next-character?
 438     # if not at end of text
 439     next-cursor:&:duplex-list:char <- next before-cursor
 440     break-unless next-cursor
 441     # scan to next character
 442     <begin-move-cursor>
 443     before-cursor <- copy next-cursor
 444     *editor <- put *editor, before-cursor:offset, before-cursor
 445     go-render?:bool <- move-cursor-coordinates-right editor, screen-height
 446     screen <- move-cursor screen, cursor-row, cursor-column
 447     undo-coalesce-tag:num <- copy 2/right-arrow
 448     <end-move-cursor>
 449     return
 450   }
 451 ]
 452 
 453 def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
 454   local-scope
 455   load-inputs
 456   before-cursor:&:duplex-list:char <- get *editor before-cursor:offset
 457   cursor-row:num <- get *editor, cursor-row:offset
 458   cursor-column:num <- get *editor, cursor-column:offset
 459   left:num <- get *editor, left:offset
 460   right:num <- get *editor, right:offset
 461   # if crossed a newline, move cursor to start of next row
 462   {
 463     old-cursor-character:char <- get *before-cursor, value:offset
 464     was-at-newline?:bool <- equal old-cursor-character, 10/newline
 465     break-unless was-at-newline?
 466     cursor-row <- add cursor-row, 1
 467     *editor <- put *editor, cursor-row:offset, cursor-row
 468     cursor-column <- copy left
 469     *editor <- put *editor, cursor-column:offset, cursor-column
 470     below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 471     return-unless below-screen?, 0/don't-render
 472     <scroll-down>
 473     cursor-row <- subtract cursor-row, 1  # bring back into screen range
 474     *editor <- put *editor, cursor-row:offset, cursor-row
 475     return 1/go-render
 476   }
 477   # if the line wraps, move cursor to start of next row
 478   {
 479     # if we're at the column just before the wrap indicator
 480     wrap-column:num <- subtract right, 1
 481     at-wrap?:bool <- equal cursor-column, wrap-column
 482     break-unless at-wrap?
 483     # and if next character isn't newline
 484     next:&:duplex-list:char <- next before-cursor
 485     break-unless next
 486     next-character:char <- get *next, value:offset
 487     newline?:bool <- equal next-character, 10/newline
 488     break-if newline?
 489     cursor-row <- add cursor-row, 1
 490     *editor <- put *editor, cursor-row:offset, cursor-row
 491     cursor-column <- copy left
 492     *editor <- put *editor, cursor-column:offset, cursor-column
 493     below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 494     return-unless below-screen?, 0/no-more-render
 495     <scroll-down>
 496     cursor-row <- subtract cursor-row, 1  # bring back into screen range
 497     *editor <- put *editor, cursor-row:offset, cursor-row
 498     return 1/go-render
 499   }
 500   # otherwise move cursor one character right
 501   cursor-column <- add cursor-column, 1
 502   *editor <- put *editor, cursor-column:offset, cursor-column
 503   go-render? <- copy 0/false
 504 ]
 505 
 506 scenario editor-moves-cursor-to-next-line-with-right-arrow [
 507   local-scope
 508   assume-screen 10/width, 5/height
 509   s:text <- new [abc
 510 d]
 511   e:&:editor <- new-editor s, 0/left, 10/right
 512   editor-render screen, e
 513   $clear-trace
 514   # type right-arrow a few times to get to start of second line
 515   assume-console [
 516     press right-arrow
 517     press right-arrow
 518     press right-arrow
 519     press right-arrow  # next line
 520   ]
 521   run [
 522     editor-event-loop screen, console, e
 523   ]
 524   check-trace-count-for-label 0, [print-character]
 525   # type something and ensure it goes where it should
 526   assume-console [
 527     type [0]
 528   ]
 529   run [
 530     editor-event-loop screen, console, e
 531   ]
 532   screen-should-contain [
 533     .          .
 534     .abc       .
 535     .0d        .
 536     .╌╌╌╌╌╌╌╌╌╌.
 537     .          .
 538   ]
 539   check-trace-count-for-label 2, [print-character]  # new length of second line
 540 ]
 541 
 542 scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [
 543   local-scope
 544   assume-screen 10/width, 5/height
 545   s:text <- new [abc
 546 d]
 547   e:&:editor <- new-editor s, 1/left, 10/right
 548   editor-render screen, e
 549   assume-console [
 550     press right-arrow
 551     press right-arrow
 552     press right-arrow
 553     press right-arrow  # next line
 554     type [0]
 555   ]
 556   run [
 557     editor-event-loop screen, console, e
 558   ]
 559   screen-should-contain [
 560     .          .
 561     . abc      .
 562     . 0d       .
 563     . ╌╌╌╌╌╌╌╌╌.
 564     .          .
 565   ]
 566 ]
 567 
 568 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
 569   local-scope
 570   assume-screen 10/width, 5/height
 571   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 572   editor-render screen, e
 573   $clear-trace
 574   assume-console [
 575     left-click 1, 3
 576     press right-arrow
 577   ]
 578   run [
 579     editor-event-loop screen, console, e
 580     3:num/raw <- get *e, cursor-row:offset
 581     4:num/raw <- get *e, cursor-column:offset
 582   ]
 583   screen-should-contain [
 584     .          .
 585     .abcd↩     .
 586     .ef        .
 587     .╌╌╌╌╌     .
 588     .          .
 589   ]
 590   memory-should-contain [
 591     3 <- 2
 592     4 <- 0
 593   ]
 594   check-trace-count-for-label 0, [print-character]
 595 ]
 596 
 597 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
 598   local-scope
 599   assume-screen 10/width, 5/height
 600   # line just barely wrapping
 601   e:&:editor <- new-editor [abcde], 0/left, 5/right
 602   editor-render screen, e
 603   $clear-trace
 604   # position cursor at last character before wrap and hit right-arrow
 605   assume-console [
 606     left-click 1, 3
 607     press right-arrow
 608   ]
 609   run [
 610     editor-event-loop screen, console, e
 611     3:num/raw <- get *e, cursor-row:offset
 612     4:num/raw <- get *e, cursor-column:offset
 613   ]
 614   memory-should-contain [
 615     3 <- 2
 616     4 <- 0
 617   ]
 618   # now hit right arrow again
 619   assume-console [
 620     press right-arrow
 621   ]
 622   run [
 623     editor-event-loop screen, console, e
 624     3:num/raw <- get *e, cursor-row:offset
 625     4:num/raw <- get *e, cursor-column:offset
 626   ]
 627   memory-should-contain [
 628     3 <- 2
 629     4 <- 1
 630   ]
 631   check-trace-count-for-label 0, [print-character]
 632 ]
 633 
 634 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
 635   local-scope
 636   assume-screen 10/width, 5/height
 637   e:&:editor <- new-editor [abcdef], 1/left, 6/right
 638   editor-render screen, e
 639   $clear-trace
 640   assume-console [
 641     left-click 1, 4
 642     press right-arrow
 643   ]
 644   run [
 645     editor-event-loop screen, console, e
 646     3:num/raw <- get *e, cursor-row:offset
 647     4:num/raw <- get *e, cursor-column:offset
 648   ]
 649   screen-should-contain [
 650     .          .
 651     . abcd↩    .
 652     . ef       .
 653     . ╌╌╌╌╌    .
 654     .          .
 655   ]
 656   memory-should-contain [
 657     3 <- 2
 658     4 <- 1
 659   ]
 660   check-trace-count-for-label 0, [print-character]
 661 ]
 662 
 663 scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
 664   local-scope
 665   assume-screen 10/width, 5/height
 666   s:text <- new [abc
 667 d]
 668   e:&:editor <- new-editor s, 0/left, 10/right
 669   editor-render screen, e
 670   $clear-trace
 671   # move to end of line, press right-arrow, type a character
 672   assume-console [
 673     left-click 1, 3
 674     press right-arrow
 675     type [0]
 676   ]
 677   run [
 678     editor-event-loop screen, console, e
 679   ]
 680   # new character should be in next line
 681   screen-should-contain [
 682     .          .
 683     .abc       .
 684     .0d        .
 685     .╌╌╌╌╌╌╌╌╌╌.
 686     .          .
 687   ]
 688   check-trace-count-for-label 2, [print-character]
 689 ]
 690 
 691 # todo: ctrl-right: next word-end
 692 
 693 # left arrow
 694 
 695 scenario editor-moves-cursor-left-with-key [
 696   local-scope
 697   assume-screen 10/width, 5/height
 698   e:&:editor <- new-editor [abc], 0/left, 10/right
 699   editor-render screen, e
 700   $clear-trace
 701   assume-console [
 702     left-click 1, 2
 703     press left-arrow
 704     type [0]
 705   ]
 706   run [
 707     editor-event-loop screen, console, e
 708   ]
 709   screen-should-contain [
 710     .          .
 711     .a0bc      .
 712     .╌╌╌╌╌╌╌╌╌╌.
 713     .          .
 714   ]
 715   check-trace-count-for-label 3, [print-character]
 716 ]
 717 
 718 after <handle-special-key> [
 719   {
 720     move-to-previous-character?:bool <- equal k, 65515/left-arrow
 721     break-unless move-to-previous-character?
 722     trace 10, [app], [left arrow]
 723     # if not at start of text (before-cursor at § sentinel)
 724     prev:&:duplex-list:char <- prev before-cursor
 725     return-unless prev, 0/don't-render
 726     <begin-move-cursor>
 727     go-render? <- move-cursor-coordinates-left editor
 728     before-cursor <- copy prev
 729     *editor <- put *editor, before-cursor:offset, before-cursor
 730     undo-coalesce-tag:num <- copy 1/left-arrow
 731     <end-move-cursor>
 732     return
 733   }
 734 ]
 735 
 736 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
 737   local-scope
 738   assume-screen 10/width, 5/height
 739   # initialize editor with two lines
 740   s:text <- new [abc
 741 d]
 742   e:&:editor <- new-editor s, 0/left, 10/right
 743   editor-render screen, e
 744   $clear-trace
 745   # position cursor at start of second line (so there's no previous newline)
 746   assume-console [
 747     left-click 2, 0
 748     press left-arrow
 749   ]
 750   run [
 751     editor-event-loop screen, console, e
 752     3:num/raw <- get *e, cursor-row:offset
 753     4:num/raw <- get *e, cursor-column:offset
 754   ]
 755   memory-should-contain [
 756     3 <- 1
 757     4 <- 3
 758   ]
 759   check-trace-count-for-label 0, [print-character]
 760 ]
 761 
 762 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
 763   local-scope
 764   assume-screen 10/width, 5/height
 765   # initialize editor with three lines
 766   s:text <- new [abc
 767 def
 768 g]
 769   e:&:editor <- new-editor s:text, 0/left, 10/right
 770   editor-render screen, e
 771   $clear-trace
 772   # position cursor further down (so there's a newline before the character at
 773   # the cursor)
 774   assume-console [
 775     left-click 3, 0
 776     press left-arrow
 777     type [0]
 778   ]
 779   run [
 780     editor-event-loop screen, console, e
 781   ]
 782   screen-should-contain [
 783     .          .
 784     .abc       .
 785     .def0      .
 786     .g         .
 787     .╌╌╌╌╌╌╌╌╌╌.
 788   ]
 789   check-trace-count-for-label 1, [print-character]  # just the '0'
 790 ]
 791 
 792 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
 793   local-scope
 794   assume-screen 10/width, 5/height
 795   s:text <- new [abc
 796 def
 797 g]
 798   e:&:editor <- new-editor s, 0/left, 10/right
 799   editor-render screen, e
 800   $clear-trace
 801   # position cursor at start of text, press left-arrow, then type a character
 802   assume-console [
 803     left-click 1, 0
 804     press left-arrow
 805     type [0]
 806   ]
 807   run [
 808     editor-event-loop screen, console, e
 809   ]
 810   # left-arrow should have had no effect
 811   screen-should-contain [
 812     .          .
 813     .0abc      .
 814     .def       .
 815     .g         .
 816     .╌╌╌╌╌╌╌╌╌╌.
 817   ]
 818   check-trace-count-for-label 4, [print-character]  # length of first line
 819 ]
 820 
 821 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
 822   local-scope
 823   assume-screen 10/width, 5/height
 824   # initialize editor with text containing an empty line
 825   s:text <- new [abc
 826 
 827 d]
 828   e:&:editor <- new-editor s, 0/left, 10/right
 829   editor-render screen, e:&:editor
 830   $clear-trace
 831   # position cursor right after empty line
 832   assume-console [
 833     left-click 3, 0
 834     press left-arrow
 835     type [0]
 836   ]
 837   run [
 838     editor-event-loop screen, console, e
 839   ]
 840   screen-should-contain [
 841     .          .
 842     .abc       .
 843     .0         .
 844     .d         .
 845     .╌╌╌╌╌╌╌╌╌╌.
 846   ]
 847   check-trace-count-for-label 1, [print-character]  # just the '0'
 848 ]
 849 
 850 scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
 851   local-scope
 852   assume-screen 10/width, 5/height
 853   # initialize editor with a wrapping line
 854   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 855   editor-render screen, e
 856   $clear-trace
 857   screen-should-contain [
 858     .          .
 859     .abcd↩     .
 860     .ef        .
 861     .╌╌╌╌╌     .
 862     .          .
 863   ]
 864   # position cursor right after empty line
 865   assume-console [
 866     left-click 2, 0
 867     press left-arrow
 868   ]
 869   run [
 870     editor-event-loop screen, console, e
 871     3:num/raw <- get *e, cursor-row:offset
 872     4:num/raw <- get *e, cursor-column:offset
 873   ]
 874   memory-should-contain [
 875     3 <- 1  # previous row
 876     4 <- 3  # right margin except wrap icon
 877   ]
 878   check-trace-count-for-label 0, [print-character]
 879 ]
 880 
 881 scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [
 882   local-scope
 883   assume-screen 10/width, 5/height
 884   # initialize editor with a wrapping line followed by a second line
 885   s:text <- new [abcdef
 886 g]
 887   e:&:editor <- new-editor s, 0/left, 5/right
 888   editor-render screen, e
 889   $clear-trace
 890   screen-should-contain [
 891     .          .
 892     .abcd↩     .
 893     .ef        .
 894     .g         .
 895     .╌╌╌╌╌     .
 896   ]
 897   # position cursor right after empty line
 898   assume-console [
 899     left-click 3, 0
 900     press left-arrow
 901   ]
 902   run [
 903     editor-event-loop screen, console, e
 904     3:num/raw <- get *e, cursor-row:offset
 905     4:num/raw <- get *e, cursor-column:offset
 906   ]
 907   memory-should-contain [
 908     3 <- 2  # previous row
 909     4 <- 2  # end of wrapped line
 910   ]
 911   check-trace-count-for-label 0, [print-character]
 912 ]
 913 
 914 scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [
 915   local-scope
 916   assume-screen 10/width, 5/height
 917   # initialize editor with a line on the verge of wrapping, followed by a second line
 918   s:text <- new [abcd
 919 e]
 920   e:&:editor <- new-editor s, 0/left, 5/right
 921   editor-render screen, e
 922   $clear-trace
 923   screen-should-contain [
 924     .          .
 925     .abcd      .
 926     .e         .
 927     .╌╌╌╌╌     .
 928     .          .
 929   ]
 930   # position cursor right after empty line
 931   assume-console [
 932     left-click 2, 0
 933     press left-arrow
 934   ]
 935   run [
 936     editor-event-loop screen, console, e
 937     3:num/raw <- get *e, cursor-row:offset
 938     4:num/raw <- get *e, cursor-column:offset
 939   ]
 940   memory-should-contain [
 941     3 <- 1  # previous row
 942     4 <- 4  # end of wrapped line
 943   ]
 944   check-trace-count-for-label 0, [print-character]
 945 ]
 946 
 947 # todo: ctrl-left: previous word-start
 948 
 949 # up arrow
 950 
 951 scenario editor-moves-to-previous-line-with-up-arrow [
 952   local-scope
 953   assume-screen 10/width, 5/height
 954   s:text <- new [abc
 955 def]
 956   e:&:editor <- new-editor s, 0/left, 10/right
 957   editor-render screen, e
 958   $clear-trace
 959   assume-console [
 960     left-click 2, 1
 961     press up-arrow
 962   ]
 963   run [
 964     editor-event-loop screen, console, e
 965     3:num/raw <- get *e, cursor-row:offset
 966     4:num/raw <- get *e, cursor-column:offset
 967   ]
 968   memory-should-contain [
 969     3 <- 1
 970     4 <- 1
 971   ]
 972   check-trace-count-for-label 0, [print-character]
 973   assume-console [
 974     type [0]
 975   ]
 976   run [
 977     editor-event-loop screen, console, e
 978   ]
 979   screen-should-contain [
 980     .          .
 981     .a0bc      .
 982     .def       .
 983     .╌╌╌╌╌╌╌╌╌╌.
 984     .          .
 985   ]
 986 ]
 987 
 988 after <handle-special-key> [
 989   {
 990     move-to-previous-line?:bool <- equal k, 65517/up-arrow
 991     break-unless move-to-previous-line?
 992     <begin-move-cursor>
 993     go-render? <- move-to-previous-line editor
 994     undo-coalesce-tag:num <- copy 3/up-arrow
 995     <end-move-cursor>
 996     return
 997   }
 998 ]
 999 
1000 def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [
1001   local-scope
1002   load-inputs
1003   go-render?:bool <- copy 0/false
1004   cursor-row:num <- get *editor, cursor-row:offset
1005   cursor-column:num <- get *editor, cursor-column:offset
1006   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1007   left:num <- get *editor, left:offset
1008   right:num <- get *editor, right:offset
1009   already-at-top?:bool <- lesser-or-equal cursor-row, 1/top
1010   {
1011     # if cursor not at top, move it
1012     break-if already-at-top?
1013     # if not at start of screen line, move to start of screen line (previous newline)
1014     # then scan back another line
1015     # if either step fails, give up without modifying cursor or coordinates
1016     curr:&:duplex-list:char <- copy before-cursor
1017     old:&:duplex-list:char <- copy curr
1018     {
1019       at-left?:bool <- equal cursor-column, left
1020       break-if at-left?
1021       curr <- before-previous-screen-line curr, editor
1022       no-motion?:bool <- equal curr, old
1023       return-if no-motion?
1024     }
1025     {
1026       curr <- before-previous-screen-line curr, editor
1027       no-motion?:bool <- equal curr, old
1028       return-if no-motion?
1029     }
1030     before-cursor <- copy curr
1031     *editor <- put *editor, before-cursor:offset, before-cursor
1032     cursor-row <- subtract cursor-row, 1
1033     *editor <- put *editor, cursor-row:offset, cursor-row
1034     # scan ahead to right column or until end of line
1035     target-column:num <- copy cursor-column
1036     cursor-column <- copy left
1037     *editor <- put *editor, cursor-column:offset, cursor-column
1038     {
1039       done?:bool <- greater-or-equal cursor-column, target-column
1040       break-if done?
1041       curr:&:duplex-list:char <- next before-cursor
1042       break-unless curr
1043       currc:char <- get *curr, value:offset
1044       at-newline?:bool <- equal currc, 10/newline
1045       break-if at-newline?
1046       #
1047       before-cursor <- copy curr
1048       *editor <- put *editor, before-cursor:offset, before-cursor
1049       cursor-column <- add cursor-column, 1
1050       *editor <- put *editor, cursor-column:offset, cursor-column
1051       loop
1052     }
1053     return
1054   }
1055   {
1056     # if cursor already at top, scroll up
1057     break-unless already-at-top?
1058     <scroll-up>
1059     return 1/go-render
1060   }
1061 ]
1062 
1063 # Takes a pointer into the doubly-linked list, scans back to before start of
1064 # previous *wrapped* line.
1065 # Returns original if no next newline.
1066 # Beware: never return null pointer.
1067 def before-previous-screen-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [
1068   local-scope
1069   load-inputs
1070   curr:&:duplex-list:char <- copy in
1071   c:char <- get *curr, value:offset
1072   # compute max, number of characters to skip
1073   #   1 + len%(width-1)
1074   #   except rotate second term to vary from 1 to width-1 rather than 0 to width-2
1075   left:num <- get *editor, left:offset
1076   right:num <- get *editor, right:offset
1077   max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon
1078   sentinel:&:duplex-list:char <- get *editor, data:offset
1079   len:num <- previous-line-length curr, sentinel
1080   {
1081     break-if len
1082     # empty line; just skip this newline
1083     prev:&:duplex-list:char <- prev curr
1084     return-unless prev, curr
1085     return prev
1086   }
1087   _, max:num <- divide-with-remainder len, max-line-length
1088   # remainder 0 => scan one width-worth
1089   {
1090     break-if max
1091     max <- copy max-line-length
1092   }
1093   max <- add max, 1
1094   count:num <- copy 0
1095   # skip 'max' characters
1096   {
1097     done?:bool <- greater-or-equal count, max
1098     break-if done?
1099     prev:&:duplex-list:char <- prev curr
1100     break-unless prev
1101     curr <- copy prev
1102     count <- add count, 1
1103     loop
1104   }
1105   return curr
1106 ]
1107 
1108 scenario editor-adjusts-column-at-previous-line [
1109   local-scope
1110   assume-screen 10/width, 5/height
1111   s:text <- new [ab
1112 def]
1113   e:&:editor <- new-editor s, 0/left, 10/right
1114   editor-render screen, e
1115   $clear-trace
1116   assume-console [
1117     left-click 2, 3
1118     press up-arrow
1119   ]
1120   run [
1121     editor-event-loop screen, console, e
1122     3:num/raw <- get *e, cursor-row:offset
1123     4:num/raw <- get *e, cursor-column:offset
1124   ]
1125   memory-should-contain [
1126     3 <- 1
1127     4 <- 2
1128   ]
1129   check-trace-count-for-label 0, [print-character]
1130   assume-console [
1131     type [0]
1132   ]
1133   run [
1134     editor-event-loop screen, console, e
1135   ]
1136   screen-should-contain [
1137     .          .
1138     .ab0       .
1139     .def       .
1140     .╌╌╌╌╌╌╌╌╌╌.
1141     .          .
1142   ]
1143 ]
1144 
1145 scenario editor-adjusts-column-at-empty-line [
1146   local-scope
1147   assume-screen 10/width, 5/height
1148   s:text <- new [
1149 def]
1150   e:&:editor <- new-editor s, 0/left, 10/right
1151   editor-render screen, e
1152   $clear-trace
1153   assume-console [
1154     left-click 2, 3
1155     press up-arrow
1156   ]
1157   run [
1158     editor-event-loop screen, console, e
1159     3:num/raw <- get *e, cursor-row:offset
1160     4:num/raw <- get *e, cursor-column:offset
1161   ]
1162   memory-should-contain [
1163     3 <- 1
1164     4 <- 0
1165   ]
1166   check-trace-count-for-label 0, [print-character]
1167   assume-console [
1168     type [0]
1169   ]
1170   run [
1171     editor-event-loop screen, console, e
1172   ]
1173   screen-should-contain [
1174     .          .
1175     .0         .
1176     .def       .
1177     .╌╌╌╌╌╌╌╌╌╌.
1178     .          .
1179   ]
1180 ]
1181 
1182 scenario editor-moves-to-previous-line-from-zero-margin [
1183   local-scope
1184   assume-screen 10/width, 5/height
1185   # start out with three lines
1186   s:text <- new [abc
1187 def
1188 ghi]
1189   e:&:editor <- new-editor s, 0/left, 10/right
1190   editor-render screen, e
pan class="p">>
<span id="L85" class="LineNr">  85 </span>  curr <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> curr
<span id="L86" class="LineNr">  86 </span>  row:num <span class="Special">&lt;-</span> copy <span class="Constant">1/top</span>
<span id="L87" class="LineNr">  87 </span>  column:num <span class="Special">&lt;-</span> copy left
<span id="L88" class="LineNr">  88 </span>  *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-row:offset</span>, target-row
<span id="L89" class="LineNr">  89 </span>  cursor-row:num <span class="Special">&lt;-</span> copy target-row
<span id="L90" class="LineNr">  90 </span>  *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-column:offset</span>, target-column
<span id="L91" class="LineNr">  91 </span>  cursor-column:num <span class="Special">&lt;-</span> copy target-column
<span id="L92" class="LineNr">  92 </span>  before-cursor:&amp;:<a href='../065duplex_list.mu.html#L3'>duplex-list</a>:char <span class="Special">&lt;-</span> get *editor, <span class="Constant">before-cursor:offset</span>
<span id="L93" class="LineNr">  93 </span>  <span class="Delimiter">{</span>
<span id="L94" class="LineNr">  94 </span><span class="Constant">    +next-character</span>
<span id="L95" class="LineNr">  95 </span>    <span class="muControl">break-unless</span> curr
<span id="L96" class="LineNr">  96 </span>    off-screen?:bool <span class="Special">&lt;-</span> greater-or-equal row, <a href='../081print.mu.html#L782'>screen-height</a>
<span id="L97" class="LineNr">  97 </span>    <span class="muControl">break-if</span> off-screen?
<span id="L98" class="LineNr">  98 </span>    <span class="Comment"># update editor.before-cursor</span>
<span id="L99" class="LineNr">  99 </span>    <span class="Comment"># Doing so at the start of each iteration ensures it stays one step behind</span>
<span id="L100" class="LineNr"> 100 </span>    <span class="Comment"># the current character.</span>
<span id="L101" class="LineNr"> 101 </span>    <span class="Delimiter">{</span>
<span id="L102" class="LineNr"> 102 </span>      at-cursor-row?:bool <span class="Special">&lt;-</span> equal row, cursor-row
<span id="L103" class="LineNr"> 103 </span>      <span class="muControl">break-unless</span> at-cursor-row?
<span id="L104" class="LineNr"> 104 </span>      at-cursor?:bool <span class="Special">&lt;-</span> equal column, cursor-column
<span id="L105" class="LineNr"> 105 </span>      <span class="muControl">break-unless</span> at-cursor?
<span id="L106" class="LineNr"> 106 </span>      before-cursor <span class="Special">&lt;-</span> copy <a href='../065duplex_list.mu.html#L32'>prev</a>
<span id="L107" class="LineNr"> 107 </span>      *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">before-cursor:offset</span>, before-cursor
<span id="L108" class="LineNr"> 108 </span>    <span class="Delimiter">}</span>
<span id="L109" class="LineNr"> 109 </span>    c:char <span class="Special">&lt;-</span> get *curr, <span class="Constant">value:offset</span>
<span id="L110" class="LineNr"> 110 </span>    <span class="Delimiter">{</span>
<span id="L111" class="LineNr"> 111 </span>      <span class="Comment"># newline? move to left rather than 0</span>
<span id="L112" class="LineNr"> 112 </span>      newline?:bool <span class="Special">&lt;-</span> equal c, <span class="Constant">10/newline</span>
<span id="L113" class="LineNr"> 113 </span>      <span class="muControl">break-unless</span> newline?
<span id="L114" class="LineNr"> 114 </span>      <span class="Comment"># adjust cursor if necessary</span>
<span id="L115" class="LineNr"> 115 </span>      <span class="Delimiter">{</span>
<span id="L116" class="LineNr"> 116 </span>        at-cursor-row?:bool <span class="Special">&lt;-</span> equal row, cursor-row
<span id="L117" class="LineNr"> 117 </span>        <span class="muControl">break-unless</span> at-cursor-row?
<span id="L118" class="LineNr"> 118 </span>        left-of-cursor?:bool <span class="Special">&lt;-</span> lesser-than column, cursor-column
<span id="L119" class="LineNr"> 119 </span>        <span class="muControl">break-unless</span> left-of-cursor?
<span id="L120" class="LineNr"> 120 </span>        cursor-column <span class="Special">&lt;-</span> copy column
<span id="L121" class="LineNr"> 121 </span>        *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-column:offset</span>, cursor-column
<span id="L122" class="LineNr"> 122 </span>        before-cursor <span class="Special">&lt;-</span> copy <a href='../065duplex_list.mu.html#L32'>prev</a>
<span id="L123" class="LineNr"> 123 </span>        *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">before-cursor:offset</span>, before-cursor
<span id="L124" class="LineNr"> 124 </span>      <span class="Delimiter">}</span>
<span id="L125" class="LineNr"> 125 </span>      <span class="Comment"># skip to next line</span>
<span id="L126" class="LineNr"> 126 </span>      row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
<span id="L127" class="LineNr"> 127 </span>      column <span class="Special">&lt;-</span> copy left
<span id="L128" class="LineNr"> 128 </span>      curr <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> curr
<span id="L129" class="LineNr"> 129 </span>      <a href='../065duplex_list.mu.html#L32'>prev</a> <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> <a href='../065duplex_list.mu.html#L32'>prev</a>
<span id="L130" class="LineNr"> 130 </span>     <span class="muControl"> loop</span> <span class="Constant">+next-character</span>
<span id="L131" class="LineNr"> 131 </span>    <span class="Delimiter">}</span>
<span id="L132" class="LineNr"> 132 </span>    <span class="Delimiter">{</span>
<span id="L133" class="LineNr"> 133 </span>      <span class="Comment"># at right? wrap. even if there's only one more letter left; we need</span>
<span id="L134" class="LineNr"> 134 </span>      <span class="Comment"># room for clicking on the cursor after it.</span>
<span id="L135" class="LineNr"> 135 </span>      at-right?:bool <span class="Special">&lt;-</span> equal column, right
<span id="L136" class="LineNr"> 136 </span>      <span class="muControl">break-unless</span> at-right?
<span id="L137" class="LineNr"> 137 </span>      column <span class="Special">&lt;-</span> copy left
<span id="L138" class="LineNr"> 138 </span>      row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
<span id="L139" class="LineNr"> 139 </span>      <span class="Comment"># don't increment curr/prev</span>
<span id="L140" class="LineNr"> 140 </span>     <span class="muControl"> loop</span> <span class="Constant">+next-character</span>
<span id="L141" class="LineNr"> 141 </span>    <span class="Delimiter">}</span>
<span id="L142" class="LineNr"> 142 </span>    curr <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> curr
<span id="L143" class="LineNr"> 143 </span>    <a href='../065duplex_list.mu.html#L32'>prev</a> <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> <a href='../065duplex_list.mu.html#L32'>prev</a>
<span id="L144" class="LineNr"> 144 </span>    column <span class="Special">&lt;-</span> add column,<span class="Constant"> 1</span>
<span id="L145" class="LineNr"> 145 </span>   <span class="muControl"> loop</span>
<span id="L146" class="LineNr"> 146 </span>  <span class="Delimiter">}</span>
<span id="L147" class="LineNr"> 147 </span>  <span class="Comment"># is cursor to the right of the last line? move to end</span>
<span id="L148" class="LineNr"> 148 </span>  <span class="Delimiter">{</span>
<span id="L149" class="LineNr"> 149 </span>    at-cursor-row?:bool <span class="Special">&lt;-</span> equal row, cursor-row
<span id="L150" class="LineNr"> 150 </span>    cursor-outside-line?:bool <span class="Special">&lt;-</span> lesser-or-equal column, cursor-column
<span id="L151" class="LineNr"> 151 </span>    before-cursor-on-same-line?:bool <span class="Special">&lt;-</span> and at-cursor-row?, cursor-outside-line?
<span id="L152" class="LineNr"> 152 </span>    above-cursor-row?:bool <span class="Special">&lt;-</span> lesser-than row, cursor-row
<span id="L153" class="LineNr"> 153 </span>    before-cursor?:bool <span class="Special">&lt;-</span> or before-cursor-on-same-line?, above-cursor-row?
<span id="L154" class="LineNr"> 154 </span>    <span class="muControl">break-unless</span> before-cursor?
<span id="L155" class="LineNr"> 155 </span>    cursor-row <span class="Special">&lt;-</span> copy row
<span id="L156" class="LineNr"> 156 </span>    *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-row:offset</span>, cursor-row
<span id="L157" class="LineNr"> 157 </span>    cursor-column <span class="Special">&lt;-</span> copy column
<span id="L158" class="LineNr"> 158 </span>    *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-column:offset</span>, cursor-column
<span id="L159" class="LineNr"> 159 </span>    before-cursor <span class="Special">&lt;-</span> copy <a href='../065duplex_list.mu.html#L32'>prev</a>
<span id="L160" class="LineNr"> 160 </span>    *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">before-cursor:offset</span>, before-cursor
<span id="L161" class="LineNr"> 161 </span>  <span class="Delimiter">}</span>
<span id="L162" class="LineNr"> 162 </span>]
<span id="L163" class="LineNr"> 163 </span>
<span id="L164" class="LineNr"> 164 </span><span class="Comment"># Process an event 'e' and try to minimally update the screen.</span>
<span id="L165" class="LineNr"> 165 </span><span class="Comment"># Set 'go-render?' to true to indicate the caller must perform a non-minimal update.</span>
<span id="L166" class="LineNr"> 166 </span><span class="muRecipe">def</span> <a href='002-typing.mu.html#L166'>handle-keyboard-event</a> <a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a>, editor:&amp;:editor, e:<a href='../084console.mu.html#L4'>event</a><span class="muRecipe"> -&gt; </span>go-render?:bool, <a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a>, editor:&amp;:editor [
<span id="L167" class="LineNr"> 167 </span>  <span class="Constant">local-scope</span>
<span id="L168" class="LineNr"> 168 </span>  <span class="Constant">load-inputs</span>
<span id="L169" class="LineNr"> 169 </span>  <span class="muControl">return-unless</span> editor, <span class="Constant">0/don't-render</span>
<span id="L170" class="LineNr"> 170 </span>  <a href='../081print.mu.html#L768'>screen-width</a>:num <span class="Special">&lt;-</span> <a href='../081print.mu.html#L768'>screen-width</a> <a href='../081print.mu.html#L16'>screen</a>
<span id="L171" class="LineNr"> 171 </span>  <a href='../081print.mu.html#L782'>screen-height</a>:num <span class="Special">&lt;-</span> <a href='../081print.mu.html#L782'>screen-height</a> <a href='../081print.mu.html#L16'>screen</a>
<span id="L172" class="LineNr"> 172 </span>  left:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">left:offset</span>
<span id="L173" class="LineNr"> 173 </span>  right:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">right:offset</span>
<span id="L174" class="LineNr"> 174 </span>  before-cursor:&amp;:<a href='../065duplex_list.mu.html#L3'>duplex-list</a>:char <span class="Special">&lt;-</span> get *editor, <span class="Constant">before-cursor:offset</span>
<span id="L175" class="LineNr"> 175 </span>  cursor-row:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">cursor-row:offset</span>
<span id="L176" class="LineNr"> 176 </span>  cursor-column:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">cursor-column:offset</span>
<span id="L177" class="LineNr"> 177 </span>  save-row:num <span class="Special">&lt;-</span> copy cursor-row
<span id="L178" class="LineNr"> 178 </span>  save-column:num <span class="Special">&lt;-</span> copy cursor-column
<span id="L179" class="LineNr"> 179 </span>  <span class="Comment"># character</span>
<span id="L180" class="LineNr"> 180 </span>  <span class="Delimiter">{</span>
<span id="L181" class="LineNr"> 181 </span>    c:char, is-unicode?:bool <span class="Special">&lt;-</span> maybe-convert e, <span class="Constant">text:variant</span>
<span id="L182" class="LineNr"> 182 </span>    <span class="muControl">break-unless</span> is-unicode?
<span id="L183" class="LineNr"> 183 </span>    trace<span class="Constant"> 10</span>, <span class="Constant">[app]</span>, <span class="Constant">[handle-keyboard-event: special character]</span>
<span id="L184" class="LineNr"> 184 </span>    <span class="Comment"># exceptions for special characters go here</span>
<span id="L185" class="LineNr"> 185 </span><span class="Constant">    <a href='002-typing.mu.html#L185'>&lt;handle-special-character&gt;</a></span>
<span id="L186" class="LineNr"> 186 </span>    <span class="Comment"># ignore any other special characters</span>
<span id="L187" class="LineNr"> 187 </span>    regular-character?:bool <span class="Special">&lt;-</span> greater-or-equal c, <span class="Constant">32/space</span>
<span id="L188" class="LineNr"> 188 </span>    <span class="muControl">return-unless</span> regular-character?, <span class="Constant">0/don't-render</span>
<span id="L189" class="LineNr"> 189 </span>    <span class="Comment"># otherwise type it in</span>
<span id="L190" class="LineNr"> 190 </span><span class="Constant">    &lt;begin-insert-character&gt;</span>
<span id="L191" class="LineNr"> 191 </span>    go-render? <span class="Special">&lt;-</span> <a href='002-typing.mu.html#L203'>insert-at-cursor</a> editor, c, <a href='../081print.mu.html#L16'>screen</a>
<span id="L192" class="LineNr"> 192 </span><span class="Constant">    &lt;end-insert-character&gt;</span>
<span id="L193" class="LineNr"> 193 </span>   <span class="muControl"> return</span>
<span id="L194" class="LineNr"> 194 </span>  <span class="Delimiter">}</span>
<span id="L195" class="LineNr"> 195 </span>  <span class="Comment"># special key to modify the text or move the cursor</span>
<span id="L196" class="LineNr"> 196 </span>  k:num, is-keycode?:bool <span class="Special">&lt;-</span> maybe-convert e:<a href='../084console.mu.html#L4'>event</a>, <span class="Constant">keycode:variant</span>
<span id="L197" class="LineNr"> 197 </span>  assert is-keycode?, <span class="Constant">[event was of unknown type; neither keyboard nor mouse]</span>
<span id="L198" class="LineNr"> 198 </span>  <span class="Comment"># handlers for each special key will go here</span>
<span id="L199" class="LineNr"> 199 </span><span class="Constant">  <a href='002-typing.mu.html#L199'>&lt;handle-special-key&gt;</a></span>
<span id="L200" class="LineNr"> 200 </span> <span class="muControl"> return</span> <span class="Constant">1/go-render</span>
<span id="L201" class="LineNr"> 201 </span>]
<span id="L202" class="LineNr"> 202 </span>
<span id="L203" class="LineNr"> 203 </span><span class="muRecipe">def</span> <a href='002-typing.mu.html#L203'>insert-at-cursor</a> editor:&amp;:editor, c:char, <a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a><span class="muRecipe"> -&gt; </span>go-render?:bool, editor:&amp;:editor, <a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a> [
<span id="L204" class="LineNr"> 204 </span>  <span class="Constant">local-scope</span>
<span id="L205" class="LineNr"> 205 </span>  <span class="Constant">load-inputs</span>
<span id="L206" class="LineNr"> 206 </span>  before-cursor:&amp;:<a href='../065duplex_list.mu.html#L3'>duplex-list</a>:char <span class="Special">&lt;-</span> get *editor, <span class="Constant">before-cursor:offset</span>
<span id="L207" class="LineNr"> 207 </span>  insert c, before-cursor
<span id="L208" class="LineNr"> 208 </span>  before-cursor <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> before-cursor
<span id="L209" class="LineNr"> 209 </span>  *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">before-cursor:offset</span>, before-cursor
<span id="L210" class="LineNr"> 210 </span>  cursor-row:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">cursor-row:offset</span>
<span id="L211" class="LineNr"> 211 </span>  cursor-column:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">cursor-column:offset</span>
<span id="L212" class="LineNr"> 212 </span>  left:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">left:offset</span>
<span id="L213" class="LineNr"> 213 </span>  right:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">right:offset</span>
<span id="L214" class="LineNr"> 214 </span>  save-row:num <span class="Special">&lt;-</span> copy cursor-row
<span id="L215" class="LineNr"> 215 </span>  save-column:num <span class="Special">&lt;-</span> copy cursor-column
<span id="L216" class="LineNr"> 216 </span>  <a href='../081print.mu.html#L768'>screen-width</a>:num <span class="Special">&lt;-</span> <a href='../081print.mu.html#L768'>screen-width</a> <a href='../081print.mu.html#L16'>screen</a>
<span id="L217" class="LineNr"> 217 </span>  <a href='../081print.mu.html#L782'>screen-height</a>:num <span class="Special">&lt;-</span> <a href='../081print.mu.html#L782'>screen-height</a> <a href='../081print.mu.html#L16'>screen</a>
<span id="L218" class="LineNr"> 218 </span>  <span class="Comment"># occasionally we'll need to mess with the cursor</span>
<span id="L219" class="LineNr"> 219 </span><span class="Constant">  <a href='002-typing.mu.html#L219'>&lt;insert-character-special-case&gt;</a></span>
<span id="L220" class="LineNr"> 220 </span>  <span class="Comment"># but mostly we'll just move the cursor right</span>
<span id="L221" class="LineNr"> 221 </span>  cursor-column <span class="Special">&lt;-</span> add cursor-column,<span class="Constant"> 1</span>
<span id="L222" class="LineNr"> 222 </span>  *editor <span class="Special">&lt;-</span> put *editor, <span class="Constant">cursor-column:offset</span>, cursor-column
<span id="L223" class="LineNr"> 223 </span>  <a href='../065duplex_list.mu.html#L25'>next</a>:&amp;:<a href='../065duplex_list.mu.html#L3'>duplex-list</a>:char <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> before-cursor
<span id="L224" class="LineNr"> 224 </span>  <span class="Delimiter">{</span>
<span id="L225" class="LineNr"> 225 </span>    <span class="Comment"># at end of all text? no need to scroll? just print the character and leave</span>
<span id="L226" class="LineNr"> 226 </span>    at-end?:bool <span class="Special">&lt;-</span> equal <a href='../065duplex_list.mu.html#L25'>next</a>, <span class="Constant">0/null</span>
<span id="L227" class="LineNr"> 227 </span>    <span class="muControl">break-unless</span> at-end?
<span id="L228" class="LineNr"> 228 </span>    bottom:num <span class="Special">&lt;-</span> subtract <a href='../081print.mu.html#L782'>screen-height</a>,<span class="Constant"> 1</span>
<span id="L229" class="LineNr"> 229 </span>    at-bottom?:bool <span class="Special">&lt;-</span> equal save-row, bottom
<span id="L230" class="LineNr"> 230 </span>    at-right?:bool <span class="Special">&lt;-</span> equal save-column, right
<span id="L231" class="LineNr"> 231 </span>    overflow?:bool <span class="Special">&lt;-</span> and at-bottom?, at-right?
<span id="L232" class="LineNr"> 232 </span>    <span class="muControl">break-if</span> overflow?
<span id="L233" class="LineNr"> 233 </span>    move-cursor <a href='../081print.mu.html#L16'>screen</a>, save-row, save-column
<span id="L234" class="LineNr"> 234 </span>    print <a href='../081print.mu.html#L16'>screen</a>, c
<span id="L235" class="LineNr"> 235 </span>   <span class="muControl"> return</span> <span class="Constant">0/don't-render</span>
<span id="L236" class="LineNr"> 236 </span>  <span class="Delimiter">}</span>
<span id="L237" class="LineNr"> 237 </span>  <span class="Delimiter">{</span>
<span id="L238" class="LineNr"> 238 </span>    <span class="Comment"># not at right margin? print the character and rest of line</span>
<span id="L239" class="LineNr"> 239 </span>    <span class="muControl">break-unless</span> <a href='../065duplex_list.mu.html#L25'>next</a>
<span id="L240" class="LineNr"> 240 </span>    at-right?:bool <span class="Special">&lt;-</span> greater-or-equal cursor-column, <a href='../081print.mu.html#L768'>screen-width</a>
<span id="L241" class="LineNr"> 241 </span>    <span class="muControl">break-if</span> at-right?
<span id="L242" class="LineNr"> 242 </span>    curr:&amp;:<a href='../065duplex_list.mu.html#L3'>duplex-list</a>:char <span class="Special">&lt;-</span> copy before-cursor
<span id="L243" class="LineNr"> 243 </span>    move-cursor <a href='../081print.mu.html#L16'>screen</a>, save-row, save-column
<span id="L244" class="LineNr"> 244 </span>    curr-column:num <span class="Special">&lt;-</span> copy save-column
<span id="L245" class="LineNr"> 245 </span>    <span class="Delimiter">{</span>
<span id="L246" class="LineNr"> 246 </span>      <span class="Comment"># hit right margin? give up and let caller render</span>
<span id="L247" class="LineNr"> 247 </span>      at-right?:bool <span class="Special">&lt;-</span> greater-than curr-column, right
<span id="L248" class="LineNr"> 248 </span>      <span class="muControl">return-if</span> at-right?, <span class="Constant">1/go-render</span>
<span id="L249" class="LineNr"> 249 </span>      <span class="muControl">break-unless</span> curr
<span id="L250" class="LineNr"> 250 </span>      <span class="Comment"># newline? done.</span>
<span id="L251" class="LineNr"> 251 </span>      currc:char <span class="Special">&lt;-</span> get *curr, <span class="Constant">value:offset</span>
<span id="L252" class="LineNr"> 252 </span>      at-newline?:bool <span class="Special">&lt;-</span> equal currc, <span class="Constant">10/newline</span>
<span id="L253" class="LineNr"> 253 </span>      <span class="muControl">break-if</span> at-newline?
<span id="L254" class="LineNr"> 254 </span>      print <a href='../081print.mu.html#L16'>screen</a>, currc
<span id="L255" class="LineNr"> 255 </span>      curr-column <span class="Special">&lt;-</span> add curr-column,<span class="Constant"> 1</span>
<span id="L256" class="LineNr"> 256 </span>      curr <span class="Special">&lt;-</span> <a href='../065duplex_list.mu.html#L25'>next</a> curr
<span id="L257" class="LineNr"> 257 </span>     <span class="muControl"> loop</span>
<span id="L258" class="LineNr"> 258 </span>    <span class="Delimiter">}</span>
<span id="L259" class="LineNr"> 259 </span>   <span class="muControl"> return</span> <span class="Constant">0/don't-render</span>
<span id="L260" class="LineNr"> 260 </span>  <span class="Delimiter">}</span>
<span id="L261" class="LineNr"> 261 </span> <span class="muControl"> return</span> <span class="Constant">1/go-render</span>
<span id="L262" class="LineNr"> 262 </span>]
<span id="L263" class="LineNr"> 263 </span>
<span id="L264" class="LineNr"> 264 </span><span class="Comment"># helper for tests</span>
<span id="L265" class="LineNr"> 265 </span><span class="muRecipe">def</span> <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a>, editor:&amp;:editor<span class="muRecipe"> -&gt; </span><a href='../081print.mu.html#L16'>screen</a>:&amp;:<a href='../081print.mu.html#L16'>screen</a>, editor:&amp;:editor [
<span id="L266" class="LineNr"> 266 </span>  <span class="Constant">local-scope</span>
<span id="L267" class="LineNr"> 267 </span>  <span class="Constant">load-inputs</span>
<span id="L268" class="LineNr"> 268 </span>  old-top-idx:num <span class="Special">&lt;-</span> <a href='../081print.mu.html#L509'>save-top-idx</a> <a href='../081print.mu.html#L16'>screen</a>
<span id="L269" class="LineNr"> 269 </span>  left:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">left:offset</span>
<span id="L270" class="LineNr"> 270 </span>  right:num <span class="Special">&lt;-</span> get *editor, <span class="Constant">right:offset</span>
<span id="L271" class="LineNr"> 271 </span>  row:num, column:num <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L107'>render</a> <a href='../081print.mu.html#L16'>screen</a>, editor
<span id="L272" class="LineNr"> 272 </span>  <a href='002-typing.mu.html#L1114'>draw-horizontal</a> <a href='../081print.mu.html#L16'>screen</a>, row, left, right, <span class="Constant">9480/horizontal-dotted</span>
<span id="L273" class="LineNr"> 273 </span>  row <span class="Special">&lt;-</span> add row,<span class="Constant"> 1</span>
<span id="L274" class="LineNr"> 274 </span>  <a href='001-editor.mu.html#L209'>clear-screen-from</a> <a href='../081print.mu.html#L16'>screen</a>, row, left, left, right
<span id="L275" class="LineNr"> 275 </span>  <a href='../081print.mu.html#L515'>assert-no-scroll</a> <a href='../081print.mu.html#L16'>screen</a>, old-top-idx
<span id="L276" class="LineNr"> 276 </span>]
<span id="L277" class="LineNr"> 277 </span>
<span id="L278" class="LineNr"> 278 </span><span class="muScenario">scenario</span> editor-handles-empty-event-queue [
<span id="L279" class="LineNr"> 279 </span>  <span class="Constant">local-scope</span>
<span id="L280" class="LineNr"> 280 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L281" class="LineNr"> 281 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L282" class="LineNr"> 282 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L283" class="LineNr"> 283 </span>  assume-console <span class="Constant">[]</span>
<span id="L284" class="LineNr"> 284 </span>  run [
<span id="L285" class="LineNr"> 285 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L286" class="LineNr"> 286 </span>  ]
<span id="L287" class="LineNr"> 287 </span>  screen-should-contain [
<span id="L288" class="LineNr"> 288 </span>   <span class="Constant"> .          .</span>
<span id="L289" class="LineNr"> 289 </span>   <span class="Constant"> .abc       .</span>
<span id="L290" class="LineNr"> 290 </span><span class="Constant">    .╌╌╌╌╌╌╌╌╌╌.</span>
<span id="L291" class="LineNr"> 291 </span>   <span class="Constant"> .          .</span>
<span id="L292" class="LineNr"> 292 </span>  ]
<span id="L293" class="LineNr"> 293 </span>]
<span id="L294" class="LineNr"> 294 </span>
<span id="L295" class="LineNr"> 295 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks [
<span id="L296" class="LineNr"> 296 </span>  <span class="Constant">local-scope</span>
<span id="L297" class="LineNr"> 297 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L298" class="LineNr"> 298 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L299" class="LineNr"> 299 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L300" class="LineNr"> 300 </span>  $clear-trace
<span id="L301" class="LineNr"> 301 </span>  assume-console [
<span id="L302" class="LineNr"> 302 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 1</span>  <span class="Comment"># on the 'b'</span>
<span id="L303" class="LineNr"> 303 </span>  ]
<span id="L304" class="LineNr"> 304 </span>  run [
<span id="L305" class="LineNr"> 305 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L306" class="LineNr"> 306 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L307" class="LineNr"> 307 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L308" class="LineNr"> 308 </span>  ]
<span id="L309" class="LineNr"> 309 </span>  screen-should-contain [
<span id="L310" class="LineNr"> 310 </span>   <span class="Constant"> .          .</span>
<span id="L311" class="LineNr"> 311 </span>   <span class="Constant"> .abc       .</span>
<span id="L312" class="LineNr"> 312 </span><span class="Constant">    .╌╌╌╌╌╌╌╌╌╌.</span>
<span id="L313" class="LineNr"> 313 </span>   <span class="Constant"> .          .</span>
<span id="L314" class="LineNr"> 314 </span>  ]
<span id="L315" class="LineNr"> 315 </span>  memory-should-contain [
<span id="L316" class="LineNr"> 316 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>  <span class="Comment"># cursor is at row 0..</span>
<span id="L317" class="LineNr"> 317 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>  <span class="Comment"># ..and column 1</span>
<span id="L318" class="LineNr"> 318 </span>  ]
<span id="L319" class="LineNr"> 319 </span>  check-trace-count-for-label<span class="Constant"> 0</span>, <span class="Constant">[print-character]</span>
<span id="L320" class="LineNr"> 320 </span>]
<span id="L321" class="LineNr"> 321 </span>
<span id="L322" class="LineNr"> 322 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text [
<span id="L323" class="LineNr"> 323 </span>  <span class="Constant">local-scope</span>
<span id="L324" class="LineNr"> 324 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L325" class="LineNr"> 325 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L326" class="LineNr"> 326 </span>  $clear-trace
<span id="L327" class="LineNr"> 327 </span>  assume-console [
<span id="L328" class="LineNr"> 328 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 7</span>  <span class="Comment"># last line, to the right of text</span>
<span id="L329" class="LineNr"> 329 </span>  ]
<span id="L330" class="LineNr"> 330 </span>  run [
<span id="L331" class="LineNr"> 331 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L332" class="LineNr"> 332 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L333" class="LineNr"> 333 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L334" class="LineNr"> 334 </span>  ]
<span id="L335" class="LineNr"> 335 </span>  memory-should-contain [
<span id="L336" class="LineNr"> 336 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>  <span class="Comment"># cursor row</span>
<span id="L337" class="LineNr"> 337 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 3</span>  <span class="Comment"># cursor column</span>
<span id="L338" class="LineNr"> 338 </span>  ]
<span id="L339" class="LineNr"> 339 </span>  check-trace-count-for-label<span class="Constant"> 0</span>, <span class="Constant">[print-character]</span>
<span id="L340" class="LineNr"> 340 </span>]
<span id="L341" class="LineNr"> 341 </span>
<span id="L342" class="LineNr"> 342 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text-2 [
<span id="L343" class="LineNr"> 343 </span>  <span class="Constant">local-scope</span>
<span id="L344" class="LineNr"> 344 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L345" class="LineNr"> 345 </span>  s:text <span class="Special">&lt;-</span> new <span class="Constant">[abc</span>
<span id="L346" class="LineNr"> 346 </span><span class="Constant">def]</span>
<span id="L347" class="LineNr"> 347 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> s, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L348" class="LineNr"> 348 </span>  $clear-trace
<span id="L349" class="LineNr"> 349 </span>  assume-console [
<span id="L350" class="LineNr"> 350 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 7</span>  <span class="Comment"># interior line, to the right of text</span>
<span id="L351" class="LineNr"> 351 </span>  ]
<span id="L352" class="LineNr"> 352 </span>  run [
<span id="L353" class="LineNr"> 353 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L354" class="LineNr"> 354 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L355" class="LineNr"> 355 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L356" class="LineNr"> 356 </span>  ]
<span id="L357" class="LineNr"> 357 </span>  memory-should-contain [
<span id="L358" class="LineNr"> 358 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>  <span class="Comment"># cursor row</span>
<span id="L359" class="LineNr"> 359 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 3</span>  <span class="Comment"># cursor column</span>
<span id="L360" class="LineNr"> 360 </span>  ]
<span id="L361" class="LineNr"> 361 </span>  check-trace-count-for-label<span class="Constant"> 0</span>, <span class="Constant">[print-character]</span>
<span id="L362" class="LineNr"> 362 </span>]
<span id="L363" class="LineNr"> 363 </span>
<span id="L364" class="LineNr"> 364 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-text-3 [
<span id="L365" class="LineNr"> 365 </span>  <span class="Constant">local-scope</span>
<span id="L366" class="LineNr"> 366 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L367" class="LineNr"> 367 </span>  s:text <span class="Special">&lt;-</span> new <span class="Constant">[abc</span>
<span id="L368" class="LineNr"> 368 </span><span class="Constant">def]</span>
<span id="L369" class="LineNr"> 369 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> s, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L370" class="LineNr"> 370 </span>  $clear-trace
<span id="L371" class="LineNr"> 371 </span>  assume-console [
<span id="L372" class="LineNr"> 372 </span>    left-click<span class="Constant"> 3</span>,<span class="Constant"> 7</span>  <span class="Comment"># below text</span>
<span id="L373" class="LineNr"> 373 </span>  ]
<span id="L374" class="LineNr"> 374 </span>  run [
<span id="L375" class="LineNr"> 375 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L376" class="LineNr"> 376 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L377" class="LineNr"> 377 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L378" class="LineNr"> 378 </span>  ]
<span id="L379" class="LineNr"> 379 </span>  memory-should-contain [
<span id="L380" class="LineNr"> 380 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 2</span>  <span class="Comment"># cursor row</span>
<span id="L381" class="LineNr"> 381 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 3</span>  <span class="Comment"># cursor column</span>
<span id="L382" class="LineNr"> 382 </span>  ]
<span id="L383" class="LineNr"> 383 </span>  check-trace-count-for-label<span class="Constant"> 0</span>, <span class="Constant">[print-character]</span>
<span id="L384" class="LineNr"> 384 </span>]
<span id="L385" class="LineNr"> 385 </span>
<span id="L386" class="LineNr"> 386 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks-outside-column [
<span id="L387" class="LineNr"> 387 </span>  <span class="Constant">local-scope</span>
<span id="L388" class="LineNr"> 388 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L389" class="LineNr"> 389 </span>  <span class="Comment"># editor occupies only left half of screen</span>
<span id="L390" class="LineNr"> 390 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
<span id="L391" class="LineNr"> 391 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L392" class="LineNr"> 392 </span>  $clear-trace
<span id="L393" class="LineNr"> 393 </span>  assume-console [
<span id="L394" class="LineNr"> 394 </span>    <span class="Comment"># click on right half of screen</span>
<span id="L395" class="LineNr"> 395 </span>    left-click<span class="Constant"> 3</span>,<span class="Constant"> 8</span>
<span id="L396" class="LineNr"> 396 </span>  ]
<span id="L397" class="LineNr"> 397 </span>  run [
<span id="L398" class="LineNr"> 398 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L399" class="LineNr"> 399 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L400" class="LineNr"> 400 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L401" class="LineNr"> 401 </span>  ]
<span id="L402" class="LineNr"> 402 </span>  screen-should-contain [
<span id="L403" class="LineNr"> 403 </span>   <span class="Constant"> .          .</span>
<span id="L404" class="LineNr"> 404 </span>   <span class="Constant"> .abc       .</span>
<span id="L405" class="LineNr"> 405 </span>   <span class="Constant"> .╌╌╌╌╌     .</span>
<span id="L406" class="LineNr"> 406 </span>   <span class="Constant"> .          .</span>
<span id="L407" class="LineNr"> 407 </span>  ]
<span id="L408" class="LineNr"> 408 </span>  memory-should-contain [
<span id="L409" class="LineNr"> 409 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>  <span class="Comment"># no change to cursor row</span>
<span id="L410" class="LineNr"> 410 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 0</span>  <span class="Comment"># ..or column</span>
<span id="L411" class="LineNr"> 411 </span>  ]
<span id="L412" class="LineNr"> 412 </span>  check-trace-count-for-label<span class="Constant"> 0</span>, <span class="Constant">[print-character]</span>
<span id="L413" class="LineNr"> 413 </span>]
<span id="L414" class="LineNr"> 414 </span>
<span id="L415" class="LineNr"> 415 </span><span class="muScenario">scenario</span> editor-handles-mouse-clicks-in-menu-area [
<span id="L416" class="LineNr"> 416 </span>  <span class="Constant">local-scope</span>
<span id="L417" class="LineNr"> 417 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L418" class="LineNr"> 418 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
<span id="L419" class="LineNr"> 419 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L420" class="LineNr"> 420 </span>  $clear-trace
<span id="L421" class="LineNr"> 421 </span>  assume-console [
<span id="L422" class="LineNr"> 422 </span>    <span class="Comment"># click on first, 'menu' row</span>
<span id="L423" class="LineNr"> 423 </span>    left-click<span class="Constant"> 0</span>,<span class="Constant"> 3</span>
<span id="L424" class="LineNr"> 424 </span>  ]
<span id="L425" class="LineNr"> 425 </span>  run [
<span id="L426" class="LineNr"> 426 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L427" class="LineNr"> 427 </span>    3:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-row:offset</span>
<span id="L428" class="LineNr"> 428 </span>    4:num/<span class="Special">raw</span> <span class="Special">&lt;-</span> get *e, <span class="Constant">cursor-column:offset</span>
<span id="L429" class="LineNr"> 429 </span>  ]
<span id="L430" class="LineNr"> 430 </span>  <span class="Comment"># no change to cursor</span>
<span id="L431" class="LineNr"> 431 </span>  memory-should-contain [
<span id="L432" class="LineNr"> 432 </span>   <span class="Constant"> 3</span> <span class="Special">&lt;-</span><span class="Constant"> 1</span>
<span id="L433" class="LineNr"> 433 </span>   <span class="Constant"> 4</span> <span class="Special">&lt;-</span><span class="Constant"> 0</span>
<span id="L434" class="LineNr"> 434 </span>  ]
<span id="L435" class="LineNr"> 435 </span>]
<span id="L436" class="LineNr"> 436 </span>
<span id="L437" class="LineNr"> 437 </span><span class="muScenario">scenario</span> editor-inserts-characters-into-empty-editor [
<span id="L438" class="LineNr"> 438 </span>  <span class="Constant">local-scope</span>
<span id="L439" class="LineNr"> 439 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L440" class="LineNr"> 440 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[]</span>, <span class="Constant">0/left</span>, <span class="Constant">5/right</span>
<span id="L441" class="LineNr"> 441 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L442" class="LineNr"> 442 </span>  $clear-trace
<span id="L443" class="LineNr"> 443 </span>  assume-console [
<span id="L444" class="LineNr"> 444 </span>    type <span class="Constant">[abc]</span>
<span id="L445" class="LineNr"> 445 </span>  ]
<span id="L446" class="LineNr"> 446 </span>  run [
<span id="L447" class="LineNr"> 447 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L448" class="LineNr"> 448 </span>  ]
<span id="L449" class="LineNr"> 449 </span>  screen-should-contain [
<span id="L450" class="LineNr"> 450 </span>   <span class="Constant"> .          .</span>
<span id="L451" class="LineNr"> 451 </span>   <span class="Constant"> .abc       .</span>
<span id="L452" class="LineNr"> 452 </span>   <span class="Constant"> .╌╌╌╌╌     .</span>
<span id="L453" class="LineNr"> 453 </span>   <span class="Constant"> .          .</span>
<span id="L454" class="LineNr"> 454 </span>  ]
<span id="L455" class="LineNr"> 455 </span>  check-trace-count-for-label<span class="Constant"> 3</span>, <span class="Constant">[print-character]</span>
<span id="L456" class="LineNr"> 456 </span>]
<span id="L457" class="LineNr"> 457 </span>
<span id="L458" class="LineNr"> 458 </span><span class="muScenario">scenario</span> editor-inserts-characters-at-cursor [
<span id="L459" class="LineNr"> 459 </span>  <span class="Constant">local-scope</span>
<span id="L460" class="LineNr"> 460 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L461" class="LineNr"> 461 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L462" class="LineNr"> 462 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L463" class="LineNr"> 463 </span>  $clear-trace
<span id="L464" class="LineNr"> 464 </span>  <span class="Comment"># type two letters at different places</span>
<span id="L465" class="LineNr"> 465 </span>  assume-console [
<span id="L466" class="LineNr"> 466 </span>    type <span class="Constant">[0]</span>
<span id="L467" class="LineNr"> 467 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 2</span>
<span id="L468" class="LineNr"> 468 </span>    type <span class="Constant">[d]</span>
<span id="L469" class="LineNr"> 469 </span>  ]
<span id="L470" class="LineNr"> 470 </span>  run [
<span id="L471" class="LineNr"> 471 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L472" class="LineNr"> 472 </span>  ]
<span id="L473" class="LineNr"> 473 </span>  screen-should-contain [
<span id="L474" class="LineNr"> 474 </span>   <span class="Constant"> .          .</span>
<span id="L475" class="LineNr"> 475 </span>   <span class="Constant"> .0adbc     .</span>
<span id="L476" class="LineNr"> 476 </span><span class="Constant">    .╌╌╌╌╌╌╌╌╌╌.</span>
<span id="L477" class="LineNr"> 477 </span>   <span class="Constant"> .          .</span>
<span id="L478" class="LineNr"> 478 </span>  ]
<span id="L479" class="LineNr"> 479 </span>  check-trace-count-for-label<span class="Constant"> 7</span>, <span class="Constant">[print-character]</span>  <span class="Comment"># 4 for first letter, 3 for second</span>
<span id="L480" class="LineNr"> 480 </span>]
<span id="L481" class="LineNr"> 481 </span>
<span id="L482" class="LineNr"> 482 </span><span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-2 [
<span id="L483" class="LineNr"> 483 </span>  <span class="Constant">local-scope</span>
<span id="L484" class="LineNr"> 484 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L485" class="LineNr"> 485 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> <span class="Constant">[abc]</span>, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L486" class="LineNr"> 486 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L487" class="LineNr"> 487 </span>  $clear-trace
<span id="L488" class="LineNr"> 488 </span>  assume-console [
<span id="L489" class="LineNr"> 489 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 5</span>  <span class="Comment"># right of last line</span>
<span id="L490" class="LineNr"> 490 </span>    type <span class="Constant">[d]</span>
<span id="L491" class="LineNr"> 491 </span>  ]
<span id="L492" class="LineNr"> 492 </span>  run [
<span id="L493" class="LineNr"> 493 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L494" class="LineNr"> 494 </span>  ]
<span id="L495" class="LineNr"> 495 </span>  screen-should-contain [
<span id="L496" class="LineNr"> 496 </span>   <span class="Constant"> .          .</span>
<span id="L497" class="LineNr"> 497 </span>   <span class="Constant"> .abcd      .</span>
<span id="L498" class="LineNr"> 498 </span><span class="Constant">    .╌╌╌╌╌╌╌╌╌╌.</span>
<span id="L499" class="LineNr"> 499 </span>   <span class="Constant"> .          .</span>
<span id="L500" class="LineNr"> 500 </span>  ]
<span id="L501" class="LineNr"> 501 </span>  check-trace-count-for-label<span class="Constant"> 1</span>, <span class="Constant">[print-character]</span>
<span id="L502" class="LineNr"> 502 </span>]
<span id="L503" class="LineNr"> 503 </span>
<span id="L504" class="LineNr"> 504 </span><span class="muScenario">scenario</span> editor-inserts-characters-at-cursor-5 [
<span id="L505" class="LineNr"> 505 </span>  <span class="Constant">local-scope</span>
<span id="L506" class="LineNr"> 506 </span>  assume-screen <span class="Constant">10/width</span>, <span class="Constant">5/height</span>
<span id="L507" class="LineNr"> 507 </span>  s:text <span class="Special">&lt;-</span> new <span class="Constant">[abc</span>
<span id="L508" class="LineNr"> 508 </span><span class="Constant">d]</span>
<span id="L509" class="LineNr"> 509 </span>  e:&amp;:editor <span class="Special">&lt;-</span> <a href='001-editor.mu.html#L51'>new-editor</a> s, <span class="Constant">0/left</span>, <span class="Constant">10/right</span>
<span id="L510" class="LineNr"> 510 </span>  <a href='002-typing.mu.html#L265'>editor-render</a> <a href='../081print.mu.html#L16'>screen</a>, e
<span id="L511" class="LineNr"> 511 </span>  $clear-trace
<span id="L512" class="LineNr"> 512 </span>  assume-console [
<span id="L513" class="LineNr"> 513 </span>    left-click<span class="Constant"> 1</span>,<span class="Constant"> 5</span>  <span class="Comment"># right of non-last line</span>
<span id="L514" class="LineNr"> 514 </span>    type <span class="Constant">[e]</span>
<span id="L515" class="LineNr"> 515 </span>  ]
<span id="L516" class="LineNr"> 516 </span>  run [
<span id="L517" class="LineNr"> 517 </span>    <a href='002-typing.mu.html#L16'>editor-event-loop</a> <a href='../081print.mu.html#L16'>screen</a>, <a href='../084console.mu.html#L23'>console</a>, e
<span id="L518" class="LineNr"> 518 </span>  ]
<span id="L519" class="LineNr"> 519 </span>  screen-should-contain [
<span id="L520" class="LineNr"> 520 </span>   <span class="Constant"> .          .</span>
<span id="L521" class="LineNr"> 521 </span>   <span class="Constant"> .abce      .</span>
<span id="L522" class="LineNr"> 522 </span>   <span class="Constant"> .d         .</span>
<span id="L523" class="LineNr"> 523 </span><span class="Constant">    .╌╌╌╌╌╌╌╌╌╌.</span>
<span id="L524" class="LineNr"> 524 </span>   <span class="Constant"> .          .</span>
<span id="L525" class="LineNr"> 525 </span>  ]
<span id="L526" class="LineNr"> 526 </span>  check-trace-count-for-label<span class="Constant"> 1</span>, <span class="Constant">[print-character]</span>
<span id="L527" class="LineNr"> 527 </span>]
<