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   ¦ <insert-character-begin>
  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   ¦ <insert-character-end>
  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   ¦ <backspace-character-begin>
 100   ¦ go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen
 101   ¦ <backspace-character-end>
 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-ingredients
 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-ingredients
 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-ingredients
 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   ¦ <delete-character-begin>
 367   ¦ go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen
 368   ¦ <delete-character-end>
 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-ingredients
 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   ¦ # newline? done.
 397   ¦ currc:char <- get *curr, value:offset
 398   ¦ at-newline?:bool <- equal currc, 10/newline
 399   ¦ break-if at-newline?
 400   ¦ screen <- print screen, currc
 401   ¦ curr-column <- add curr-column, 1
 402   ¦ curr <- next curr
 403   ¦ loop
 404   }
 405   # we're guaranteed not to be at the right margin
 406   space:char <- copy 32/space
 407   screen <- print screen, space
 408   go-render? <- copy 0/false
 409 ]
 410 
 411 # right arrow
 412 
 413 scenario editor-moves-cursor-right-with-key [
 414   local-scope
 415   assume-screen 10/width, 5/height
 416   e:&:editor <- new-editor [abc], 0/left, 10/right
 417   editor-render screen, e
 418   $clear-trace
 419   assume-console [
 420   ¦ press right-arrow
 421   ¦ type [0]
 422   ]
 423   run [
 424   ¦ editor-event-loop screen, console, e
 425   ]
 426   screen-should-contain [
 427   ¦ .          .
 428   ¦ .a0bc      .
 429   ¦ .╌╌╌╌╌╌╌╌╌╌.
 430   ¦ .          .
 431   ]
 432   check-trace-count-for-label 3, [print-character]  # 0 and following characters
 433 ]
 434 
 435 after <handle-special-key> [
 436   {
 437   ¦ move-to-next-character?:bool <- equal k, 65514/right-arrow
 438   ¦ break-unless move-to-next-character?
 439   ¦ # if not at end of text
 440   ¦ next-cursor:&:duplex-list:char <- next before-cursor
 441   ¦ break-unless next-cursor
 442   ¦ # scan to next character
 443   ¦ <move-cursor-begin>
 444   ¦ before-cursor <- copy next-cursor
 445   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 446   ¦ go-render?:bool <- move-cursor-coordinates-right editor, screen-height
 447   ¦ screen <- move-cursor screen, cursor-row, cursor-column
 448   ¦ undo-coalesce-tag:num <- copy 2/right-arrow
 449   ¦ <move-cursor-end>
 450   ¦ return
 451   }
 452 ]
 453 
 454 def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
 455   local-scope
 456   load-ingredients
 457   before-cursor:&:duplex-list:char <- get *editor before-cursor:offset
 458   cursor-row:num <- get *editor, cursor-row:offset
 459   cursor-column:num <- get *editor, cursor-column:offset
 460   left:num <- get *editor, left:offset
 461   right:num <- get *editor, right:offset
 462   # if crossed a newline, move cursor to start of next row
 463   {
 464   ¦ old-cursor-character:char <- get *before-cursor, value:offset
 465   ¦ was-at-newline?:bool <- equal old-cursor-character, 10/newline
 466   ¦ break-unless was-at-newline?
 467   ¦ cursor-row <- add cursor-row, 1
 468   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 469   ¦ cursor-column <- copy left
 470   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 471   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 472   ¦ return-unless below-screen?, 0/don't-render
 473   ¦ <scroll-down>
 474   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 475   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 476   ¦ return 1/go-render
 477   }
 478   # if the line wraps, move cursor to start of next row
 479   {
 480   ¦ # if we're at the column just before the wrap indicator
 481   ¦ wrap-column:num <- subtract right, 1
 482   ¦ at-wrap?:bool <- equal cursor-column, wrap-column
 483   ¦ break-unless at-wrap?
 484   ¦ # and if next character isn't newline
 485   ¦ next:&:duplex-list:char <- next before-cursor
 486   ¦ break-unless next
 487   ¦ next-character:char <- get *next, value:offset
 488   ¦ newline?:bool <- equal next-character, 10/newline
 489   ¦ break-if newline?
 490   ¦ cursor-row <- add cursor-row, 1
 491   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 492   ¦ cursor-column <- copy left
 493   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 494   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 495   ¦ return-unless below-screen?, 0/no-more-render
 496   ¦ <scroll-down>
 497   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 498   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 499   ¦ return 1/go-render
 500   }
 501   # otherwise move cursor one character right
 502   cursor-column <- add cursor-column, 1
 503   *editor <- put *editor, cursor-column:offset, cursor-column
 504   go-render? <- copy 0/false
 505 ]
 506 
 507 scenario editor-moves-cursor-to-next-line-with-right-arrow [
 508   local-scope
 509   assume-screen 10/width, 5/height
 510   s:text <- new [abc
 511 d]
 512   e:&:editor <- new-editor s, 0/left, 10/right
 513   editor-render screen, e
 514   $clear-trace
 515   # type right-arrow a few times to get to start of second line
 516   assume-console [
 517   ¦ press right-arrow
 518   ¦ press right-arrow
 519   ¦ press right-arrow
 520   ¦ press right-arrow  # next line
 521   ]
 522   run [
 523   ¦ editor-event-loop screen, console, e
 524   ]
 525   check-trace-count-for-label 0, [print-character]
 526   # type something and ensure it goes where it should
 527   assume-console [
 528   ¦ type [0]
 529   ]
 530   run [
 531   ¦ editor-event-loop screen, console, e
 532   ]
 533   screen-should-contain [
 534   ¦ .          .
 535   ¦ .abc       .
 536   ¦ .0d        .
 537   ¦ .╌╌╌╌╌╌╌╌╌╌.
 538   ¦ .          .
 539   ]
 540   check-trace-count-for-label 2, [print-character]  # new length of second line
 541 ]
 542 
 543 scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [
 544   local-scope
 545   assume-screen 10/width, 5/height
 546   s:text <- new [abc
 547 d]
 548   e:&:editor <- new-editor s, 1/left, 10/right
 549   editor-render screen, e
 550   assume-console [
 551   ¦ press right-arrow
 552   ¦ press right-arrow
 553   ¦ press right-arrow
 554   ¦ press right-arrow  # next line
 555   ¦ type [0]
 556   ]
 557   run [
 558   ¦ editor-event-loop screen, console, e
 559   ]
 560   screen-should-contain [
 561   ¦ .          .
 562   ¦ . abc      .
 563   ¦ . 0d       .
 564   ¦ . ╌╌╌╌╌╌╌╌╌.
 565   ¦ .          .
 566   ]
 567 ]
 568 
 569 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
 570   local-scope
 571   assume-screen 10/width, 5/height
 572   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 573   editor-render screen, e
 574   $clear-trace
 575   assume-console [
 576   ¦ left-click 1, 3
 577   ¦ press right-arrow
 578   ]
 579   run [
 580   ¦ editor-event-loop screen, console, e
 581   ¦ 3:num/raw <- get *e, cursor-row:offset
 582   ¦ 4:num/raw <- get *e, cursor-column:offset
 583   ]
 584   screen-should-contain [
 585   ¦ .          .
 586   ¦ .abcd↩     .
 587   ¦ .ef        .
 588   ¦ .╌╌╌╌╌     .
 589   ¦ .          .
 590   ]
 591   memory-should-contain [
 592   ¦ 3 <- 2
 593   ¦ 4 <- 0
 594   ]
 595   check-trace-count-for-label 0, [print-character]
 596 ]
 597 
 598 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
 599   local-scope
 600   assume-screen 10/width, 5/height
 601   # line just barely wrapping
 602   e:&:editor <- new-editor [abcde], 0/left, 5/right
 603   editor-render screen, e
 604   $clear-trace
 605   # position cursor at last character before wrap and hit right-arrow
 606   assume-console [
 607   ¦ left-click 1, 3
 608   ¦ press right-arrow
 609   ]
 610   run [
 611   ¦ editor-event-loop screen, console, e
 612   ¦ 3:num/raw <- get *e, cursor-row:offset
 613   ¦ 4:num/raw <- get *e, cursor-column:offset
 614   ]
 615   memory-should-contain [
 616   ¦ 3 <- 2
 617   ¦ 4 <- 0
 618   ]
 619   # now hit right arrow again
 620   assume-console [
 621   ¦ press right-arrow
 622   ]
 623   run [
 624   ¦ editor-event-loop screen, console, e
 625   ¦ 3:num/raw <- get *e, cursor-row:offset
 626   ¦ 4:num/raw <- get *e, cursor-column:offset
 627   ]
 628   memory-should-contain [
 629   ¦ 3 <- 2
 630   ¦ 4 <- 1
 631   ]
 632   check-trace-count-for-label 0, [print-character]
 633 ]
 634 
 635 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
 636   local-scope
 637   assume-screen 10/width, 5/height
 638   e:&:editor <- new-editor [abcdef], 1/left, 6/right
 639   editor-render screen, e
 640   $clear-trace
 641   assume-console [
 642   ¦ left-click 1, 4
 643   ¦ press right-arrow
 644   ]
 645   run [
 646   ¦ editor-event-loop screen, console, e
 647   ¦ 3:num/raw <- get *e, cursor-row:offset
 648   ¦ 4:num/raw <- get *e, cursor-column:offset
 649   ]
 650   screen-should-contain [
 651   ¦ .          .
 652   ¦ . abcd↩    .
 653   ¦ . ef       .
 654   ¦ . ╌╌╌╌╌    .
 655   ¦ .          .
 656   ]
 657   memory-should-contain [
 658   ¦ 3 <- 2
 659   ¦ 4 <- 1
 660   ]
 661   check-trace-count-for-label 0, [print-character]
 662 ]
 663 
 664 scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
 665   local-scope
 666   assume-screen 10/width, 5/height
 667   s:text <- new [abc
 668 d]
 669   e:&:editor <- new-editor s, 0/left, 10/right
 670   editor-render screen, e
 671   $clear-trace
 672   # move to end of line, press right-arrow, type a character
 673   assume-console [
 674   ¦ left-click 1, 3
 675   ¦ press right-arrow
 676   ¦ type [0]
 677   ]
 678   run [
 679   ¦ editor-event-loop screen, console, e
 680   ]
 681   # new character should be in next line
 682   screen-should-contain [
 683   ¦ .          .
 684   ¦ .abc       .
 685   ¦ .0d        .
 686   ¦ .╌╌╌╌╌╌╌╌╌╌.
 687   ¦ .          .
 688   ]
 689   check-trace-count-for-label 2, [print-character]
 690 ]
 691 
 692 # todo: ctrl-right: next word-end
 693 
 694 # left arrow
 695 
 696 scenario editor-moves-cursor-left-with-key [
 697   local-scope
 698   assume-screen 10/width, 5/height
 699   e:&:editor <- new-editor [abc], 0/left, 10/right
 700   editor-render screen, e
 701   $clear-trace
 702   assume-console [
 703   ¦ left-click 1, 2
 704   ¦ press left-arrow
 705   ¦ type [0]
 706   ]
 707   run [
 708   ¦ editor-event-loop screen, console, e
 709   ]
 710   screen-should-contain [
 711   ¦ .          .
 712   ¦ .a0bc      .
 713   ¦ .╌╌╌╌╌╌╌╌╌╌.
 714   ¦ .          .
 715   ]
 716   check-trace-count-for-label 3, [print-character]
 717 ]
 718 
 719 after <handle-special-key> [
 720   {
 721   ¦ move-to-previous-character?:bool <- equal k, 65515/left-arrow
 722   ¦ break-unless move-to-previous-character?
 723   ¦ trace 10, [app], [left arrow]
 724   ¦ # if not at start of text (before-cursor at § sentinel)
 725   ¦ prev:&:duplex-list:char <- prev before-cursor
 726   ¦ return-unless prev, 0/don't-render
 727   ¦ <move-cursor-begin>
 728   ¦ go-render? <- move-cursor-coordinates-left editor
 729   ¦ before-cursor <- copy prev
 730   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 731   ¦ undo-coalesce-tag:num <- copy 1/left-arrow
 732   ¦ <move-cursor-end>
 733   ¦ return
 734   }
 735 ]
 736 
 737 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
 738   local-scope
 739   assume-screen 10/width, 5/height
 740   # initialize editor with two lines
 741   s:text <- new [abc
 742 d]
 743   e:&:editor <- new-editor s, 0/left, 10/right
 744   editor-render screen, e
 745   $clear-trace
 746   # position cursor at start of second line (so there's no previous newline)
 747   assume-console [
 748   ¦ left-click 2, 0
 749   ¦ press left-arrow
 750   ]
 751   run [
 752   ¦ editor-event-loop screen, console, e
 753   ¦ 3:num/raw <- get *e, cursor-row:offset
 754   ¦ 4:num/raw <- get *e, cursor-column:offset
 755   ]
 756   memory-should-contain [
 757   ¦ 3 <- 1
 758   ¦ 4 <- 3
 759   ]
 760   check-trace-count-for-label 0, [print-character]
 761 ]
 762 
 763 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
 764   local-scope
 765   assume-screen 10/width, 5/height
 766   # initialize editor with three lines
 767   s:text <- new [abc
 768 def
 769 g]
 770   e:&:editor <- new-editor s:text, 0/left, 10/right
 771   editor-render screen, e
 772   $clear-trace
 773   # position cursor further down (so there's a newline before the character at
 774   # the cursor)
 775   assume-console [
 776   ¦ left-click 3, 0
 777   ¦ press left-arrow
 778   ¦ type [0]
 779   ]
 780   run [
 781   ¦ editor-event-loop screen, console, e
 782   ]
 783   screen-should-contain [
 784   ¦ .          .
 785   ¦ .abc       .
 786   ¦ .def0      .
 787   ¦ .g         .
 788   ¦ .╌╌╌╌╌╌╌╌╌╌.
 789   ]
 790   check-trace-count-for-label 1, [print-character]  # just the '0'
 791 ]
 792 
 793 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
 794   local-scope
 795   assume-screen 10/width, 5/height
 796   s:text <- new [abc
 797 def
 798 g]
 799   e:&:editor <- new-editor s, 0/left, 10/right
 800   editor-render screen, e
 801   $clear-trace
 802   # position cursor at start of text, press left-arrow, then type a character
 803   assume-console [
 804   ¦ left-click 1, 0
 805   ¦ press left-arrow
 806   ¦ type [0]
 807   ]
 808   run [
 809   ¦ editor-event-loop screen, console, e
 810   ]
 811   # left-arrow should have had no effect
 812   screen-should-contain [
 813   ¦ .          .
 814   ¦ .0abc      .
 815   ¦ .def       .
 816   ¦ .g         .
 817   ¦ .╌╌╌╌╌╌╌╌╌╌.
 818   ]
 819   check-trace-count-for-label 4, [print-character]  # length of first line
 820 ]
 821 
 822 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
 823   local-scope
 824   assume-screen 10/width, 5/height
 825   # initialize editor with text containing an empty line
 826   s:text <- new [abc
 827 
 828 d]
 829   e:&:editor <- new-editor s, 0/left, 10/right
 830   editor-render screen, e:&:editor
 831   $clear-trace
 832   # position cursor right after empty line
 833   assume-console [
 834   ¦ left-click 3, 0
 835   ¦ press left-arrow
 836   ¦ type [0]
 837   ]
 838   run [
 839   ¦ editor-event-loop screen, console, e
 840   ]
 841   screen-should-contain [
 842   ¦ .          .
 843   ¦ .abc       .
 844   ¦ .0         .
 845   ¦ .d         .
 846   ¦ .╌╌╌╌╌╌╌╌╌╌.
 847   ]
 848   check-trace-count-for-label 1, [print-character]  # just the '0'
 849 ]
 850 
 851 scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
 852   local-scope
 853   assume-screen 10/width, 5/height
 854   # initialize editor with a wrapping line
 855   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 856   editor-render screen, e
 857   $clear-trace
 858   screen-should-contain [
 859   ¦ .          .
 860   ¦ .abcd↩     .
 861   ¦ .ef        .
 862   ¦ .╌╌╌╌╌     .
 863   ¦ .          .
 864   ]
 865   # position cursor right after empty line
 866   assume-console [
 867   ¦ left-click 2, 0
 868   ¦ press left-arrow
 869   ]
 870   run [
 871   ¦ editor-event-loop screen, console, e
 872   ¦ 3:num/raw <- get *e, cursor-row:offset
 873   ¦ 4:num/raw <- get *e, cursor-column:offset
 874   ]
 875   memory-should-contain [
 876   ¦ 3 <- 1  # previous row
 877   ¦ 4 <- 3  # right margin except wrap icon
 878   ]
 879   check-trace-count-for-label 0, [print-character]
 880 ]
 881 
 882 scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [
 883   local-scope
 884   assume-screen 10/width, 5/height
 885   # initialize editor with a wrapping line followed by a second line
 886   s:text <- new [abcdef
 887 g]
 888   e:&:editor <- new-editor s, 0/left, 5/right
 889   editor-render screen, e
 890   $clear-trace
 891   screen-should-contain [
 892   ¦ .          .
 893   ¦ .abcd↩     .
 894   ¦ .ef        .
 895   ¦ .g         .
 896   ¦ .╌╌╌╌╌     .
 897   ]
 898   # position cursor right after empty line
 899   assume-console [
 900   ¦ left-click 3, 0
 901   ¦ press left-arrow
 902   ]
 903   run [
 904   ¦ editor-event-loop screen, console, e
 905   ¦ 3:num/raw <- get *e, cursor-row:offset
 906   ¦ 4:num/raw <- get *e, cursor-column:offset
 907   ]
 908   memory-should-contain [
 909   ¦ 3 <- 2  # previous row
 910   ¦ 4 <- 2  # end of wrapped line
 911   ]
 912   check-trace-count-for-label 0, [print-character]
 913 ]
 914 
 915 scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [
 916   local-scope
 917   assume-screen 10/width, 5/height
 918   # initialize editor with a line on the verge of wrapping, followed by a second line
 919   s:text <- new [abcd
 920 e]
 921   e:&:editor <- new-editor s, 0/left, 5/right
 922   editor-render screen, e
 923   $clear-trace
 924   screen-should-contain [
 925   ¦ .          .
 926   ¦ .abcd      .
 927   ¦ .e         .
 928   ¦ .╌╌╌╌╌     .
 929   ¦ .          .
 930   ]
 931   # position cursor right after empty line
 932   assume-console [
 933   ¦ left-click 2, 0
 934   ¦ press left-arrow
 935   ]
 936   run [
 937   ¦ editor-event-loop screen, console, e
 938   ¦ 3:num/raw <- get *e, cursor-row:offset
 939   ¦ 4:num/raw <- get *e, cursor-column:offset
 940   ]
 941   memory-should-contain [
 942   ¦ 3 <- 1  # previous row
 943   ¦ 4 <- 4  # end of wrapped line
 944   ]
 945   check-trace-count-for-label 0, [print-character]
 946 ]
 947 
 948 # todo: ctrl-left: previous word-start
 949 
 950 # up arrow
 951 
 952 scenario editor-moves-to-previous-line-with-up-arrow [
 953   local-scope
 954   assume-screen 10/width, 5/height
 955   s:text <- new [abc
 956 def]
 957   e:&:editor <- new-editor s, 0/left, 10/right
 958   editor-render screen, e
 959   $clear-trace
 960   assume-console [
 961   ¦ left-click 2, 1
 962   ¦ press up-arrow
 963   ]
 964   run [
 965   ¦ editor-event-loop screen, console, e
 966   ¦ 3:num/raw <- get *e, cursor-row:offset
 967   ¦ 4:num/raw <- get *e, cursor-column:offset
 968   ]
 969   memory-should-contain [
 970   ¦ 3 <- 1
 971   ¦ 4 <- 1
 972   ]
 973   check-trace-count-for-label 0, [print-character]
 974   assume-console [
 975   ¦ type [0]
 976   ]
 977   run [
 978   ¦ editor-event-loop screen, console, e
 979   ]
 980   screen-should-contain [
 981   ¦ .          .
 982   ¦ .a0bc      .
 983   ¦ .def       .
 984   ¦ .╌╌╌╌╌╌╌╌╌╌.
 985   ¦ .          .
 986   ]
 987 ]
 988 
 989 after <handle-special-key> [
 990   {
 991   ¦ move-to-previous-line?:bool <- equal k, 65517/up-arrow
 992   ¦ break-unless move-to-previous-line?
 993   ¦ <move-cursor-begin>
 994   ¦ go-render? <- move-to-previous-line editor
 995   ¦ undo-coalesce-tag:num <- copy 3/up-arrow
 996   ¦ <move-cursor-end>
 997   ¦ return
 998   }
 999 ]
