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