1000 
1001 def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [
1002   local-scope
1003   load-ingredients
1004   go-render?:bool <- copy 0/false
1005   cursor-row:num <- get *editor, cursor-row:offset
1006   cursor-column:num <- get *editor, cursor-column:offset
1007   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1008   left:num <- get *editor, left:offset
1009   right:num <- get *editor, right:offset
1010   already-at-top?:bool <- lesser-or-equal cursor-row, 1/top
1011   {
1012   ¦ # if cursor not at top, move it
1013   ¦ break-if already-at-top?
1014   ¦ # if not at start of screen line, move to start of screen line (previous newline)
1015   ¦ # then scan back another line
1016   ¦ # if either step fails, give up without modifying cursor or coordinates
1017   ¦ curr:&:duplex-list:char <- copy before-cursor
1018   ¦ old:&:duplex-list:char <- copy curr
1019   ¦ {
1020   ¦ ¦ break-unless cursor-column
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 scenario editor-adjusts-column-at-previous-line [
1064   local-scope
1065   assume-screen 10/width, 5/height
1066   s:text <- new [ab
1067 def]
1068   e:&:editor <- new-editor s, 0/left, 10/right
1069   editor-render screen, e
1070   $clear-trace
1071   assume-console [
1072   ¦ left-click 2, 3
1073   ¦ press up-arrow
1074   ]
1075   run [
1076   ¦ editor-event-loop screen, console, e
1077   ¦ 3:num/raw <- get *e, cursor-row:offset
1078   ¦ 4:num/raw <- get *e, cursor-column:offset
1079   ]
1080   memory-should-contain [
1081   ¦ 3 <- 1
1082   ¦ 4 <- 2
1083   ]
1084   check-trace-count-for-label 0, [print-character]
1085   assume-console [
1086   ¦ type [0]
1087   ]
1088   run [
1089   ¦ editor-event-loop screen, console, e
1090   ]
1091   screen-should-contain [
1092   ¦ .          .
1093   ¦ .ab0       .
1094   ¦ .def       .
1095   ¦ .╌╌╌╌╌╌╌╌╌╌.
1096   ¦ .          .
1097   ]
1098 ]
1099 
1100 scenario editor-adjusts-column-at-empty-line [
1101   local-scope
1102   assume-screen 10/width, 5/height
1103   s:text <- new [
1104 def]
1105   e:&:editor <- new-editor s, 0/left, 10/right
1106   editor-render screen, e
1107   $clear-trace
1108   assume-console [
1109   ¦ left-click 2, 3
1110   ¦ press up-arrow
1111   ]
1112   run [
1113   ¦ editor-event-loop screen, console, e
1114   ¦ 3:num/raw <- get *e, cursor-row:offset
1115   ¦ 4:num/raw <- get *e, cursor-column:offset
1116   ]
1117   memory-should-contain [
1118   ¦ 3 <- 1
1119   ¦ 4 <- 0
1120   ]
1121   check-trace-count-for-label 0, [print-character]
1122   assume-console [
1123   ¦ type [0]
1124   ]
1125   run [
1126   ¦ editor-event-loop screen, console, e
1127   ]
1128   screen-should-contain [
1129   ¦ .          .
1130   ¦ .0         .
1131   ¦ .def       .
1132   ¦ .╌╌╌╌╌╌╌╌╌╌.
1133   ¦ .          .
1134   ]
1135 ]
1136 
1137 scenario editor-moves-to-previous-line-from-left-margin [
1138   local-scope
1139   assume-screen 10/width, 5/height
1140   # start out with three lines
1141   s:text <- new [abc
1142 def
1143 ghi]
1144   e:&:editor <- new-editor s, 0/left, 10/right
1145   editor-render screen, e
1146   $clear-trace
1147   # click on the third line and hit up-arrow, so you end up just after a newline
1148   assume-console [
1149   ¦ left-click 3, 0
1150   ¦ press up-arrow
1151   ]
1152   run [
1153   ¦ editor-event-loop screen, console, e
1154   ¦ 3:num/raw <- get *e, cursor-row:offset
1155   ¦ 4:num/raw <- get *e, cursor-column:offset
1156   ]
1157   memory-should-contain [
1158   ¦ 3 <- 2
1159   ¦ 4 <- 0
1160   ]
1161   check-trace-count-for-label 0, [print-character]
1162   assume-console [
1163   ¦ type [0]
1164   ]
1165   run [
1166   ¦ editor-event-loop screen, console, e
1167   ]
1168   screen-should-contain [
1169   ¦ .          .
1170   ¦ .abc       .
1171   ¦ .0def      .
1172   ¦ .ghi       .
1173   ¦ .╌╌╌╌╌╌╌╌╌╌.
1174   ]
1175 ]
1176 
1177 scenario editor-moves-to-top-line-in-presence-of-wrapped-line [
1178   local-scope
1179   assume-screen 10/width, 5/height
1180   e:&:editor <- new-editor [abcde], 0/left, 5/right
1181   editor-render screen, e
1182   screen-should-contain [
1183   ¦ .          .
1184   ¦ .abcd↩     .
1185   ¦ .e         .
1186   ¦ .╌╌╌╌╌     .
1187   ]
1188   $clear-trace
1189   assume-console [
1190   ¦ left-click 2, 0
1191   ¦ press up-arrow
1192   ]
1193   run [
1194   ¦ editor-event-loop screen, console, e
1195   ¦ 3:num/raw <- get *e, cursor-row:offset
1196   ¦ 4:num/raw <- get *e, cursor-column:offset
1197   ]
1198   memory-should-contain [
1199   ¦ 3 <- 1
1200   ¦ 4 <- 0
1201   ]
1202   check-trace-count-for-label 0, [print-character]
1203   assume-console [
1204   ¦ type [0]
1205   ]
1206   run [
1207   ¦ editor-event-loop screen, console, e
1208   ]
1209   screen-should-contain [
1210   ¦ .          .
1211   ¦ .0abc↩     .
1212   ¦ .de        .
1213   ¦ .╌╌╌╌╌     .
1214   ]
1215 ]
1216 
1217 scenario editor-moves-to-top-line-in-presence-of-wrapped-line-2 [
1218   local-scope
1219   assume-screen 10/width, 5/height
1220   s:text <- new [abc
1221 defgh]
1222   e:&:editor <- new-editor s, 0/left, 5/right
1223   editor-render screen, e
1224   screen-should-contain [
1225   ¦ .          .
1226   ¦ .abc       .
1227   ¦ .defg↩     .
1228   ¦ .h         .
1229   ¦ .╌╌╌╌╌     .
1230   ]
1231   $clear-trace
1232   assume-console [
1233   ¦ left-click 3, 0
1234   ¦ press up-arrow
1235   ¦ press up-arrow
1236   ]
1237   run [
1238   ¦ editor-event-loop screen, console, e
1239   ¦ 3:num/raw <- get *e, cursor-row:offset
1240   ¦ 4:num/raw <- get *e, cursor-column:offset
1241   ]
1242   memory-should-contain [
1243   ¦ 3 <- 1
1244   ¦ 4 <- 0
1245   ]
1246   check-trace-count-for-label 0, [print-character]
1247   assume-console [
1248   ¦ type [0]
1249   ]
1250   run [
1251   ¦ editor-event-loop screen, console, e
1252   ]
1253   screen-should-contain [
1254   ¦ .          .
1255   ¦ .0abc      .
1256   ¦ .defg↩     .
1257   ¦ .h         .
1258   ¦ .╌╌╌╌╌     .
1259   ]
1260 ]
1261 
1262 # down arrow
1263 
1264 scenario editor-moves-to-next-line-with-down-arrow [
1265   local-scope
1266   assume-screen 10/width, 5/height
1267   s:text <- new [abc
1268 def]
1269   e:&:editor <- new-editor s, 0/left, 10/right
1270   editor-render screen, e
1271   $clear-trace
1272   # cursor starts out at (1, 0)
1273   assume-console [
1274   ¦ press down-arrow
1275   ]
1276   run [
1277   ¦ editor-event-loop screen, console, e
1278   ¦ 3:num/raw <- get *e, cursor-row:offset
1279   ¦ 4:num/raw <- get *e, cursor-column:offset
1280   ]
1281   # ..and ends at (2, 0)
1282   memory-should-contain [
1283   ¦ 3 <- 2
1284   ¦ 4 <- 0
1285   ]
1286   check-trace-count-for-label 0, [print-character]
1287   assume-console [
1288   ¦ type [0]
1289   ]
1290   run [
1291   ¦ editor-event-loop screen, console, e
1292   ]
1293   screen-should-contain [
1294   ¦ .          .
1295   ¦ .abc       .
1296   ¦ .0def      .
1297   ¦ .╌╌╌╌╌╌╌╌╌╌.
1298   ¦ .          .
1299   ]
1300 ]
1301 
1302 after <handle-special-key> [
1303   {
1304   ¦ move-to-next-line?:bool <- equal k, 65516/down-arrow
1305   ¦ break-unless move-to-next-line?
1306   ¦ <move-cursor-begin>
1307   ¦ go-render? <- move-to-next-line editor, screen-height
1308   ¦ undo-coalesce-tag:num <- copy 4/down-arrow
1309   ¦ <move-cursor-end>
1310   ¦ return
1311   }
1312 ]
1313 
1314 def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
1315   local-scope
1316   load-ingredients
1317   cursor-row:num <- get *editor, cursor-row:offset
1318   cursor-column:num <- get *editor, cursor-column:offset
1319   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1320   left:num <- get *editor, left:offset
1321   right:num <- get *editor, right:offset
1322   last-line:num <- subtract screen-height, 1
1323   already-at-bottom?:bool <- greater-or-equal cursor-row, last-line
1324   {
1325   ¦ # if cursor not at bottom, move it
1326   ¦ break-if already-at-bottom?
1327   ¦ # scan to start of next line, then to right column or until end of line
1328   ¦ max:num <- subtract right, left
1329   ¦ next-line:&:duplex-list:char <- before-start-of-next-line before-cursor, max
1330   ¦ {
1331   ¦ ¦ # already at end of buffer? try to scroll up (so we can see more
1332   ¦ ¦ # warnings or sandboxes below)
1333   ¦ ¦ no-motion?:bool <- equal next-line, before-cursor
1334   ¦ ¦ break-unless no-motion?
1335   ¦ ¦ scroll?:bool <- greater-than cursor-row, 1
1336   ¦ ¦ break-if scroll?, +try-to-scroll
1337   ¦ ¦ return 0/don't-render
1338   ¦ }
1339   ¦ cursor-row <- add cursor-row, 1
1340   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
1341   ¦ before-cursor <- copy next-line
1342   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1343   ¦ target-column:num <- copy cursor-column
1344   ¦ cursor-column <- copy left
1345   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1346   ¦ {
1347   ¦ ¦ done?:bool <- greater-or-equal cursor-column, target-column
1348   ¦ ¦ break-if done?
1349   ¦ ¦ curr:&:duplex-list:char <- next before-cursor
1350   ¦ ¦ break-unless curr
1351   ¦ ¦ currc:char <- get *curr, value:offset
1352   ¦ ¦ at-newline?:bool <- equal currc, 10/newline
1353   ¦ ¦ break-if at-newline?
1354   ¦ ¦ #
1355   ¦ ¦ before-cursor <- copy curr
1356   ¦ ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1357   ¦ ¦ cursor-column <- add cursor-column, 1
1358   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1359   ¦ ¦ loop
1360   ¦ }
1361   ¦ return 0/don't-render
1362   }
1363   +try-to-scroll
1364   <scroll-down>
1365   go-render? <- copy 1/true
1366 ]
1367 
1368 scenario editor-adjusts-column-at-next-line [
1369   local-scope
1370   assume-screen 10/width, 5/height
1371   s:text <- new [abc
1372 de]
1373   e:&:editor <- new-editor s, 0/left, 10/right
1374   editor-render screen, e
1375   $clear-trace
1376   assume-console [
1377   ¦ left-click 1, 3
1378   ¦ press down-arrow
1379   ]
1380   run [
1381   ¦ editor-event-loop screen, console, e
1382   ¦ 3:num/raw <- get *e, cursor-row:offset
1383   ¦ 4:num/raw <- get *e, cursor-column:offset
1384   ]
1385   memory-should-contain [
1386   ¦ 3 <- 2
1387   ¦ 4 <- 2
1388   ]
1389   check-trace-count-for-label 0, [print-character]
1390   assume-console [
1391   ¦ type [0]
1392   ]
1393   run [
1394   ¦ editor-event-loop screen, console, e
1395   ]
1396   screen-should-contain [
1397   ¦ .          .
1398   ¦ .abc       .
1399   ¦ .de0       .
1400   ¦ .╌╌╌╌╌╌╌╌╌╌.
1401   ¦ .          .
1402   ]
1403 ]
1404 
1405 # ctrl-a/home - move cursor to start of line
1406 
1407 scenario editor-moves-to-start-of-line-with-ctrl-a [
1408   local-scope
1409   assume-screen 10/width, 5/height
1410   s:text <- new [123
1411 456]
1412   e:&:editor <- new-editor s, 0/left, 10/right
1413   editor-render screen, e
1414   $clear-trace
1415   # start on second line, press ctrl-a
1416   assume-console [
1417   ¦ left-click 2, 3
1418   ¦ press ctrl-a
1419   ]
1420   run [
1421   ¦ editor-event-loop screen, console, e
1422   ¦ 4:num/raw <- get *e, cursor-row:offset
1423   ¦ 5:num/raw <- get *e, cursor-column:offset
1424   ]
1425   # cursor moves to start of line
1426   memory-should-contain [
1427   ¦ 4 <- 2
1428   ¦ 5 <- 0
1429   ]
1430   check-trace-count-for-label 0, [print-character]
1431 ]
1432 
1433 after <handle-special-character> [
1434   {
1435   ¦ move-to-start-of-line?:bool <- equal c, 1/ctrl-a
1436   ¦ break-unless move-to-start-of-line?
1437   ¦ <move-cursor-begin>
1438   ¦ move-to-start-of-screen-line editor
1439   ¦ undo-coalesce-tag:num <- copy 0/never
1440   ¦ <move-cursor-end>
1441   ¦ return 0/don't-render
1442   }
1443 ]
1444 
1445 after <handle-special-key> [
1446   {
1447   ¦ move-to-start-of-line?:bool <- equal k, 65521/home
1448   ¦ break-unless move-to-start-of-line?
1449   ¦ <move-cursor-begin>
1450   ¦ move-to-start-of-screen-line editor
1451   ¦ undo-coalesce-tag:num <- copy 0/never
1452   ¦ <move-cursor-end>
1453   ¦ return 0/don't-render
1454   }
1455 ]
1456 
1457 # handles wrapped lines
1458 # precondition: cursor-column should be in a consistent state
1459 def move-to-start-of-screen-line editor:&:editor -> editor:&:editor [
1460   local-scope
1461   load-ingredients
1462   # update cursor column
1463   left:num <- get *editor, left:offset
1464   col:num <- get *editor, cursor-column:offset
1465   # update before-cursor
1466   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1467   # while not at start of line, move
1468   {
1469   ¦ done?:bool <- equal col, left
1470   ¦ break-if done?
1471   ¦ assert curr, [move-to-start-of-line tried to move before start of text]
1472   ¦ curr <- prev curr
1473   ¦ col <- subtract col, 1
1474   ¦ loop
1475   }
1476   *editor <- put *editor, cursor-column:offset, col
1477   *editor <- put *editor, before-cursor:offset, curr
1478 ]
1479 
1480 scenario editor-moves-to-start-of-line-with-ctrl-a-2 [
1481   local-scope
1482   assume-screen 10/width, 5/height
1483   s:text <- new [123
1484 456]
1485   e:&:editor <- new-editor s, 0/left, 10/right
1486   editor-render screen, e
1487   $clear-trace
1488   # start on first line (no newline before), press ctrl-a
1489   assume-console [
1490   ¦ left-click 1, 3
1491   ¦ press ctrl-a
1492   ]
1493   run [
1494   ¦ editor-event-loop screen, console, e
1495   ¦ 4:num/raw <- get *e, cursor-row:offset
1496   ¦ 5:num/raw <- get *e, cursor-column:offset
1497   ]
1498   # cursor moves to start of line
1499   memory-should-contain [
1500   ¦ 4 <- 1
1501   ¦ 5 <- 0
1502   ]
1503   check-trace-count-for-label 0, [print-character]
1504 ]
1505 
1506 scenario editor-moves-to-start-of-line-with-home [
1507   local-scope
1508   assume-screen 10/width, 5/height
1509   s:text <- new [123
1510 456]
1511   e:&:editor <- new-editor s, 0/left, 10/right
1512   $clear-trace
1513   # start on second line, press 'home'
1514   assume-console [
1515   ¦ left-click 2, 3
1516   ¦ press home
1517   ]
1518   run [
1519   ¦ editor-event-loop screen, console, e
1520   ¦ 3:num/raw <- get *e, cursor-row:offset
1521   ¦ 4:num/raw <- get *e, cursor-column:offset
1522   ]
1523   # cursor moves to start of line
1524   memory-should-contain [
1525   ¦ 3 <- 2
1526   ¦ 4 <- 0
1527   ]
1528   check-trace-count-for-label 0, [print-character]
1529 ]
1530 
1531 scenario editor-moves-to-start-of-line-with-home-2 [
1532   local-scope
1533   assume-screen 10/width, 5/height
1534   s:text <- new [123
1535 456]
1536   e:&:editor <- new-editor s, 0/left, 10/right
1537   editor-render screen, e
1538   $clear-trace
1539   # start on first line (no newline before), press 'home'
1540   assume-console [
1541   ¦ left-click 1, 3
1542   ¦ press home
1543   ]
1544   run [
1545   ¦ editor-event-loop screen, console, e
1546   ¦ 3:num/raw <- get *e, cursor-row:offset
1547   ¦ 4:num/raw <- get *e, cursor-column:offset
1548   ]
1549   # cursor moves to start of line
1550   memory-should-contain [
1551   ¦ 3 <- 1
1552   ¦ 4 <- 0
1553   ]
1554   check-trace-count-for-label 0, [print-character]
1555 ]
1556 
1557 scenario editor-moves-to-start-of-screen-line-with-ctrl-a [
1558   local-scope
1559   assume-screen 10/width, 5/height
1560   e:&:editor <- new-editor [123456], 0/left, 5/right
1561   editor-render screen, e
1562   screen-should-contain [
1563   ¦ .          .
1564   ¦ .1234↩     .
1565   ¦ .56        .
1566   ¦ .╌╌╌╌╌     .
1567   ¦ .          .
1568   ]
1569   $clear-trace
1570   # start on second line, press ctrl-a then up
1571   assume-console [
1572   ¦ left-click 2, 1
1573   ¦ press ctrl-a
1574   ¦ press up-arrow
1575   ]
1576   run [
1577   ¦ editor-event-loop screen, console, e
1578   ¦ 4:num/raw <- get *e, cursor-row:offset
1579   ¦ 5:num/raw <- get *e, cursor-column:offset
1580   ]
1581   # cursor moves to start of first line
1582   memory-should-contain [
1583   ¦ 4 <- 1  # cursor-row
1584   ¦ 5 <- 0  # cursor-column
1585   ]
1586   check-trace-count-for-label 0, [print-character]
1587   # make sure before-cursor is in sync
1588   assume-console [
1589   ¦ type [a]
1590   ]
1591   run [
1592   ¦ editor-event-loop screen, console, e
1593   ¦ 4:num/raw <- get *e, cursor-row:offset
1594   ¦ 5:num/raw <- get *e, cursor-column:offset
1595   ]
1596   screen-should-contain [
1597   ¦ .          .
1598   ¦ .a123↩     .
1599   ¦ .456       .
1600   ¦ .╌╌╌╌╌     .
1601   ¦ .          .
1602   ]
1603   memory-should-contain [
1604   ¦ 4 <- 1  # cursor-row
1605   ¦ 5 <- 1  # cursor-column
1606   ]
1607 ]
1608 
1609 # ctrl-e/end - move cursor to end of line
1610 
1611 scenario editor-moves-to-end-of-line-with-ctrl-e [
1612   local-scope
1613   assume-screen 10/width, 5/height
1614   s:text <- new [123
1615 456]
1616   e:&:editor <- new-editor s, 0/left, 10/right
1617   editor-render screen, e
1618   $clear-trace
1619   # start on first line, press ctrl-e
1620   assume-console [
1621   ¦ left-click 1, 1
1622   ¦ press ctrl-e
1623   ]
1624   run [
1625   ¦ editor-event-loop screen, console, e
1626   ¦ 4:num/raw <- get *e, cursor-row:offset
1627   ¦ 5:num/raw <- get *e, cursor-column:offset
1628   ]
1629   # cursor moves to end of line
1630   memory-should-contain [
1631   ¦ 4 <- 1
1632   ¦ 5 <- 3
1633   ]
1634   check-trace-count-for-label 0, [print-character]
1635   # editor inserts future characters at cursor
1636   assume-console [
1637   ¦ type [z]
1638   ]
1639   run [
1640   ¦ editor-event-loop screen, console, e
1641   ¦ 4:num/raw <- get *e, cursor-row:offset
1642   ¦ 5:num/raw <- get *e, cursor-column:offset
1643   ]
1644   memory-should-contain [
1645   ¦ 4 <- 1
1646   ¦ 5 <- 4
1647   ]
1648   screen-should-contain [
1649   ¦ .          .
1650   ¦ .123z      .
1651   ¦ .456       .
1652   ¦ .╌╌╌╌╌╌╌╌╌╌.
1653   ¦ .          .
1654   ]
1655   check-trace-count-for-label 1, [print-character]
1656 ]
1657 
1658 after <handle-special-character> [
1659   {
1660   ¦ move-to-end-of-line?:bool <- equal c, 5/ctrl-e
1661   ¦ break-unless move-to-end-of-line?
1662   ¦ <move-cursor-begin>
1663   ¦ move-to-end-of-line editor
1664   ¦ undo-coalesce-tag:num <- copy 0/never
1665   ¦ <move-cursor-end>
1666   ¦ return 0/don't-render
1667   }
1668 ]
1669 
1670 after <handle-special-key> [
1671   {
1672   ¦ move-to-end-of-line?:bool <- equal k, 65520/end
1673   ¦ break-unless move-to-end-of-line?
1674   ¦ <move-cursor-begin>
1675   ¦ move-to-end-of-line editor
1676   ¦ undo-coalesce-tag:num <- copy 0/never
1677   ¦ <move-cursor-end>
1678   ¦ return 0/don't-render
1679   }
1680 ]
1681 
1682 def move-to-end-of-line editor:&:editor -> editor:&:editor [
1683   local-scope
1684   load-ingredients
1685   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1686   cursor-column:num <- get *editor, cursor-column:offset
1687   right:num <- get *editor, right:offset
1688   # while not at end of line, move
1689   {
1690   ¦ next:&:duplex-list:char <- next before-cursor
1691   ¦ break-unless next  # end of text
1692   ¦ nextc:char <- get *next, value:offset
1693   ¦ at-end-of-line?:bool <- equal nextc, 10/newline
1694   ¦ break-if at-end-of-line?
1695   ¦ cursor-column <- add cursor-column, 1
1696   ¦ at-right?:bool <- equal cursor-column, right
1697   ¦ break-if at-right?
1698   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1699   ¦ before-cursor <- copy next
1700   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1701   ¦ loop
1702   }
1703 ]
1704 
1705 scenario editor-moves-to-end-of-line-with-ctrl-e-2 [
1706   local-scope
1707   assume-screen 10/width, 5/height
1708   s:text <- new [123
1709 456]
1710   e:&:editor <- new-editor s, 0/left, 10/right
1711   editor-render screen, e
1712   $clear-trace
1713   # start on second line (no newline after), press ctrl-e
1714   assume-console [
1715   ¦ left-click 2, 1
1716   ¦ press ctrl-e
1717   ]
1718   run [
1719   ¦ editor-event-loop screen, console, e
1720   ¦ 4:num/raw <- get *e, cursor-row:offset
1721   ¦ 5:num/raw <- get *e, cursor-column:offset
1722   ]
1723   # cursor moves to end of line
1724   memory-should-contain [
1725   ¦ 4 <- 2
1726   ¦ 5 <- 3
1727   ]
1728   check-trace-count-for-label 0, [print-character]
1729 ]
1730 
1731 scenario editor-moves-to-end-of-line-with-end [
1732   local-scope
1733   assume-screen 10/width, 5/height
1734   s:text <- new [123
1735 456]
1736   e:&:editor <- new-editor s, 0/left, 10/right
1737   editor-render screen, e
1738   $clear-trace
1739   # start on first line, press 'end'
1740   assume-console [
1741   ¦ left-click 1, 1
1742   ¦ press end
1743   ]
1744   run [
1745   ¦ editor-event-loop screen, console, e
1746   ¦ 3:num/raw <- get *e, cursor-row:offset
1747   ¦ 4:num/raw <- get *e, cursor-column:offset
1748   ]
1749   # cursor moves to end of line
1750   memory-should-contain [
1751   ¦ 3 <- 1
1752   ¦ 4 <- 3
1753   ]
1754   check-trace-count-for-label 0, [print-character]
1755 ]
1756 
1757 scenario editor-moves-to-end-of-line-with-end-2 [
1758   local-scope
1759   assume-screen 10/width, 5/height
1760   s:text <- new [123
1761 456]
1762   e:&:editor <- new-editor s, 0/left, 10/right
1763   editor-render screen, e
1764   $clear-trace
1765   # start on second line (no newline after), press 'end'
1766   assume-console [
1767   ¦ left-click 2, 1
1768   ¦ press end
1769   ]
1770   run [
1771   ¦ editor-event-loop screen, console, e
1772   ¦ 3:num/raw <- get *e, cursor-row:offset
1773   ¦ 4:num/raw <- get *e, cursor-column:offset
1774   ]
1775   # cursor moves to end of line
1776   memory-should-contain [
1777   ¦ 3 <- 2
1778   ¦ 4 <- 3
1779   ]
1780   check-trace-count-for-label 0, [print-character]
1781 ]
1782 
1783 scenario editor-moves-to-end-of-wrapped-line [
1784   local-scope
1785   assume-screen 10/width, 5/height
1786   s:text <- new [123456
1787 789]
1788   e:&:editor <- new-editor s, 0/left, 5/right
1789   editor-render screen, e
1790   $clear-trace
1791   # start on first line, press 'end'
1792   assume-console [
1793   ¦ left-click 1, 1
1794   ¦ press end
1795   ]
1796   run [
1797   ¦ editor-event-loop screen, console, e
1798   ¦ 10:num/raw <- get *e, cursor-row:offset
1799   ¦ 11:num/raw <- get *e, cursor-column:offset
1800   ]
1801   # cursor moves to end of line
1802   memory-should-contain [
1803   ¦ 10 <- 1
1804   ¦ 11 <- 3
1805   ]
1806   # no prints
1807   check-trace-count-for-label 0, [print-character]
1808   # before-cursor is also consistent
1809   assume-console [
1810   ¦ type [a]
1811   ]
1812   run [
1813   ¦ editor-event-loop screen, console, e
1814   ]
1815   screen-should-contain [
1816   ¦ .          .
1817   ¦ .123a↩     .
1818   ¦ .456       .
1819   ¦ .789       .
1820   ¦ .╌╌╌╌╌     .
1821   ]
1822 ]
1823 
1824 # ctrl-u - delete text from start of line until (but not at) cursor
1825 
1826 scenario editor-deletes-to-start-of-line-with-ctrl-u [
1827   local-scope
1828   assume-screen 10/width, 5/height
1829   s:text <- new [123
1830 456]
1831   e:&:editor <- new-editor s, 0/left, 10/right
1832   editor-render screen, e
1833   $clear-trace
1834   # start on second line, press ctrl-u
1835   assume-console [
1836   ¦ left-click 2, 2
1837   ¦ press ctrl-u
1838   ]
1839   run [
1840   ¦ editor-event-loop screen, console, e
1841   ]
1842   # cursor deletes to start of line
1843   screen-should-contain [
1844   ¦ .          .
1845   ¦ .123       .
1846   ¦ .6         .
1847   ¦ .╌╌╌╌╌╌╌╌╌╌.
1848   ¦ .          .
1849   ]
1850   check-trace-count-for-label 10, [print-character]
1851 ]
1852 
1853 after <handle-special-character> [
1854   {
1855   ¦ delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
1856   ¦ break-unless delete-to-start-of-line?
1857   ¦ <delete-to-start-of-line-begin>
1858   ¦ deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
1859   ¦ <delete-to-start-of-line-end>
1860   ¦ go-render?:bool <- minimal-render-for-ctrl-u screen, editor, deleted-cells
1861   ¦ return
1862   }
1863 ]
1864 
1865 def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
1866   local-scope
1867   load-ingredients
1868   curr-column:num <- get *editor, cursor-column:offset
1869   # accumulate the current line as text and render it
1870   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1871   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1872   i:num <- copy curr-column
1873   right:num <- get *editor, right:offset
1874   {
1875   ¦ # if we have a wrapped line, give up and render the whole screen
1876   ¦ wrap?:bool <- greater-or-equal i, right
1877   ¦ return-if wrap?, 1/go-render
1878   ¦ curr <- next curr
1879   ¦ break-unless curr
1880   ¦ c:char <- get *curr, value:offset
1881   ¦ b:bool <- equal c, 10
1882   ¦ break-if b
1883   ¦ buf <- append buf, c
1884   ¦ i <- add i, 1
1885   ¦ loop
1886   }
1887   # if the line used to be wrapped, give up and render the whole screen
1888   num-deleted-cells:num <- length deleted-cells
1889   old-row-len:num <- add i, num-deleted-cells
1890   left:num <- get *editor, left:offset
1891   end:num <- subtract right, left
1892   wrap?:bool <- greater-or-equal old-row-len, end
1893   return-if wrap?, 1/go-render
1894   curr-line:text <- buffer-to-array buf
1895   curr-row:num <- get *editor, cursor-row:offset
1896   render-code screen, curr-line, curr-column, right, curr-row
1897   return 0/dont-render
1898 ]
1899 
1900 def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1901   local-scope
1902   load-ingredients
1903   # compute range to delete
1904   init:&:duplex-list:char <- get *editor, data:offset
1905   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
1906   update-top-of-screen?:bool <- copy 0/false
1907   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1908   start:&:duplex-list:char <- copy before-cursor
1909   end:&:duplex-list:char <- next before-cursor
1910   {
1911   ¦ at-start-of-text?:bool <- equal start, init
1912   ¦ break-if at-start-of-text?
1913   ¦ curr:char <- get *start, value:offset
1914   ¦ at-start-of-line?:bool <- equal curr, 10/newline
1915   ¦ break-if at-start-of-line?
1916   ¦ # if we went past top-of-screen, make a note to update it as well
1917   ¦ at-top-of-screen?:bool <- equal start, top-of-screen
1918   ¦ update-top-of-screen?:bool <- or update-top-of-screen?, at-top-of-screen?
1919   ¦ start <- prev start
1920   ¦ assert start, [delete-to-start-of-line tried to move before start of text]
1921   ¦ loop
1922   }
1923   # snip it out
1924   result:&:duplex-list:char <- next start
1925   remove-between start, end
1926   # update top-of-screen if it's just been invalidated
1927   {
1928   ¦ break-unless update-top-of-screen?
1929   ¦ put *editor, top-of-screen:offset, start
1930   }
1931   # adjust cursor
1932   before-cursor <- copy start
1933   *editor <- put *editor, before-cursor:offset, before-cursor
1934   left:num <- get *editor, left:offset
1935   *editor <- put *editor, cursor-column:offset, left
1936   # if the line wrapped before, we may need to adjust cursor-row as well
1937   right:num <- get *editor, right:offset
1938   width:num <- subtract right, left
1939   num-deleted:num <- length result
1940   cursor-row-adjustment:num <- divide-with-remainder num-deleted, width
1941   return-unless cursor-row-adjustment
1942   cursor-row:num <- get *editor, cursor-row:offset
1943   cursor-row-in-editor:num <- subtract cursor-row, 1  # ignore menubar
1944   at-top?:bool <- lesser-or-equal cursor-row-in-editor, cursor-row-adjustment
1945   {
1946   ¦ break-unless at-top?
1947   ¦ cursor-row <- copy 1  # top of editor, below menubar
1948   }
1949   {
1950   ¦ break-if at-top?
1951   ¦ cursor-row <- subtract cursor-row, cursor-row-adjustment
1952   }
1953   put *editor, cursor-row:offset, cursor-row
1954 ]
1955 
1956 def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [
1957   local-scope
1958   load-ingredients
1959   return-unless s
1960   color:num <- copy 7/white
1961   column:num <- copy left
1962   screen <- move-cursor screen, row, column
1963   screen-height:num <- screen-height screen
1964   i:num <- copy 0
1965   len:num <- length *s
1966   {
1967   ¦ +next-character
1968   ¦ done?:bool <- greater-or-equal i, len
1969   ¦ break-if done?
1970   ¦ done? <- greater-or-equal row, screen-height
1971   ¦ break-if done?
1972   ¦ c:char <- index *s, i
1973   ¦ <character-c-received>
1974   ¦ {
1975   ¦ ¦ # newline? move to left rather than 0
1976   ¦ ¦ newline?:bool <- equal c, 10/newline
1977   ¦ ¦ break-unless newline?
1978   ¦ ¦ # clear rest of line in this window
1979   ¦ ¦ {
1980   ¦ ¦ ¦ done?:bool <- greater-than column, right
1981   ¦ ¦ ¦ break-if done?
1982   ¦ ¦ ¦ space:char <- copy 32/space
1983   ¦ ¦ ¦ print screen, space
1984   ¦ ¦ ¦ column <- add column, 1
1985   ¦ ¦ ¦ loop
1986   ¦ ¦ }
1987   ¦ ¦ row <- add row, 1
1988   ¦ ¦ column <- copy left
1989   ¦ ¦ screen <- move-cursor screen, row, column
1990   ¦ ¦ i <- add i, 1
1991   ¦ ¦ loop +next-character
1992   ¦ }
1993   ¦ {
1994   ¦ ¦ # at right? wrap.
1995   ¦ ¦ at-right?:bool <- equal column, right
1996   ¦ ¦ break-unless at-right?
1997   ¦ ¦ # print wrap icon
1998   ¦ ¦ wrap-icon:char <- copy 8617/loop-back-to-left
1999   ¦ ¦ print screen, wrap-icon, 245/grey
2000   ¦ ¦ column <- copy left
2001   ¦ ¦ row <- add row, 1
2002   ¦ ¦ screen <- move-cursor screen, row, column
2003   ¦ ¦ # don't increment i
2004   ¦ ¦ loop +next-character
2005   ¦ }
2006   ¦ i <- add i, 1
2007   ¦ print screen, c, color
2008   ¦ column <- add column, 1
2009   ¦ loop
2010   }
2011   was-at-left?:bool <- equal column, left
2012   clear-line-until screen, right
2013   {
2014   ¦ break-if was-at-left?
2015   ¦ row <- add row, 1
2016   }
2017   move-cursor screen, row, left
2018 ]
2019 
2020 scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [
2021   local-scope
2022   assume-screen 10/width, 5/height
2023   s:text <- new [123
2024 456]
2025   e:&:editor <- new-editor s, 0/left, 10/right
2026   editor-render screen, e
2027   $clear-trace
2028   # start on first line (no newline before), press ctrl-u
2029   assume-console [
2030   ¦ left-click 1, 2
2031   ¦ press ctrl-u
2032   ]
2033   run [
2034   ¦ editor-event-loop screen, console, e
2035   ]
2036   # cursor deletes to start of line
2037   screen-should-contain [
2038   ¦ .          .
2039   ¦ .3         .
2040   ¦ .456       .
2041   ¦ .╌╌╌╌╌╌╌╌╌╌.
2042   ¦ .          .
2043   ]
2044   check-trace-count-for-label 10, [print-character]
2045 ]
2046 
2047 scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [
2048   local-scope
2049   assume-screen 10/width, 5/height
2050   s:text <- new [123
2051 456]
2052   e:&:editor <- new-editor s, 0/left, 10/right
2053   editor-render screen, e
2054   $clear-trace
2055   # start past end of line, press ctrl-u
2056   assume-console [
2057   ¦ left-click 1, 3
2058   ¦ press ctrl-u
2059   ]
2060   run [
2061   ¦ editor-event-loop screen, console, e
2062   ]
2063   # cursor deletes to start of line
2064   screen-should-contain [
2065   ¦ .          .
2066   ¦ .          .
2067   ¦ .456       .
2068   ¦ .╌╌╌╌╌╌╌╌╌╌.
2069   ¦ .          .
2070   ]
2071   check-trace-count-for-label 10, [print-character]
2072 ]
2073 
2074 scenario editor-deletes-to-start-of-final-line-with-ctrl-u [
2075   local-scope
2076   assume-screen 10/width, 5/height
2077   s:text <- new [123
2078 456]
2079   e:&:editor <- new-editor s, 0/left, 10/right
2080   editor-render screen, e
2081   $clear-trace
2082   # start past end of final line, press ctrl-u
2083   assume-console [
2084   ¦ left-click 2, 3
2085   ¦ press ctrl-u
2086   ]
2087   run [
2088   ¦ editor-event-loop screen, console, e
2089   ]
2090   # cursor deletes to start of line
2091   screen-should-contain [
2092   ¦ .          .
2093   ¦ .123       .
2094   ¦ .          .
2095   ¦ .╌╌╌╌╌╌╌╌╌╌.
2096   ¦ .          .
2097   ]
2098   check-trace-count-for-label 10, [print-character]
2099 ]
2100 
2101 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u [
2102   local-scope
2103   assume-screen 10/width, 10/height
2104   # first line starts out wrapping
2105   s:text <- new [123456
2106 789]
2107   e:&:editor <- new-editor s, 0/left, 5/right
2108   editor-render screen, e
2109   screen-should-contain [
2110   ¦ .          .
2111   ¦ .1234↩     .
2112   ¦ .56        .
2113   ¦ .789       .
2114   ¦ .╌╌╌╌╌     .
2115   ¦ .          .
2116   ]
2117   $clear-trace
2118   # ctrl-u enough of the first line that it's no longer wrapping
2119   assume-console [
2120   ¦ left-click 1, 3
2121   ¦ press ctrl-u
2122   ]
2123   run [
2124   ¦ editor-event-loop screen, console, e
2125   ]
2126   # entire screen needs to be refreshed
2127   screen-should-contain [
2128   ¦ .          .
2129   ¦ .456       .
2130   ¦ .789       .
2131   ¦ .╌╌╌╌╌     .
2132   ¦ .          .
2133   ]
2134   check-trace-count-for-label 45, [print-character]
2135 ]
2136 
2137 # sometimes hitting ctrl-u needs to adjust the cursor row
2138 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-2 [
2139   local-scope
2140   assume-screen 10/width, 10/height
2141   # third line starts out wrapping
2142   s:text <- new [1
2143 2
2144 345678
2145 9]
2146   e:&:editor <- new-editor s, 0/left, 5/right
2147   editor-render screen, e
2148   screen-should-contain [
2149   ¦ .          .
2150   ¦ .1         .
2151   ¦ .2         .
2152   ¦ .3456↩     .
2153   ¦ .78        .
2154   ¦ .9         .
2155   ¦ .╌╌╌╌╌     .
2156   ¦ .          .
2157   ]
2158   # position cursor on screen line after the wrap and hit ctrl-u
2159   assume-console [
2160   ¦ left-click 4, 1  # on '8'
2161   ¦ press ctrl-u
2162   ]
2163   run [
2164   ¦ editor-event-loop screen, console, e
2165   ¦ 10:num/raw <- get *e, cursor-row:offset
2166   ¦ 11:num/raw <- get *e, cursor-column:offset
2167   ]
2168   screen-should-contain [
2169   ¦ .          .
2170   ¦ .1         .
2171   ¦ .2         .
2172   ¦ .8         .
2173   ¦ .9         .
2174   ¦ .╌╌╌╌╌     .
2175   ¦ .          .
2176   ]
2177   # cursor moves up one screen line
2178   memory-should-contain [
2179   ¦ 10 <- 3  # cursor-row
2180   ¦ 11 <- 0  # cursor-column
2181   ]
2182 ]
2183 
2184 # line wrapping twice (taking up 3 screen lines)
2185 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-3 [
2186   local-scope
2187   assume-screen 10/width, 10/height
2188   # third line starts out wrapping
2189   s:text <- new [1
2190 2
2191 3456789abcd
2192 e]
2193   e:&:editor <- new-editor s, 0/left, 5/right
2194   editor-render screen, e
2195   assume-console [
2196   ¦ left-click 4, 1  # on '8'
2197   ]
2198   editor-event-loop screen, console, e
2199   screen-should-contain [
2200   ¦ .          .
2201   ¦ .1         .
2202   ¦ .2         .
2203   ¦ .3456↩     .
2204   ¦ .789a↩     .
2205   ¦ .bcd       .
2206   ¦ .e         .
2207   ¦ .╌╌╌╌╌     .
2208   ¦ .          .
2209   ]
2210   assume-console [
2211   ¦ left-click 5, 1
2212   ¦ press ctrl-u
2213   ]
2214   run [
2215   ¦ editor-event-loop screen, console, e
2216   ¦ 10:num/raw <- get *e, cursor-row:offset
2217   ¦ 11:num/raw <- get *e, cursor-column:offset
2218   ]
2219   screen-should-contain [
2220   ¦ .          .
2221   ¦ .1         .
2222   ¦ .2         .
2223   ¦ .cd        .
2224   ¦ .e         .
2225   ¦ .╌╌╌╌╌     .
2226   ¦ .          .
2227   ]
2228   # make sure we adjusted cursor-row
2229   memory-should-contain [
2230   ¦ 10 <- 3  # cursor-row
2231   ¦ 11 <- 0  # cursor-column
2232   ]
2233 ]
2234 
2235 # adjusting cursor row at the top of the screen
2236 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-4 [
2237   local-scope
2238   assume-screen 10/width, 10/height
2239   # first line starts out wrapping
2240   s:text <- new [1234567
2241 89]
2242   e:&:editor <- new-editor s, 0/left, 5/right
2243   editor-render screen, e
2244   screen-should-contain [
2245   ¦ .          .
2246   ¦ .1234↩     .
2247   ¦ .567       .
2248   ¦ .89        .
2249   ¦ .╌╌╌╌╌     .
2250   ¦ .          .
2251   ]
2252   # position cursor on second screen line (after the wrap) and hit ctrl-u
2253   assume-console [
2254   ¦ left-click 2, 1
2255   ¦ press ctrl-u
2256   ]
2257   run [
2258   ¦ editor-event-loop screen, console, e
2259   ¦ 10:num/raw <- get *e, cursor-row:offset
2260   ¦ 11:num/raw <- get *e, cursor-column:offset
2261   ]
2262   screen-should-contain [
2263   ¦ .          .
2264   ¦ .67        .
2265   ¦ .89        .
2266   ¦ .╌╌╌╌╌     .
2267   ¦ .          .
2268   ]
2269   # cursor moves up to screen line 1
2270   memory-should-contain [
2271   ¦ 10 <- 1  # cursor-row
2272   ¦ 11 <- 0  # cursor-column
2273   ]
2274 ]
2275 
2276 # screen begins part-way through a wrapping line
2277 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-5 [
2278   local-scope
2279   assume-screen 10/width, 10/height
2280   # third line starts out wrapping
2281   s:text <- new [1
2282 2
2283 345678
2284 9]
2285   e:&:editor <- new-editor s, 0/left, 5/right
2286   editor-render screen, e
2287   # position the '78' line at the top of the screen
2288   assume-console [
2289   ¦ left-click 4, 1  # on '8'
2290   ¦ press ctrl-t
2291   ]
2292   editor-event-loop screen, console, e
2293   screen-should-contain [
2294   ¦ .          .
2295   ¦ .78        .
2296   ¦ .9         .
2297   ¦ .╌╌╌╌╌     .
2298   ¦ .          .
2299   ]
2300   assume-console [
2301   ¦ left-click 1, 1
2302   ¦ press ctrl-u
2303   ]
2304   run [
2305   ¦ editor-event-loop screen, console, e
2306   ¦ 10:num/raw <- get *e, cursor-row:offset
2307   ¦ 11:num/raw <- get *e, cursor-column:offset
2308   ]
2309   # make sure we updated top-of-screen correctly
2310   screen-should-contain [
2311   ¦ .          .
2312   ¦ .8         .
2313   ¦ .9         .
2314   ¦ .╌╌╌╌╌     .
2315   ¦ .          .
2316   ]
2317   memory-should-contain [
2318   ¦ 10 <- 1  # cursor-row
2319   ¦ 11 <- 0  # cursor-column
2320   ]
2321   # the entire line is deleted, even the part not shown on screen
2322   assume-console [
2323   ¦ press up-arrow
2324   ]
2325   run [
2326   ¦ editor-event-loop screen, console, e
2327   ]
2328   screen-should-contain [
2329   ¦ .          .
2330   ¦ .2         .
2331   ¦ .8         .
2332   ¦ .9         .
2333   ¦ .╌╌╌╌╌     .
2334   ¦ .          .
2335   ]
2336 ]
2337 
2338 # screen begins part-way through a line wrapping twice (taking up 3 screen lines)
2339 scenario editor-deletes-to-start-of-wrapped-line-with-ctrl-u-6 [
2340   local-scope
2341   assume-screen 10/width, 10/height
2342   # third line starts out wrapping
2343   s:text <- new [1
2344 2
2345 3456789abcd
2346 e]
2347   e:&:editor <- new-editor s, 0/left, 5/right
2348   editor-render screen, e
2349   # position the 'bcd' line at the top of the screen
2350   assume-console [
2351   ¦ left-click 4, 1  # on '8'
2352   ¦ press ctrl-t
2353   ¦ press ctrl-s  # now on 'c'
2354   ]
2355   editor-event-loop screen, console, e
2356   screen-should-contain [
2357   ¦ .          .
2358   ¦ .bcd       .
2359   ¦ .e         .
2360   ¦ .╌╌╌╌╌     .
2361   ¦ .          .
2362   ]
2363   assume-console [
2364   ¦ left-click 1, 1
2365   ¦ press ctrl-u
2366   ]
2367   run [
2368   ¦ editor-event-loop screen, console, e
2369   ¦ 10:num/raw <- get *e, cursor-row:offset
2370   ¦ 11:num/raw <- get *e, cursor-column:offset
2371   ]
2372   # make sure we updated top-of-screen correctly
2373   screen-should-contain [
2374   ¦ .          .
2375   ¦ .cd        .
2376   ¦ .e         .
2377   ¦ .╌╌╌╌╌     .
2378   ¦ .          .
2379   ]
2380   memory-should-contain [
2381   ¦ 10 <- 1  # cursor-row
2382   ¦ 11 <- 0  # cursor-column
2383   ]
2384   # the entire line is deleted, even the part not shown on screen
2385   assume-console [
2386   ¦ press up-arrow
2387   ]
2388   run [
2389   ¦ editor-event-loop screen, console, e
2390   ]
2391   screen-should-contain [
2392   ¦ .          .
2393   ¦ .2         .
2394   ¦ .cd        .
2395   ¦ .e         .
2396   ¦ .╌╌╌╌╌     .
2397   ¦ .          .
2398   ]
2399 ]
2400 
2401 # ctrl-k - delete text from cursor to end of line (but not the newline)
2402 
2403 scenario editor-deletes-to-end-of-line-with-ctrl-k [
2404   local-scope
2405   assume-screen 10/width, 5/height
2406   s:text <- new [123
2407 456]
2408   e:&:editor <- new-editor s, 0/left, 10/right
2409   editor-render screen, e
2410   $clear-trace
2411   # start on first line, press ctrl-k
2412   assume-console [
2413   ¦ left-click 1, 1
2414   ¦ press ctrl-k
2415   ]
2416   run [
2417   ¦ editor-event-loop screen, console, e
2418   ]
2419   # cursor deletes to end of line
2420   screen-should-contain [
2421   ¦ .          .
2422   ¦ .1         .
2423   ¦ .456       .
2424   ¦ .╌╌╌╌╌╌╌╌╌╌.
2425   ¦ .          .
2426   ]
2427   check-trace-count-for-label 9, [print-character]
2428 ]
2429 
2430 after <handle-special-character> [
2431   {
2432   ¦ delete-to-end-of-line?:bool <- equal c, 11/ctrl-k
2433   ¦ break-unless delete-to-end-of-line?
2434   ¦ <delete-to-end-of-line-begin>
2435   ¦ deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor
2436   ¦ <delete-to-end-of-line-end>
2437   ¦ # checks if we can do a minimal render and if we can it will do a minimal render
2438   ¦ go-render?:bool <- minimal-render-for-ctrl-k screen, editor, deleted-cells
2439   ¦ return
2440   }
2441 ]
2442 
2443 def minimal-render-for-ctrl-k screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
2444   local-scope
2445   load-ingredients
2446   # if we deleted nothing, there's nothing to render
2447   return-unless deleted-cells, 0/dont-render
2448   # if the line used to wrap before, give up and render the whole screen
2449   curr-column:num <- get *editor, cursor-column:offset
2450   num-deleted-cells:num <- length deleted-cells
2451   old-row-len:num <- add curr-column, num-deleted-cells
2452   left:num <- get *editor, left:offset
2453   right:num <- get *editor, right:offset
2454   end:num <- subtract right, left
2455   wrap?:bool <- greater-or-equal old-row-len, end
2456   return-if wrap?, 1/go-render
2457   clear-line-until screen, right
2458   return 0/dont-render
2459 ]
2460 
2461 def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
2462   local-scope
2463   load-ingredients
2464   # compute range to delete
2465   start:&:duplex-list:char <- get *editor, before-cursor:offset
2466   end:&:duplex-list:char <- next start
2467   {
2468   ¦ at-end-of-text?:bool <- equal end, 0/null
2469   ¦ break-if at-end-of-text?
2470   ¦ curr:char <- get *end, value:offset
2471   ¦ at-end-of-line?:bool <- equal curr, 10/newline
2472   ¦ break-if at-end-of-line?
2473   ¦ end <- next end
2474   ¦ loop
2475   }
2476   # snip it out
2477   result <- next start
2478   remove-between start, end
2479 ]
2480 
2481 scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [
2482   local-scope
2483   assume-screen 10/width, 5/height
2484   s:text <- new [123
2485 456]
2486   e:&:editor <- new-editor s, 0/left, 10/right
2487   editor-render screen, e
2488   $clear-trace
2489   # start on second line (no newline after), press ctrl-k
2490   assume-console [
2491   ¦ left-click 2, 1
2492   ¦ press ctrl-k
2493   ]
2494   run [
2495   ¦ editor-event-loop screen, console, e
2496   ]
2497   # cursor deletes to end of line
2498   screen-should-contain [
2499   ¦ .          .
2500   ¦ .123       .
2501   ¦ .4         .
2502   ¦ .╌╌╌╌╌╌╌╌╌╌.
2503   ¦ .          .
2504   ]
2505   check-trace-count-for-label 9, [print-character]
2506 ]
2507 
2508 scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [
2509   local-scope
2510   assume-screen 10/width, 5/height
2511   s:text <- new [123
2512 456]
2513   e:&:editor <- new-editor s, 0/left, 10/right
2514   editor-render screen, e
2515   $clear-trace
2516   # start at end of line
2517   assume-console [
2518   ¦ left-click 1, 2
2519   ¦ press ctrl-k
2520   ]
2521   run [
2522   ¦ editor-event-loop screen, console, e
2523   ]
2524   # cursor deletes just last character
2525   screen-should-contain [
2526   ¦ .          .
2527   ¦ .12        .
2528   ¦ .456       .
2529   ¦ .╌╌╌╌╌╌╌╌╌╌.
2530   ¦ .          .
2531   ]
2532   check-trace-count-for-label 8, [print-character]
2533 ]
2534 
2535 scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [
2536   local-scope
2537   assume-screen 10/width, 5/height
2538   s:text <- new [123
2539 456]
2540   e:&:editor <- new-editor s, 0/left, 10/right
2541   editor-render screen, e
2542   $clear-trace
2543   # start past end of line
2544   assume-console [
2545   ¦ left-click 1, 3
2546   ¦ press ctrl-k
2547   ]
2548   run [
2549   ¦ editor-event-loop screen, console, e
2550   ]
2551   # cursor deletes nothing
2552   screen-should-contain [
2553   ¦ .          .
2554   ¦ .123       .
2555   ¦ .456       .
2556   ¦ .╌╌╌╌╌╌╌╌╌╌.
2557   ¦ .          .
2558   ]
2559   check-trace-count-for-label 7, [print-character]
2560 ]
2561 
2562 scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [
2563   local-scope
2564   assume-screen 10/width, 5/height
2565   s:text <- new [123
2566 456]
2567   e:&:editor <- new-editor s, 0/left, 10/right
2568   editor-render screen, e
2569   $clear-trace
2570   # start at end of text
2571   assume-console [
2572   ¦ left-click 2, 2
2573   ¦ press ctrl-k
2574   ]
2575   run [
2576   ¦ editor-event-loop screen, console, e
2577   ]
2578   # cursor deletes just the final character
2579   screen-should-contain [
2580   ¦ .          .
2581   ¦ .123       .
2582   ¦ .45        .
2583   ¦ .╌╌╌╌╌╌╌╌╌╌.
2584   ¦ .          .
2585   ]
2586   check-trace-count-for-label 8, [print-character]
2587 ]
2588 
2589 scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
2590   local-scope
2591   assume-screen 10/width, 5/height
2592   s:text <- new [123
2593 456]
2594   e:&:editor <- new-editor s, 0/left, 10/right
2595   editor-render screen, e
2596   $clear-trace
2597   # start past end of text
2598   assume-console [
2599   ¦ left-click 2, 3
2600   ¦ press ctrl-k
2601   ]
2602   run [
2603   ¦ editor-event-loop screen, console, e
2604   ]
2605   # cursor deletes nothing
2606   screen-should-contain [
2607   ¦ .          .
2608   ¦ .123       .
2609   ¦ .456       .
2610   ¦ .╌╌╌╌╌╌╌╌╌╌.
2611   ¦ .          .
2612   ]
2613   # no prints necessary
2614   check-trace-count-for-label 0, [print-character]
2615 ]
2616 
2617 scenario editor-deletes-to-end-of-wrapped-line-with-ctrl-k [
2618   local-scope
2619   assume-screen 10/width, 5/height
2620   # create an editor with the first line wrapping to a second screen row
2621   s:text <- new [1234
2622 567]
2623   e:&:editor <- new-editor s, 0/left, 4/right
2624   editor-render screen, e
2625   $clear-trace
2626   # delete all of the first wrapped line
2627   assume-console [
2628   ¦ press ctrl-k
2629   ]
2630   run [
2631   ¦ editor-event-loop screen, console, e
2632   ]
2633   # screen shows an empty unwrapped first line
2634   screen-should-contain [
2635   ¦ .          .
2636   ¦ .          .
2637   ¦ .567       .
2638   ¦ .╌╌╌╌      .
2639   ¦ .          .
2640   ]
2641   # entire screen is refreshed
2642   check-trace-count-for-label 16, [print-character]
2643 ]
2644 
2645 # cursor-down can scroll if necessary
2646 
2647 scenario editor-can-scroll-down-using-arrow-keys [
2648   local-scope
2649   # screen has 1 line for menu + 3 lines
2650   assume-screen 10/width, 4/height
2651   # initialize editor with >3 lines
2652   s:text <- new [a
2653 b
2654 c
2655 d]
2656   e:&:editor <- new-editor s, 0/left, 10/right
2657   editor-render screen, e
2658   screen-should-contain [
2659   ¦ .          .
2660   ¦ .a         .
2661   ¦ .b         .
2662   ¦ .c         .
2663   ]
2664   # position cursor at last line, then try to move further down
2665   assume-console [
2666   ¦ left-click 3, 0
2667   ¦ press down-arrow
2668   ]
2669   run [
2670   ¦ editor-event-loop screen, console, e
2671   ]
2672   # screen slides by one line
2673   screen-should-contain [
2674   ¦ .          .
2675   ¦ .b         .
2676   ¦ .c         .
2677   ¦ .d         .
2678   ]
2679 ]
2680 
2681 after <scroll-down> [
2682   trace 10, [app], [scroll down]
2683   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2684   left:num <- get *editor, left:offset
2685   right:num <- get *editor, right:offset
2686   max:num <- subtract right, left
2687   old-top:&:duplex-list:char <- copy top-of-screen
2688   top-of-screen <- before-start-of-next-line top-of-screen, max
2689   *editor <- put *editor, top-of-screen:offset, top-of-screen
2690   no-movement?:bool <- equal old-top, top-of-screen
2691   return-if no-movement?, 0/don't-render
2692 ]
2693 
2694 # takes a pointer into the doubly-linked list, scans ahead at most 'max'
2695 # positions until the next newline
2696 # returns original if no next newline
2697 # beware: never return null pointer.
2698 def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [
2699   local-scope
2700   load-ingredients
2701   count:num <- copy 0
2702   curr:&:duplex-list:char <- copy original
2703   # skip the initial newline if it exists
2704   {
2705   ¦ c:char <- get *curr, value:offset
2706   ¦ at-newline?:bool <- equal c, 10/newline
2707   ¦ break-unless at-newline?
2708   ¦ curr <- next curr
2709   ¦ count <- add count, 1
2710   }
2711   {
2712   ¦ return-unless curr, original
2713   ¦ done?:bool <- greater-or-equal count, max
2714   ¦ break-if done?
2715   ¦ c:char <- get *curr, value:offset
2716   ¦ at-newline?:bool <- equal c, 10/newline
2717   ¦ break-if at-newline?
2718   ¦ curr <- next curr
2719   ¦ count <- add count, 1
2720   ¦ loop
2721   }
2722   return-unless curr, original
2723   return curr
2724 ]
2725 
2726 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
2727   local-scope
2728   # screen has 1 line for menu + 3 lines
2729   assume-screen 10/width, 4/height
2730   # initialize editor with a long, wrapped line and more than a screen of
2731   # other lines
2732   s:text <- new [abcdef
2733 g
2734 h
2735 i]
2736   e:&:editor <- new-editor s, 0/left, 5/right
2737   editor-render screen, e
2738   screen-should-contain [
2739   ¦ .          .
2740   ¦ .abcd↩     .
2741   ¦ .ef        .
2742   ¦ .g         .
2743   ]
2744   # position cursor at last line, then try to move further down
2745   assume-console [
2746   ¦ left-click 3, 0
2747   ¦ press down-arrow
2748   ]
2749   run [
2750   ¦ editor-event-loop screen, console, e
2751   ]
2752   # screen shows partial wrapped line
2753   screen-should-contain [
2754   ¦ .          .
2755   ¦ .ef        .
2756   ¦ .g         .
2757   ¦ .h         .
2758   ]
2759 ]
2760 
2761 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
2762   local-scope
2763   # screen has 1 line for menu + 3 lines
2764   assume-screen 10/width, 4/height
2765   # editor starts with a long line wrapping twice
2766   s:text <- new [abcdefghij
2767 k
2768 l
2769 m]
2770   e:&:editor <- new-editor s, 0/left, 5/right
2771   # position cursor at last line, then try to move further down
2772   assume-console [
2773   ¦ left-click 3, 0
2774   ¦ press down-arrow
2775   ]
2776   run [
2777   ¦ editor-event-loop screen, console, e
2778   ]
2779   # screen shows partial wrapped line containing a wrap icon
2780   screen-should-contain [
2781   ¦ .          .
2782   ¦ .efgh↩     .
2783   ¦ .ij        .
2784   ¦ .k         .
2785   ]
2786   # scroll down again
2787   assume-console [
2788   ¦ press down-arrow
2789   ]
2790   run [
2791   ¦ editor-event-loop screen, console, e
2792   ]
2793   # screen shows partial wrapped line
2794   screen-should-contain [
2795   ¦ .          .
2796   ¦ .ij        .
2797   ¦ .k         .
2798   ¦ .l         .
2799   ]
2800 ]
2801 
2802 scenario editor-scrolls-down-when-line-wraps [
2803   local-scope
2804   # screen has 1 line for menu + 3 lines
2805   assume-screen 5/width, 4/height
2806   # editor contains a long line in the third line
2807   s:text <- new [a
2808 b
2809 cdef]
2810   e:&:editor <- new-editor s, 0/left, 5/right
2811   # position cursor at end, type a character
2812   assume-console [
2813   ¦ left-click 3, 4
2814   ¦ type [g]
2815   ]
2816   run [
2817   ¦ editor-event-loop screen, console, e
2818   ¦ 3:num/raw <- get *e, cursor-row:offset
2819   ¦ 4:num/raw <- get *e, cursor-column:offset
2820   ]
2821   # screen scrolls
2822   screen-should-contain [
2823   ¦ .     .
2824   ¦ .b    .
2825   ¦ .cdef↩.
2826   ¦ .g    .
2827   ]
2828   memory-should-contain [
2829   ¦ 3 <- 3
2830   ¦ 4 <- 1
2831   ]
2832 ]
2833 
2834 scenario editor-scrolls-down-on-newline [
2835   local-scope
2836   assume-screen 5/width, 4/height
2837   # position cursor after last line and type newline
2838   s:text <- new [a
2839 b
2840 c]
2841   e:&:editor <- new-editor s, 0/left, 5/right
2842   assume-console [
2843   ¦ left-click 3, 4
2844   ¦ type [
2845 ]
2846   ]
2847   run [
2848   ¦ editor-event-loop screen, console, e
2849   ¦ 3:num/raw <- get *e, cursor-row:offset
2850   ¦ 4:num/raw <- get *e, cursor-column:offset
2851   ]
2852   # screen scrolls
2853   screen-should-contain [
2854   ¦ .     .
2855   ¦ .b    .
2856   ¦ .c    .
2857   ¦ .     .
2858   ]
2859   memory-should-contain [
2860   ¦ 3 <- 3
2861   ¦ 4 <- 0
2862   ]
2863 ]
2864 
2865 scenario editor-scrolls-down-on-right-arrow [
2866   local-scope
2867   # screen has 1 line for menu + 3 lines
2868   assume-screen 5/width, 4/height
2869   # editor contains a wrapped line
2870   s:text <- new [a
2871 b
2872 cdefgh]
2873   e:&:editor <- new-editor s, 0/left, 5/right
2874   # position cursor at end of screen and try to move right
2875   assume-console [
2876   ¦ left-click 3, 3
2877   ¦ press right-arrow
2878   ]
2879   run [
2880   ¦ editor-event-loop screen, console, e
2881   ¦ 3:num/raw <- get *e, cursor-row:offset
2882   ¦ 4:num/raw <- get *e, cursor-column:offset
2883   ]
2884   # screen scrolls
2885   screen-should-contain [
2886   ¦ .     .
2887   ¦ .b    .
2888   ¦ .cdef↩.
2889   ¦ .gh   .
2890   ]
2891   memory-should-contain [
2892   ¦ 3 <- 3
2893   ¦ 4 <- 0
2894   ]
2895 ]
2896 
2897 scenario editor-scrolls-down-on-right-arrow-2 [
2898   local-scope
2899   # screen has 1 line for menu + 3 lines
2900   assume-screen 5/width, 4/height
2901   # editor contains more lines than can fit on screen
2902   s:text <- new [a
2903 b
2904 c
2905 d]
2906   e:&:editor <- new-editor s, 0/left, 5/right
2907   # position cursor at end of screen and try to move right
2908   assume-console [
2909   ¦ left-click 3, 3
2910   ¦ press right-arrow
2911   ]
2912   run [
2913   ¦ editor-event-loop screen, console, e
2914   ¦ 3:num/raw <- get *e, cursor-row:offset
2915   ¦ 4:num/raw <- get *e, cursor-column:offset
2916   ]
2917   # screen scrolls
2918   screen-should-contain [
2919   ¦ .     .
2920   ¦ .b    .
2921   ¦ .c    .
2922   ¦ .d    .
2923   ]
2924   memory-should-contain [
2925   ¦ 3 <- 3
2926   ¦ 4 <- 0
2927   ]
2928 ]
2929 
2930 scenario editor-scrolls-at-end-on-down-arrow [
2931   local-scope
2932   assume-screen 10/width, 5/height
2933   s:text <- new [abc
2934 de]
2935   e:&:editor <- new-editor s, 0/left, 10/right
2936   editor-render screen, e
2937   $clear-trace
2938   # try to move down past end of text
2939   assume-console [
2940   ¦ left-click 2, 0
2941   ¦ press down-arrow
2942   ]
2943   run [
2944   ¦ editor-event-loop screen, console, e
2945   ¦ 3:num/raw <- get *e, cursor-row:offset
2946   ¦ 4:num/raw <- get *e, cursor-column:offset
2947   ]
2948   # screen should scroll, moving cursor to end of text
2949   memory-should-contain [
2950   ¦ 3 <- 1
2951   ¦ 4 <- 2
2952   ]
2953   assume-console [
2954   ¦ type [0]
2955   ]
2956   run [
2957   ¦ editor-event-loop screen, console, e
2958   ]
2959   screen-should-contain [
2960   ¦ .          .
2961   ¦ .de0       .
2962   ¦ .╌╌╌╌╌╌╌╌╌╌.
2963   ¦ .          .
2964   ]
2965   # try to move down again
2966   $clear-trace
2967   assume-console [
2968   ¦ left-click 2, 0
2969   ¦ press down-arrow
2970   ]
2971   run [
2972   ¦ editor-event-loop screen, console, e
2973   ¦ 3:num/raw <- get *e, cursor-row:offset
2974   ¦ 4:num/raw <- get *e, cursor-column:offset
2975   ]
2976   # screen stops scrolling because cursor is already at top
2977   memory-should-contain [
2978   ¦ 3 <- 1
2979   ¦ 4 <- 3
2980   ]
2981   check-trace-count-for-label 0, [print-character]
2982   assume-console [
2983   ¦ type [1]
2984   ]
2985   run [
2986   ¦ editor-event-loop screen, console, e
2987   ]
2988   screen-should-contain [
2989   ¦ .          .
2990   ¦ .de01      .
2991   ¦ .╌╌╌╌╌╌╌╌╌╌.
2992   ¦ .          .
2993   ]
2994 ]
2995 
2996 scenario editor-combines-page-and-line-scroll [
2997   local-scope
2998   # screen has 1 line for menu + 3 lines
2999   assume-screen 10/width, 4/height
3000   # initialize editor with a few pages of lines
3001   s:text <- new [a
3002 b
3003 c
3004 d
3005 e
3006 f
3007 g]
3008   e:&:editor <- new-editor s, 0/left, 5/right
3009   editor-render screen, e
3010   # scroll down one page and one line
3011   assume-console [
3012   ¦ press page-down
3013   ¦ left-click 3, 0
3014   ¦ press down-arrow
3015   ]
3016   run [
3017   ¦ editor-event-loop screen, console, e
3018   ]
3019   # screen scrolls down 3 lines
3020   screen-should-contain [
3021   ¦ .          .
3022   ¦ .d         .
3023   ¦ .e         .
3024   ¦ .f         .
3025   ]
3026 ]
3027 
3028 # cursor-up can scroll if necessary
3029 
3030 scenario editor-can-scroll-up-using-arrow-keys [
3031   local-scope
3032   # screen has 1 line for menu + 3 lines
3033   assume-screen 10/width, 4/height
3034   # initialize editor with >3 lines
3035   s:text <- new [a
3036 b
3037 c
3038 d]
3039   e:&:editor <- new-editor s, 0/left, 10/right
3040   editor-render screen, e
3041   screen-should-contain [
3042   ¦ .          .
3043   ¦ .a         .
3044   ¦ .b         .
3045   ¦ .c         .
3046   ]
3047   # position cursor at top of second page, then try to move up
3048   assume-console [
3049   ¦ press page-down
3050   ¦ press up-arrow
3051   ]
3052   run [
3053   ¦ editor-event-loop screen, console, e
3054   ]
3055   # screen slides by one line
3056   screen-should-contain [
3057   ¦ .          .
3058   ¦ .b         .
3059   ¦ .c         .
3060   ¦ .d         .
3061   ]
3062 ]
3063 
3064 after <scroll-up> [
3065   trace 10, [app], [scroll up]
3066   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3067   old-top:&:duplex-list:char <- copy top-of-screen
3068   top-of-screen <- before-previous-screen-line top-of-screen, editor
3069   *editor <- put *editor, top-of-screen:offset, top-of-screen
3070   no-movement?:bool <- equal old-top, top-of-screen
3071   return-if no-movement?, 0/don't-render
3072 ]
3073 
3074 # takes a pointer into the doubly-linked list, scans back to before start of
3075 # previous *wrapped* line
3076 # returns original if no next newline
3077 # beware: never return null pointer
3078 def before-previous-screen-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [
3079   local-scope
3080   load-ingredients
3081   curr:&:duplex-list:char <- copy in
3082   c:char <- get *curr, value:offset
3083   # compute max, number of characters to skip
3084   #   1 + len%(width-1)
3085   #   except rotate second term to vary from 1 to width-1 rather than 0 to width-2
3086   left:num <- get *editor, left:offset
3087   right:num <- get *editor, right:offset
3088   max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon
3089   sentinel:&:duplex-list:char <- get *editor, data:offset
3090   len:num <- previous-line-length curr, sentinel
3091   {
3092   ¦ break-if len
3093   ¦ # empty line; just skip this newline
3094   ¦ prev:&:duplex-list:char <- prev curr
3095   ¦ return-unless prev, curr
3096   ¦ return prev
3097   }
3098   _, max:num <- divide-with-remainder len, max-line-length
3099   # remainder 0 => scan one width-worth
3100   {
3101   ¦ break-if max
3102   ¦ max <- copy max-line-length
3103   }
3104   max <- add max, 1
3105   count:num <- copy 0
3106   # skip 'max' characters
3107   {
3108   ¦ done?:bool <- greater-or-equal count, max
3109   ¦ break-if done?
3110   ¦ prev:&:duplex-list:char <- prev curr
3111   ¦ break-unless prev
3112   ¦ curr <- copy prev
3113   ¦ count <- add count, 1
3114   ¦ loop
3115   }
3116   return curr
3117 ]
3118 
3119 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [
3120   local-scope
3121   # screen has 1 line for menu + 3 lines
3122   assume-screen 10/width, 4/height
3123   # initialize editor with a long, wrapped line and more than a screen of
3124   # other lines
3125   s:text <- new [abcdef
3126 g
3127 h
3128 i]
3129   e:&:editor <- new-editor s, 0/left, 5/right
3130   editor-render screen, e
3131   screen-should-contain [
3132   ¦ .          .
3133   ¦ .abcd↩     .
3134   ¦ .ef        .
3135   ¦ .g         .
3136   ]
3137   # position cursor at top of second page, just below wrapped line
3138   assume-console [
3139   ¦ press page-down
3140   ]
3141   run [
3142   ¦ editor-event-loop screen, console, e
3143   ]
3144   screen-should-contain [
3145   ¦ .          .
3146   ¦ .g         .
3147   ¦ .h         .
3148   ¦ .i         .
3149   ]
3150   # now move up one line
3151   assume-console [
3152   ¦ press up-arrow
3153   ]
3154   run [
3155   ¦ editor-event-loop screen, console, e
3156   ]
3157   # screen shows partial wrapped line
3158   screen-should-contain [
3159   ¦ .          .
3160   ¦ .ef        .
3161   ¦ .g         .
3162   ¦ .h         .
3163   ]
3164 ]
3165 
3166 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
3167   local-scope
3168   # screen has 1 line for menu + 4 lines
3169   assume-screen 10/width, 5/height
3170   # editor starts with a long line wrapping twice, occupying 3 of the 4 lines
3171   s:text <- new [abcdefghij
3172 k
3173 l
3174 m]
3175   e:&:editor <- new-editor s, 0/left, 5/right
3176   editor-render screen, e
3177   # position cursor at top of second page
3178   assume-console [
3179   ¦ press page-down
3180   ]
3181   run [
3182   ¦ editor-event-loop screen, console, e
3183   ]
3184   screen-should-contain [
3185   ¦ .          .
3186   ¦ .k         .
3187   ¦ .l         .
3188   ¦ .m         .
3189   ¦ .╌╌╌╌╌     .
3190   ]
3191   # move up one line
3192   assume-console [
3193   ¦ press up-arrow
3194   ]
3195   run [
3196   ¦ editor-event-loop screen, console, e
3197   ]
3198   # screen shows partial wrapped line
3199   screen-should-contain [
3200   ¦ .          .
3201   ¦ .ij        .
3202   ¦ .k         .
3203   ¦ .l         .
3204   ¦ .m         .
3205   ]
3206   # move up a second line
3207   assume-console [
3208   ¦ press up-arrow
3209   ]
3210   run [
3211   ¦ editor-event-loop screen, console, e
3212   ]
3213   # screen shows partial wrapped line
3214   screen-should-contain [
3215   ¦ .          .
3216   ¦ .efgh↩     .
3217   ¦ .ij        .
3218   ¦ .k         .
3219   ¦ .l         .
3220   ]
3221   # move up a third line
3222   assume-console [
3223   ¦ press up-arrow
3224   ]
3225   run [
3226   ¦ editor-event-loop screen, console, e
3227   ]
3228   # screen shows partial wrapped line
3229   screen-should-contain [
3230   ¦ .          .
3231   ¦ .abcd↩     .
3232   ¦ .efgh↩     .
3233   ¦ .ij        .
3234   ¦ .k         .
3235   ]
3236 ]
3237 
3238 # same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length
3239 # slightly off, just to prevent over-training
3240 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
3241   local-scope
3242   # screen has 1 line for menu + 3 lines
3243   assume-screen 10/width, 4/height
3244   # initialize editor with a long, wrapped line and more than a screen of
3245   # other lines
3246   s:text <- new [abcdef
3247 g
3248 h
3249 i]
3250   e:&:editor <- new-editor s, 0/left, 6/right
3251   editor-render screen, e
3252   screen-should-contain [
3253   ¦ .          .
3254   ¦ .abcde↩    .
3255   ¦ .f         .
3256   ¦ .g         .
3257   ]
3258   # position cursor at top of second page, just below wrapped line
3259   assume-console [
3260   ¦ press page-down
3261   ]
3262   run [
3263   ¦ editor-event-loop screen, console, e
3264   ]
3265   screen-should-contain [
3266   ¦ .          .
3267   ¦ .g         .
3268   ¦ .h         .
3269   ¦ .i         .
3270   ]
3271   # now move up one line
3272   assume-console [
3273   ¦ press up-arrow
3274   ]
3275   run [
3276   ¦ editor-event-loop screen, console, e
3277   ]
3278   # screen shows partial wrapped line
3279   screen-should-contain [
3280   ¦ .          .
3281   ¦ .f         .
3282   ¦ .g         .
3283   ¦ .h         .
3284   ]
3285 ]
3286 
3287 # check empty lines
3288 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
3289   local-scope
3290   assume-screen 10/width, 4/height
3291   # initialize editor with some lines around an empty line
3292   s:text <- new [a
3293 b
3294 
3295 c
3296 d
3297 e]
3298   e:&:editor <- new-editor s, 0/left, 6/right
3299   editor-render screen, e
3300   assume-console [
3301   ¦ press page-down
3302   ]
3303   run [
3304   ¦ editor-event-loop screen, console, e
3305   ]
3306   screen-should-contain [
3307   ¦ .          .
3308   ¦ .          .
3309   ¦ .c         .
3310   ¦ .d         .
3311   ]
3312   assume-console [
3313   ¦ press page-down
3314   ]
3315   run [
3316   ¦ editor-event-loop screen, console, e
3317   ]
3318   screen-should-contain [
3319   ¦ .          .
3320   ¦ .d         .
3321   ¦ .e         .
3322   ¦ .╌╌╌╌╌╌    .
3323   ]
3324   assume-console [
3325   ¦ press page-up
3326   ]
3327   run [
3328   ¦ editor-event-loop screen, console, e
3329   ]
3330   screen-should-contain [
3331   ¦ .          .
3332   ¦ .          .
3333   ¦ .c         .
3334   ¦ .d         .
3335   ]
3336 ]
3337 
3338 scenario editor-scrolls-up-on-left-arrow [
3339   local-scope
3340   # screen has 1 line for menu + 3 lines
3341   assume-screen 5/width, 4/height
3342   # editor contains >3 lines
3343   s:text <- new [a
3344 b
3345 c
3346 d
3347 e]
3348   e:&:editor <- new-editor s, 0/left, 5/right
3349   editor-render screen, e
3350   # position cursor at top of second page
3351   assume-console [
3352   ¦ press page-down
3353   ]
3354   run [
3355   ¦ editor-event-loop screen, console, e
3356   ]
3357   screen-should-contain [
3358   ¦ .     .
3359   ¦ .c    .
3360   ¦ .d    .
3361   ¦ .e    .
3362   ]
3363   # now try to move left
3364   assume-console [
3365   ¦ press left-arrow
3366   ]
3367   run [
3368   ¦ editor-event-loop screen, console, e
3369   ¦ 3:num/raw <- get *e, cursor-row:offset
3370   ¦ 4:num/raw <- get *e, cursor-column:offset
3371   ]
3372   # screen scrolls
3373   screen-should-contain [
3374   ¦ .     .
3375   ¦ .b    .
3376   ¦ .c    .
3377   ¦ .d    .
3378   ]
3379   memory-should-contain [
3380   ¦ 3 <- 1
3381   ¦ 4 <- 1
3382   ]
3383 ]
3384 
3385 scenario editor-can-scroll-up-to-start-of-file [
3386   local-scope
3387   # screen has 1 line for menu + 3 lines
3388   assume-screen 10/width, 4/height
3389   # initialize editor with >3 lines
3390   s:text <- new [a
3391 b
3392 c
3393 d]
3394   e:&:editor <- new-editor s, 0/left, 10/right
3395   editor-render screen, e
3396   screen-should-contain [
3397   ¦ .          .
3398   ¦ .a         .
3399   ¦ .b         .
3400   ¦ .c         .
3401   ]
3402   # position cursor at top of second page, then try to move up to start of
3403   # text
3404   assume-console [
3405   ¦ press page-down
3406   ¦ press up-arrow
3407   ¦ press up-arrow
3408   ]
3409   run [
3410   ¦ editor-event-loop screen, console, e
3411   ]
3412   # screen slides by one line
3413   screen-should-contain [
3414   ¦ .          .
3415   ¦ .a         .
3416   ¦ .b         .
3417   ¦ .c         .
3418   ]
3419   # try to move up again
3420   assume-console [
3421   ¦ press up-arrow
3422   ]
3423   run [
3424   ¦ editor-event-loop screen, console, e
3425   ]
3426   # screen remains unchanged
3427   screen-should-contain [
3428   ¦ .          .
3429   ¦ .a         .
3430   ¦ .b         .
3431   ¦ .c         .
3432   ]
3433 ]
3434 
3435 # ctrl-f/page-down - render next page if it exists
3436 
3437 scenario editor-can-scroll [
3438   local-scope
3439   assume-screen 10/width, 4/height
3440   s:text <- new [a
3441 b
3442 c
3443 d]
3444   e:&:editor <- new-editor s, 0/left, 10/right
3445   editor-render screen, e
3446   screen-should-contain [
3447   ¦ .          .
3448   ¦ .a         .
3449   ¦ .b         .
3450   ¦ .c         .
3451   ]
3452   # scroll down
3453   assume-console [
3454   ¦ press page-down
3455   ]
3456   run [
3457   ¦ editor-event-loop screen, console, e
3458   ]
3459   # screen shows next page
3460   screen-should-contain [
3461   ¦ .          .
3462   ¦ .c         .
3463   ¦ .d         .
3464   ¦ .╌╌╌╌╌╌╌╌╌╌.
3465   ]
3466 ]
3467 
3468 after <handle-special-character> [
3469   {
3470   ¦ page-down?:bool <- equal c, 6/ctrl-f
3471   ¦ break-unless page-down?
3472   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3473   ¦ <move-cursor-begin>
3474   ¦ page-down editor
3475   ¦ undo-coalesce-tag:num <- copy 0/never
3476   ¦ <move-cursor-end>
3477   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3478   ¦ movement?:bool <- not-equal top-of-screen, old-top
3479   ¦ return movement?/go-render
3480   }
3481 ]
3482 
3483 after <handle-special-key> [
3484   {
3485   ¦ page-down?:bool <- equal k, 65518/page-down
3486   ¦ break-unless page-down?
3487   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3488   ¦ <move-cursor-begin>
3489   ¦ page-down editor
3490   ¦ undo-coalesce-tag:num <- copy 0/never
3491   ¦ <move-cursor-end>
3492   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3493   ¦ movement?:bool <- not-equal top-of-screen, old-top
3494   ¦ return movement?/go-render
3495   }
3496 ]
3497 
3498 # page-down skips entire wrapped lines, so it can't scroll past lines
3499 # taking up the entire screen
3500 def page-down editor:&:editor -> editor:&:editor [
3501   local-scope
3502   load-ingredients
3503   # if editor contents don't overflow screen, do nothing
3504   bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset
3505   return-unless bottom-of-screen
3506   # if not, position cursor at final character
3507   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3508   before-cursor:&:duplex-list:char <- prev bottom-of-screen
3509   *editor <- put *editor, before-cursor:offset, before-cursor
3510   # keep one line in common with previous page
3511   {
3512   ¦ last:char <- get *before-cursor, value:offset
3513   ¦ newline?:bool <- equal last, 10/newline
3514   ¦ break-unless newline?:bool
3515   ¦ before-cursor <- prev before-cursor
3516   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
3517   }
3518   # move cursor and top-of-screen to start of that line
3519   move-to-start-of-line editor
3520   before-cursor <- get *editor, before-cursor:offset
3521   *editor <- put *editor, top-of-screen:offset, before-cursor
3522 ]
3523 
3524 # jump to previous newline
3525 def move-to-start-of-line editor:&:editor -> editor:&:editor [
3526   local-scope
3527   load-ingredients
3528   # update cursor column
3529   left:num <- get *editor, left:offset
3530   cursor-column:num <- copy left
3531   *editor <- put *editor, cursor-column:offset, cursor-column
3532   # update before-cursor
3533   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3534   init:&:duplex-list:char <- get *editor, data:offset
3535   # while not at start of line, move
3536   {
3537   ¦ at-start-of-text?:bool <- equal before-cursor, init
3538   ¦ break-if at-start-of-text?
3539   ¦ prev:char <- get *before-cursor, value:offset
3540   ¦ at-start-of-line?:bool <- equal prev, 10/newline
3541   ¦ break-if at-start-of-line?
3542   ¦ before-cursor <- prev before-cursor
3543   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
3544   ¦ assert before-cursor, [move-to-start-of-line tried to move before start of text]
3545   ¦ loop
3546   }
3547 ]
3548 
3549 scenario editor-does-not-scroll-past-end [
3550   local-scope
3551   assume-screen 10/width, 4/height
3552   s:text <- new [a
3553 b]
3554   e:&:editor <- new-editor s, 0/left, 10/right
3555   editor-render screen, e
3556   screen-should-contain [
3557   ¦ .          .
3558   ¦ .a         .
3559   ¦ .b         .
3560   ¦ .╌╌╌╌╌╌╌╌╌╌.
3561   ]
3562   # scroll down
3563   assume-console [
3564   ¦ press page-down
3565   ]
3566   run [
3567   ¦ editor-event-loop screen, console, e
3568   ]
3569   # screen remains unmodified
3570   screen-should-contain [
3571   ¦ .          .
3572   ¦ .a         .
3573   ¦ .b         .
3574   ¦ .╌╌╌╌╌╌╌╌╌╌.
3575   ]
3576 ]
3577 
3578 scenario editor-starts-next-page-at-start-of-wrapped-line [
3579   local-scope
3580   # screen has 1 line for menu + 3 lines for text
3581   assume-screen 10/width, 4/height
3582   # editor contains a long last line
3583   s:text <- new [a
3584 b
3585 cdefgh]
3586   # editor screen triggers wrap of last line
3587   e:&:editor <- new-editor s, 0/left, 4/right
3588   editor-render screen, e
3589   # some part of last line is not displayed
3590   screen-should-contain [
3591   ¦ .          .
3592   ¦ .a         .
3593   ¦ .b         .
3594   ¦ .cde↩      .
3595   ]
3596   # scroll down
3597   assume-console [
3598   ¦ press page-down
3599   ]
3600   run [
3601   ¦ editor-event-loop screen, console, e
3602   ]
3603   # screen shows entire wrapped line
3604   screen-should-contain [
3605   ¦ .          .
3606   ¦ .cde↩      .
3607   ¦ .fgh       .
3608   ¦ .╌╌╌╌      .
3609   ]
3610 ]
3611 
3612 scenario editor-starts-next-page-at-start-of-wrapped-line-2 [
3613   local-scope
3614   # screen has 1 line for menu + 3 lines for text
3615   assume-screen 10/width, 4/height
3616   # editor contains a very long line that occupies last two lines of screen
3617   # and still has something left over
3618   s:text <- new [a
3619 bcdefgh]
3620   e:&:editor <- new-editor s, 0/left, 4/right
3621   editor-render screen, e
3622   # some part of last line is not displayed
3623   screen-should-contain [
3624   ¦ .          .
3625   ¦ .a         .
3626   ¦ .bcd↩      .
3627   ¦ .efg↩      .
3628   ]
3629   # scroll down
3630   assume-console [
3631   ¦ press page-down
3632   ]
3633   run [
3634   ¦ editor-event-loop screen, console, e
3635   ]
3636   # screen shows entire wrapped line
3637   screen-should-contain [
3638   ¦ .          .
3639   ¦ .bcd↩      .
3640   ¦ .efg↩      .
3641   ¦ .h         .
3642   ]
3643 ]
3644 
3645 # ctrl-b/page-up - render previous page if it exists
3646 
3647 scenario editor-can-scroll-up [
3648   local-scope
3649   assume-screen 10/width, 4/height
3650   s:text <- new [a
3651 b
3652 c
3653 d]
3654   e:&:editor <- new-editor s, 0/left, 10/right
3655   editor-render screen, e
3656   screen-should-contain [
3657   ¦ .          .
3658   ¦ .a         .
3659   ¦ .b         .
3660   ¦ .c         .
3661   ]
3662   # scroll down
3663   assume-console [
3664   ¦ press page-down
3665   ]
3666   run [
3667   ¦ editor-event-loop screen, console, e
3668   ]
3669   # screen shows next page
3670   screen-should-contain [
3671   ¦ .          .
3672   ¦ .c         .
3673   ¦ .d         .
3674   ¦ .╌╌╌╌╌╌╌╌╌╌.
3675   ]
3676   # scroll back up
3677   assume-console [
3678   ¦ press page-up
3679   ]
3680   run [
3681   ¦ editor-event-loop screen, console, e
3682   ]
3683   # screen shows original page again
3684   screen-should-contain [
3685   ¦ .          .
3686   ¦ .a         .
3687   ¦ .b         .
3688   ¦ .c         .
3689   ]
3690 ]
3691 
3692 after <handle-special-character> [
3693   {
3694   ¦ page-up?:bool <- equal c, 2/ctrl-b
3695   ¦ break-unless page-up?
3696   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3697   ¦ <move-cursor-begin>
3698   ¦ editor <- page-up editor, screen-height
3699   ¦ undo-coalesce-tag:num <- copy 0/never
3700   ¦ <move-cursor-end>
3701   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3702   ¦ movement?:bool <- not-equal top-of-screen, old-top
3703   ¦ return movement?/go-render
3704   }
3705 ]
3706 
3707 after <handle-special-key> [
3708   {
3709   ¦ page-up?:bool <- equal k, 65519/page-up
3710   ¦ break-unless page-up?
3711   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3712   ¦ <move-cursor-begin>
3713   ¦ editor <- page-up editor, screen-height
3714   ¦ undo-coalesce-tag:num <- copy 0/never
3715   ¦ <move-cursor-end>
3716   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3717   ¦ movement?:bool <- not-equal top-of-screen, old-top
3718   ¦ # don't bother re-rendering if nothing changed. todo: test this
3719   ¦ return movement?/go-render
3720   }
3721 ]
3722 
3723 def page-up editor:&:editor, screen-height:num -> editor:&:editor [
3724   local-scope
3725   load-ingredients
3726   max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line
3727   count:num <- copy 0
3728   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3729   {
3730   ¦ done?:bool <- greater-or-equal count, max
3731   ¦ break-if done?
3732   ¦ prev:&:duplex-list:char <- before-previous-screen-line top-of-screen, editor
3733   ¦ break-unless prev
3734   ¦ top-of-screen <- copy prev
3735   ¦ *editor <- put *editor, top-of-screen:offset, top-of-screen
3736   ¦ count <- add count, 1
3737   ¦ loop
3738   }
3739 ]
3740 
3741 scenario editor-can-scroll-up-multiple-pages [
3742   local-scope
3743   # screen has 1 line for menu + 3 lines
3744   assume-screen 10/width, 4/height
3745   # initialize editor with 8 lines
3746   s:text <- new [a
3747 b
3748 c
3749 d
3750 e
3751 f
3752 g
3753 h]
3754   e:&:editor <- new-editor s, 0/left, 10/right
3755   editor-render screen, e
3756   screen-should-contain [
3757   ¦ .          .
3758   ¦ .a         .
3759   ¦ .b         .
3760   ¦ .c         .
3761   ]
3762   # scroll down two pages
3763   assume-console [
3764   ¦ press page-down
3765   ¦ press page-down
3766   ]
3767   run [
3768   ¦ editor-event-loop screen, console, e
3769   ]
3770   # screen shows third page
3771   screen-should-contain [
3772   ¦ .          .
3773   ¦ .e         .
3774   ¦ .f         .
3775   ¦ .g         .
3776   ]
3777   # scroll up
3778   assume-console [
3779   ¦ press page-up
3780   ]
3781   run [
3782   ¦ editor-event-loop screen, console, e
3783   ]
3784   # screen shows second page
3785   screen-should-contain [
3786   ¦ .          .
3787   ¦ .c         .
3788   ¦ .d         .
3789   ¦ .e         .
3790   ]
3791   # scroll up again
3792   assume-console [
3793   ¦ press page-up
3794   ]
3795   run [
3796   ¦ editor-event-loop screen, console, e
3797   ]
3798   # screen shows original page again
3799   screen-should-contain [
3800   ¦ .          .
3801   ¦ .a         .
3802   ¦ .b         .
3803   ¦ .c         .
3804   ]
3805 ]
3806 
3807 scenario editor-can-scroll-up-wrapped-lines [
3808   local-scope
3809   # screen has 1 line for menu + 5 lines for text
3810   assume-screen 10/width, 6/height
3811   # editor contains a long line in the first page
3812   s:text <- new [a
3813 b
3814 cdefgh
3815 i
3816 j
3817 k
3818 l
3819 m
3820 n
3821 o]
3822   # editor screen triggers wrap of last line
3823   e:&:editor <- new-editor s, 0/left, 4/right
3824   editor-render screen, e
3825   # some part of last line is not displayed
3826   screen-should-contain [
3827   ¦ .          .
3828   ¦ .a         .
3829   ¦ .b         .
3830   ¦ .cde↩      .
3831   ¦ .fgh       .
3832   ¦ .i         .
3833   ]
3834   # scroll down a page and a line
3835   assume-console [
3836   ¦ press page-down
3837   ¦ left-click 5, 0
3838   ¦ press down-arrow
3839   ]
3840   run [
3841   ¦ editor-event-loop screen, console, e
3842   ]
3843   # screen shows entire wrapped line
3844   screen-should-contain [
3845   ¦ .          .
3846   ¦ .j         .
3847   ¦ .k         .
3848   ¦ .l         .
3849   ¦ .m         .
3850   ¦ .n         .
3851   ]
3852   # now scroll up one page
3853   assume-console [
3854   ¦ press page-up
3855   ]
3856   run [
3857   ¦ editor-event-loop screen, console, e
3858   ]
3859   # screen resets
3860   screen-should-contain [
3861   ¦ .          .
3862   ¦ .b         .
3863   ¦ .cde↩      .
3864   ¦ .fgh       .
3865   ¦ .i         .
3866   ¦ .j         .
3867   ]
3868 ]
3869 
3870 scenario editor-can-scroll-up-wrapped-lines-2 [
3871   local-scope
3872   # screen has 1 line for menu + 3 lines for text
3873   assume-screen 10/width, 4/height
3874   # editor contains a very long line that occupies last two lines of screen
3875   # and still has something left over
3876   s:text <- new [a
3877 bcdefgh]
3878   e:&:editor <- new-editor s, 0/left, 4/right
3879   editor-render screen, e
3880   # some part of last line is not displayed
3881   screen-should-contain [
3882   ¦ .          .
3883   ¦ .a         .
3884   ¦ .bcd↩      .
3885   ¦ .efg↩      .
3886   ]
3887   # scroll down
3888   assume-console [
3889   ¦ press page-down
3890   ]
3891   run [
3892   ¦ editor-event-loop screen, console, e
3893   ]
3894   # screen shows entire wrapped line
3895   screen-should-contain [
3896   ¦ .          .
3897   ¦ .bcd↩      .
3898   ¦ .efg↩      .
3899   ¦ .h         .
3900   ]
3901   # scroll back up
3902   assume-console [
3903   ¦ press page-up
3904   ]
3905   run [
3906   ¦ editor-event-loop screen, console, e
3907   ]
3908   # screen resets
3909   screen-should-contain [
3910   ¦ .          .
3911   ¦ .a         .
3912   ¦ .bcd↩      .
3913   ¦ .efg↩      .
3914   ]
3915 ]
3916 
3917 scenario editor-can-scroll-up-past-nonempty-lines [
3918   local-scope
3919   assume-screen 10/width, 4/height
3920   # text with empty line in second screen
3921   s:text <- new [axx
3922 bxx
3923 cxx
3924 dxx
3925 exx
3926 fxx
3927 gxx
3928 hxx
3929 ]
3930   e:&:editor <- new-editor s, 0/left, 4/right
3931   editor-render screen, e
3932   screen-should-contain [
3933   ¦ .          .
3934   ¦ .axx       .
3935   ¦ .bxx       .
3936   ¦ .cxx       .
3937   ]
3938   assume-console [
3939   ¦ press page-down
3940   ]
3941   run [
3942   ¦ editor-event-loop screen, console, e
3943   ]
3944   screen-should-contain [
3945   ¦ .          .
3946   ¦ .cxx       .
3947   ¦ .dxx       .
3948   ¦ .exx       .
3949   ]
3950   assume-console [
3951   ¦ press page-down
3952   ]
3953   run [
3954   ¦ editor-event-loop screen, console, e
3955   ]
3956   screen-should-contain [
3957   ¦ .          .
3958   ¦ .exx       .
3959   ¦ .fxx       .
3960   ¦ .gxx       .
3961   ]
3962   # scroll back up past empty line
3963   assume-console [
3964   ¦ press page-up
3965   ]
3966   run [
3967   ¦ editor-event-loop screen, console, e
3968   ]
3969   screen-should-contain [
3970   ¦ .          .
3971   ¦ .cxx       .
3972   ¦ .dxx       .
3973   ¦ .exx       .
3974   ]
3975 ]
3976 
3977 scenario editor-can-scroll-up-past-empty-lines [
3978   local-scope
3979   assume-screen 10/width, 4/height
3980   # text with empty line in second screen
3981   s:text <- new [axy
3982 bxy
3983 cxy
3984 
3985 dxy
3986 exy
3987 fxy
3988 gxy
3989 ]
3990   e:&:editor <- new-editor s, 0/left, 4/right
3991   editor-render screen, e
3992   screen-should-contain [
3993   ¦ .          .
3994   ¦ .axy       .
3995   ¦ .bxy       .
3996   ¦ .cxy       .
3997   ]
3998   assume-console [
3999   ¦ press page-down
4000   ]
4001   run [
4002   ¦ editor-event-loop screen, console, e
4003   ]
4004   screen-should-contain [
4005   ¦ .          .
4006   ¦ .cxy       .
4007   ¦ .          .
4008   ¦ .dxy       .
4009   ]
4010   assume-console [
4011   ¦ press page-down
4012   ]
4013   run [
4014   ¦ editor-event-loop screen, console, e
4015   ]
4016   screen-should-contain [
4017   ¦ .          .
4018   ¦ .dxy       .
4019   ¦ .exy       .
4020   ¦ .fxy       .
4021   ]
4022   # scroll back up past empty line
4023   assume-console [
4024   ¦ press page-up
4025   ]
4026   run [
4027   ¦ editor-event-loop screen, console, e
4028   ]
4029   screen-should-contain [
4030   ¦ .          .
4031   ¦ .cxy       .
4032   ¦ .          .
4033   ¦ .dxy       .
4034   ]
4035 ]
4036 
4037 # ctrl-s - scroll up by one line
4038 # todo: scenarios
4039 
4040 after <handle-special-character> [
4041   {
4042   ¦ scroll-up?:bool <- equal c, 19/ctrl-s
4043   ¦ break-unless scroll-up?
4044   ¦ <move-cursor-begin>
4045   ¦ go-render?:bool, editor <- line-up editor, screen-height
4046   ¦ undo-coalesce-tag:num <- copy 5/line-up
4047   ¦ <move-cursor-end>
4048   ¦ return go-render?
4049   }
4050 ]
4051 
4052 def line-up editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
4053   local-scope
4054   load-ingredients
4055   left:num <- get *editor, left:offset
4056   right:num <- get *editor, right:offset
4057   max:num <- subtract right, left
4058   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
4059   new-top:&:duplex-list:char <- before-start-of-next-line old-top, max
4060   movement?:bool <- not-equal old-top, new-top
4061   {
4062   ¦ break-unless movement?
4063   ¦ *editor <- put *editor, top-of-screen:offset, new-top
4064   }
4065   return movement?
4066 ]
4067 
4068 # ctrl-x - scroll down by one line
4069 # todo: scenarios
4070 
4071 after <handle-special-character> [
4072   {
4073   ¦ scroll-down?:bool <- equal c, 24/ctrl-x
4074   ¦ break-unless scroll-down?
4075   ¦ <move-cursor-begin>
4076   ¦ go-render?:bool, editor <- line-down editor, screen-height
4077   ¦ undo-coalesce-tag:num <- copy 6/line-down
4078   ¦ <move-cursor-end>
4079   ¦ return go-render?
4080   }
4081 ]
4082 
4083 def line-down editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
4084   local-scope
4085   load-ingredients
4086   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
4087   new-top:&:duplex-list:char <- before-previous-screen-line old-top, editor
4088   movement?:bool <- not-equal old-top, new-top
4089   {
4090   ¦ break-unless movement?
4091   ¦ *editor <- put *editor, top-of-screen:offset, new-top
4092   }
4093   return movement?
4094 ]
4095 
4096 # ctrl-t - move current line to top of screen
4097 # todo: scenarios
4098 
4099 after <handle-special-character> [
4100   {
4101   ¦ scroll-down?:bool <- equal c, 20/ctrl-t
4102   ¦ break-unless scroll-down?
4103   ¦ <move-cursor-begin>
4104   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
4105   ¦ cursor:&:duplex-list:char <- get *editor, before-cursor:offset
4106   ¦ cursor <- next cursor
4107   ¦ new-top:&:duplex-list:char <- before-previous-screen-line cursor, editor
4108   ¦ *editor <- put *editor, top-of-screen:offset, new-top
4109   ¦ *editor <- put *editor, cursor-row:offset, 1
4110   ¦ go-render?:bool <- not-equal new-top, old-top
4111   ¦ undo-coalesce-tag:num <- copy 0/never
4112   ¦ <move-cursor-end>
4113   ¦ return go-render?
4114   }
4115 ]
4116 
4117 # ctrl-/ - comment/uncomment current line
4118 
4119 after <handle-special-character> [
4120   {
4121   ¦ comment-toggle?:bool <- equal c, 31/ctrl-slash
4122   ¦ break-unless comment-toggle?
4123   ¦ cursor-column:num <- get *editor, cursor-column:offset
4124   ¦ data:&:duplex-list:char <- get *editor, data:offset
4125   ¦ <insert-character-begin>
4126   ¦ before-line-start:&:duplex-list:char <- before-start-of-screen-line editor
4127   ¦ line-start:&:duplex-list:char <- next before-line-start
4128   ¦ commented-out?:bool <- match line-start, [#? ]  # comment prefix
4129   ¦ {
4130   ¦ ¦ break-unless commented-out?
4131   ¦ ¦ # uncomment
4132   ¦ ¦ data <- remove line-start, 3/length-comment-prefix, data
4133   ¦ ¦ cursor-column <- subtract cursor-column, 3/length-comment-prefix
4134   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
4135   ¦ ¦ go-render? <- render-line-from-start screen, editor, 3/size-of-comment-leader
4136   ¦ }
4137   ¦ {
4138   ¦ ¦ break-if commented-out?
4139   ¦ ¦ # comment
4140   ¦ ¦ insert before-line-start, [#? ]
4141   ¦ ¦ cursor-column <- add cursor-column, 3/length-comment-prefix
4142   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
4143   ¦ ¦ go-render? <- render-line-from-start screen, editor, 0
4144   ¦ }
4145   ¦ <insert-character-end>
4146   ¦ return
4147   }
4148 ]
4149 
4150 # Render just from the start of the current line, and only if it wasn't
4151 # wrapping before (include margin) and isn't wrapping now. Otherwise just tell
4152 # the caller to go-render? the entire screen.
4153 def render-line-from-start screen:&:screen, editor:&:editor, right-margin:num -> go-render?:bool, screen:&:screen [
4154   local-scope
4155   load-ingredients
4156   before-line-start:&:duplex-list:char <- before-start-of-screen-line editor
4157   line-start:&:duplex-list:char <- next before-line-start
4158   color:num <- copy 7/white
4159   left:num <- get *editor, left:offset
4160   cursor-row:num <- get *editor, cursor-row:offset
4161   screen <- move-cursor screen, cursor-row, left
4162   right:num <- get *editor, right:offset
4163   end:num <- subtract right, right-margin
4164   i:num <- copy 0
4165   curr:&:duplex-list:char <- copy line-start
4166   {
4167   ¦ render-all?:bool <- greater-or-equal i, end
4168   ¦ return-if render-all?, 1/go-render
4169   ¦ break-unless curr
4170   ¦ c:char <- get *curr, value:offset
4171   ¦ newline?:bool <- equal c, 10/newline
4172   ¦ break-if newline?
4173   ¦ color <- get-color color, c
4174   ¦ print screen, c, color
4175   ¦ curr <- next curr
4176   ¦ i <- add i, 1
4177   ¦ loop
4178   }
4179   clear-line-until screen, right
4180   return 0/dont-render
4181 ]
4182 
4183 def before-start-of-screen-line editor:&:editor -> result:&:duplex-list:char [
4184   local-scope
4185   load-ingredients
4186   cursor:&:duplex-list:char <- get *editor, before-cursor:offset
4187   {
4188   ¦ next:&:duplex-list:char <- next cursor
4189   ¦ break-unless next
4190   ¦ cursor <- copy next
4191   }
4192   result <- before-previous-screen-line cursor, editor
4193 ]
4194 
4195 scenario editor-comments-empty-line [
4196   local-scope
4197   assume-screen 10/width, 5/height
4198   e:&:editor <- new-editor [], 0/left, 5/right
4199   editor-render screen, e
4200   $clear-trace
4201   assume-console [
4202   ¦ press ctrl-slash
4203   ]
4204   run [
4205   ¦ editor-event-loop screen, console, e
4206   ¦ 4:num/raw <- get *e, cursor-row:offset
4207   ¦ 5:num/raw <- get *e, cursor-column:offset
4208   ]
4209   screen-should-contain [
4210   ¦ .          .
4211   ¦ .#?        .
4212   ¦ .╌╌╌╌╌     .
4213   ¦ .          .
4214   ]
4215   memory-should-contain [
4216   ¦ 4 <- 1
4217   ¦ 5 <- 3
4218   ]
4219   check-trace-count-for-label 5, [print-character]
4220 ]
4221 
4222 scenario editor-comments-at-start-of-contents [
4223   local-scope
4224   assume-screen 10/width, 5/height
4225   e:&:editor <- new-editor [ab], 0/left, 10/right
4226   editor-render screen, e
4227   $clear-trace
4228   assume-console [
4229   ¦ press ctrl-slash
4230   ]
4231   run [
4232   ¦ editor-event-loop screen, console, e
4233   ¦ 4:num/raw <- get *e, cursor-row:offset
4234   ¦ 5:num/raw <- get *e, cursor-column:offset
4235   ]
4236   screen-should-contain [
4237   ¦ .          .
4238   ¦ .#? ab     .
4239   ¦ .╌╌╌╌╌╌╌╌╌╌.
4240   ¦ .          .
4241   ]
4242   memory-should-contain [
4243   ¦ 4 <- 1
4244   ¦ 5 <- 3
4245   ]
4246   check-trace-count-for-label 10, [print-character]
4247 ]
4248 
4249 scenario editor-comments-at-end-of-contents [
4250   local-scope
4251   assume-screen 10/width, 5/height
4252   e:&:editor <- new-editor [ab], 0/left, 10/right
4253   editor-render screen, e
4254   $clear-trace
4255   assume-console [
4256   ¦ left-click 1, 7
4257   ¦ press ctrl-slash
4258   ]
4259   run [
4260   ¦ editor-event-loop screen, console, e
4261   ¦ 4:num/raw <- get *e, cursor-row:offset
4262   ¦ 5:num/raw <- get *e, cursor-column:offset
4263   ]
4264   screen-should-contain [
4265   ¦ .          .
4266   ¦ .#? ab     .
4267   ¦ .╌╌╌╌╌╌╌╌╌╌.
4268   ¦ .          .
4269   ]
4270   memory-should-contain [
4271   ¦ 4 <- 1
4272   ¦ 5 <- 5
4273   ]
4274   check-trace-count-for-label 10, [print-character]
4275   # toggle to uncomment
4276   $clear-trace
4277   assume-console [
4278   ¦ press ctrl-slash
4279   ]
4280   run [
4281   ¦ editor-event-loop screen, console, e
4282   ¦ 4:num/raw <- get *e, cursor-row:offset
4283   ¦ 5:num/raw <- get *e, cursor-column:offset
4284   ]
4285   screen-should-contain [
4286   ¦ .          .
4287   ¦ .ab        .
4288   ¦ .╌╌╌╌╌╌╌╌╌╌.
4289   ¦ .          .
4290   ]
4291   check-trace-count-for-label 10, [print-character]
4292 ]
4293 
4294 scenario editor-comments-almost-wrapping-line [
4295   local-scope
4296   assume-screen 10/width, 5/height
4297   # editor starts out with a non-wrapping line
4298   e:&:editor <- new-editor [abcd], 0/left, 5/right
4299   editor-render screen, e
4300   screen-should-contain [
4301   ¦ .          .
4302   ¦ .abcd      .
4303   ¦ .╌╌╌╌╌     .
4304   ¦ .          .
4305   ]
4306   $clear-trace
4307   # on commenting the line is now wrapped
4308   assume-console [
4309   ¦ left-click 1, 7
4310   ¦ press ctrl-slash
4311   ]
4312   run [
4313   ¦ editor-event-loop screen, console, e
4314   ]
4315   screen-should-contain [
4316   ¦ .          .
4317   ¦ .#? a↩     .
4318   ¦ .bcd       .
4319   ¦ .╌╌╌╌╌     .
4320   ¦ .          .
4321   ]
4322 ]
4323 
4324 scenario editor-uncomments-just-wrapping-line [
4325   local-scope
4326   assume-screen 10/width, 5/height
4327   # editor starts out with a comment that wraps the line
4328   e:&:editor <- new-editor [#? ab], 0/left, 5/right
4329   editor-render screen, e
4330   screen-should-contain [
4331   ¦ .          .
4332   ¦ .#? a↩     .
4333   ¦ .b         .
4334   ¦ .╌╌╌╌╌     .
4335   ¦ .          .
4336   ]
4337   $clear-trace
4338   # on uncommenting the line is no longer wrapped
4339   assume-console [
4340   ¦ left-click 1, 7
4341   ¦ press ctrl-slash
4342   ]
4343   run [
4344   ¦ editor-event-loop screen, console, e
4345   ]
4346   screen-should-contain [
4347   ¦ .          .
4348   ¦ .ab        .
4349   ¦ .╌╌╌╌╌     .
4350   ¦ .          .
4351   ]
4352 ]