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   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   s:text <- new [abcd]
  34   e:&:editor <- new-editor s, 0/left, 5/right
  35   render screen, e
  36   $clear-trace
  37   assume-console [
  38   ¦ press tab
  39   ]
  40   run [
  41   ¦ editor-event-loop screen, console, e
  42   ]
  43   screen-should-contain [
  44   ¦ .          .
  45   ¦ .  ab↩     .
  46   ¦ .cd        .
  47   ]
  48   # we re-render the whole editor
  49   check-trace-count-for-label-greater-than 10, [print-character]
  50 ]
  51 
  52 after <handle-special-character> [
  53   {
  54   ¦ tab?:bool <- equal c, 9/tab
  55   ¦ break-unless tab?
  56   ¦ <insert-character-begin>
  57   ¦ # todo: decompose insert-at-cursor into editor update and screen update,
  58   ¦ # so that 'tab' doesn't render the current line multiple times
  59   ¦ insert-at-cursor editor, 32/space, screen
  60   ¦ go-render? <- insert-at-cursor editor, 32/space, screen
  61   ¦ <insert-character-end>
  62   ¦ return
  63   }
  64 ]
  65 
  66 # backspace - delete character before cursor
  67 
  68 scenario editor-handles-backspace-key [
  69   local-scope
  70   assume-screen 10/width, 5/height
  71   e:&:editor <- new-editor [abc], 0/left, 10/right
  72   editor-render screen, e
  73   $clear-trace
  74   assume-console [
  75   ¦ left-click 1, 1
  76   ¦ press backspace
  77   ]
  78   run [
  79   ¦ editor-event-loop screen, console, e
  80   ¦ 4:num/raw <- get *e, cursor-row:offset
  81   ¦ 5:num/raw <- get *e, cursor-column:offset
  82   ]
  83   screen-should-contain [
  84   ¦ .          .
  85   ¦ .bc        .
  86   ¦ .╌╌╌╌╌╌╌╌╌╌.
  87   ¦ .          .
  88   ]
  89   memory-should-contain [
  90   ¦ 4 <- 1
  91   ¦ 5 <- 0
  92   ]
  93   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
  94 ]
  95 
  96 after <handle-special-character> [
  97   {
  98   ¦ delete-previous-character?:bool <- equal c, 8/backspace
  99   ¦ break-unless delete-previous-character?
 100   ¦ <backspace-character-begin>
 101   ¦ go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen
 102   ¦ <backspace-character-end>
 103   ¦ return
 104   }
 105 ]
 106 
 107 # return values:
 108 #   go-render? - whether caller needs to update the screen
 109 #   backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc.
 110 def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
 111   local-scope
 112   load-ingredients
 113   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 114   data:&:duplex-list:char <- get *editor, data:offset
 115   # if at start of text (before-cursor at § sentinel), return
 116   prev:&:duplex-list:char <- prev before-cursor
 117   return-unless prev, 0/no-more-render, 0/nothing-deleted
 118   trace 10, [app], [delete-before-cursor]
 119   original-row:num <- get *editor, cursor-row:offset
 120   scroll?:bool <- move-cursor-coordinates-left editor
 121   backspaced-cell:&:duplex-list:char <- copy before-cursor
 122   data <- remove before-cursor, data  # will also neatly trim next/prev pointers in backspaced-cell/before-cursor
 123   before-cursor <- copy prev
 124   *editor <- put *editor, before-cursor:offset, before-cursor
 125   return-if scroll?, 1/go-render
 126   screen-width:num <- screen-width screen
 127   cursor-row:num <- get *editor, cursor-row:offset
 128   cursor-column:num <- get *editor, cursor-column:offset
 129   # did we just backspace over a newline?
 130   same-row?:bool <- equal cursor-row, original-row
 131   return-unless same-row?, 1/go-render
 132   left:num <- get *editor, left:offset
 133   right:num <- get *editor, right:offset
 134   curr:&:duplex-list:char <- next before-cursor
 135   screen <- move-cursor screen, cursor-row, cursor-column
 136   curr-column:num <- copy cursor-column
 137   {
 138   ¦ # hit right margin? give up and let caller render
 139   ¦ at-right?:bool <- greater-or-equal curr-column, right
 140   ¦ return-if at-right?, 1/go-render
 141   ¦ break-unless curr
 142   ¦ # newline? done.
 143   ¦ currc:char <- get *curr, value:offset
 144   ¦ at-newline?:bool <- equal currc, 10/newline
 145   ¦ break-if at-newline?
 146   ¦ screen <- print screen, currc
 147   ¦ curr-column <- add curr-column, 1
 148   ¦ curr <- next curr
 149   ¦ loop
 150   }
 151   # we're guaranteed not to be at the right margin
 152   space:char <- copy 32/space
 153   screen <- print screen, space
 154   go-render? <- copy 0/false
 155 ]
 156 
 157 def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [
 158   local-scope
 159   load-ingredients
 160   go-render?:bool <- copy 0/false
 161   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 162   cursor-row:num <- get *editor, cursor-row:offset
 163   cursor-column:num <- get *editor, cursor-column:offset
 164   left:num <- get *editor, left:offset
 165   # if not at left margin, move one character left
 166   {
 167   ¦ at-left-margin?:bool <- equal cursor-column, left
 168   ¦ break-if at-left-margin?
 169   ¦ trace 10, [app], [decrementing cursor column]
 170   ¦ cursor-column <- subtract cursor-column, 1
 171   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 172   ¦ return
 173   }
 174   # if at left margin, we must move to previous row:
 175   top-of-screen?:bool <- equal cursor-row, 1  # exclude menu bar
 176   {
 177   ¦ break-if top-of-screen?
 178   ¦ cursor-row <- subtract cursor-row, 1
 179   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 180   }
 181   {
 182   ¦ break-unless top-of-screen?
 183   ¦ <scroll-up>
 184   ¦ go-render? <- copy 1/true
 185   }
 186   {
 187   ¦ # case 1: if previous character was newline, figure out how long the previous line is
 188   ¦ previous-character:char <- get *before-cursor, value:offset
 189   ¦ previous-character-is-newline?:bool <- equal previous-character, 10/newline
 190   ¦ break-unless previous-character-is-newline?
 191   ¦ # compute length of previous line
 192   ¦ trace 10, [app], [switching to previous line]
 193   ¦ d:&:duplex-list:char <- get *editor, data:offset
 194   ¦ end-of-line:num <- previous-line-length before-cursor, d
 195   ¦ right:num <- get *editor, right:offset
 196   ¦ width:num <- subtract right, left
 197   ¦ wrap?:bool <- greater-than end-of-line, width
 198   ¦ {
 199   ¦ ¦ break-unless wrap?
 200   ¦ ¦ _, column-offset:num <- divide-with-remainder end-of-line, width
 201   ¦ ¦ cursor-column <- add left, column-offset
 202   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 203   ¦ }
 204   ¦ {
 205   ¦ ¦ break-if wrap?
 206   ¦ ¦ cursor-column <- add left, end-of-line
 207   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 208   ¦ }
 209   ¦ return
 210   }
 211   # case 2: if previous-character was not newline, we're just at a wrapped line
 212   trace 10, [app], [wrapping to previous line]
 213   right:num <- get *editor, right:offset
 214   cursor-column <- subtract right, 1  # leave room for wrap icon
 215   *editor <- put *editor, cursor-column:offset, cursor-column
 216 ]
 217 
 218 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts
 219 # the length of the previous line before the 'curr' pointer.
 220 def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [
 221   local-scope
 222   load-ingredients
 223   result:num <- copy 0
 224   return-unless curr
 225   at-start?:bool <- equal curr, start
 226   return-if at-start?
 227   {
 228   ¦ curr <- prev curr
 229   ¦ break-unless curr
 230   ¦ at-start?:bool <- equal curr, start
 231   ¦ break-if at-start?
 232   ¦ c:char <- get *curr, value:offset
 233   ¦ at-newline?:bool <- equal c, 10/newline
 234   ¦ break-if at-newline?
 235   ¦ result <- add result, 1
 236   ¦ loop
 237   }
 238 ]
 239 
 240 scenario editor-clears-last-line-on-backspace [
 241   local-scope
 242   assume-screen 10/width, 5/height
 243   s:text <- new [ab
 244 cd]
 245   e:&:editor <- new-editor s, 0/left, 10/right
 246   assume-console [
 247   ¦ left-click 2, 0
 248   ¦ press backspace
 249   ]
 250   run [
 251   ¦ editor-event-loop screen, console, e
 252   ¦ 4:num/raw <- get *e, cursor-row:offset
 253   ¦ 5:num/raw <- get *e, cursor-column:offset
 254   ]
 255   screen-should-contain [
 256   ¦ .          .
 257   ¦ .abcd      .
 258   ¦ .╌╌╌╌╌╌╌╌╌╌.
 259   ¦ .          .
 260   ]
 261   memory-should-contain [
 262   ¦ 4 <- 1
 263   ¦ 5 <- 2
 264   ]
 265 ]
 266 
 267 scenario editor-joins-and-wraps-lines-on-backspace [
 268   local-scope
 269   assume-screen 10/width, 5/height
 270   # initialize editor with two long-ish but non-wrapping lines
 271   s:text <- new [abc def
 272 ghi jkl]
 273   e:&:editor <- new-editor s, 0/left, 10/right
 274   editor-render screen, e
 275   $clear-trace
 276   # position the cursor at the start of the second and hit backspace
 277   assume-console [
 278   ¦ left-click 2, 0
 279   ¦ press backspace
 280   ]
 281   run [
 282   ¦ editor-event-loop screen, console, e
 283   ]
 284   # resulting single line should wrap correctly
 285   screen-should-contain [
 286   ¦ .          .
 287   ¦ .abc defgh↩.
 288   ¦ .i jkl     .
 289   ¦ .╌╌╌╌╌╌╌╌╌╌.
 290   ¦ .          .
 291   ]
 292 ]
 293 
 294 scenario editor-wraps-long-lines-on-backspace [
 295   local-scope
 296   assume-screen 10/width, 5/height
 297   # initialize editor in part of the screen with a long line
 298   e:&:editor <- new-editor [abc def ghij], 0/left, 8/right
 299   editor-render screen, e
 300   # confirm that it wraps
 301   screen-should-contain [
 302   ¦ .          .
 303   ¦ .abc def↩  .
 304   ¦ . ghij     .
 305   ¦ .╌╌╌╌╌╌╌╌  .
 306   ]
 307   $clear-trace
 308   # position the cursor somewhere in the middle of the top screen line and hit backspace
 309   assume-console [
 310   ¦ left-click 1, 4
 311   ¦ press backspace
 312   ]
 313   run [
 314   ¦ editor-event-loop screen, console, e
 315   ]
 316   # resulting single line should wrap correctly and not overflow its bounds
 317   screen-should-contain [
 318   ¦ .          .
 319   ¦ .abcdef ↩  .
 320   ¦ .ghij      .
 321   ¦ .╌╌╌╌╌╌╌╌  .
 322   ¦ .          .
 323   ]
 324 ]
 325 
 326 # delete - delete character at cursor
 327 
 328 scenario editor-handles-delete-key [
 329   local-scope
 330   assume-screen 10/width, 5/height
 331   e:&:editor <- new-editor [abc], 0/left, 10/right
 332   editor-render screen, e
 333   $clear-trace
 334   assume-console [
 335   ¦ press delete
 336   ]
 337   run [
 338   ¦ editor-event-loop screen, console, e
 339   ]
 340   screen-should-contain [
 341   ¦ .          .
 342   ¦ .bc        .
 343   ¦ .╌╌╌╌╌╌╌╌╌╌.
 344   ¦ .          .
 345   ]
 346   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
 347   $clear-trace
 348   assume-console [
 349   ¦ press delete
 350   ]
 351   run [
 352   ¦ editor-event-loop screen, console, e
 353   ]
 354   screen-should-contain [
 355   ¦ .          .
 356   ¦ .c         .
 357   ¦ .╌╌╌╌╌╌╌╌╌╌.
 358   ¦ .          .
 359   ]
 360   check-trace-count-for-label 2, [print-character]  # new length to overwrite
 361 ]
 362 
 363 after <handle-special-key> [
 364   {
 365   ¦ delete-next-character?:bool <- equal k, 65522/delete
 366   ¦ break-unless delete-next-character?
 367   ¦ <delete-character-begin>
 368   ¦ go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen
 369   ¦ <delete-character-end>
 370   ¦ return
 371   }
 372 ]
 373 
 374 def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
 375   local-scope
 376   load-ingredients
 377   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 378   data:&:duplex-list:char <- get *editor, data:offset
 379   deleted-cell:&:duplex-list:char <- next before-cursor
 380   return-unless deleted-cell, 0/don't-render
 381   currc:char <- get *deleted-cell, value:offset
 382   data <- remove deleted-cell, data
 383   deleted-newline?:bool <- equal currc, 10/newline
 384   return-if deleted-newline?, 1/go-render
 385   # wasn't a newline? render rest of line
 386   curr:&:duplex-list:char <- next before-cursor  # refresh after remove above
 387   cursor-row:num <- get *editor, cursor-row:offset
 388   cursor-column:num <- get *editor, cursor-column:offset
 389   screen <- move-cursor screen, cursor-row, cursor-column
 390   curr-column:num <- copy cursor-column
 391   screen-width:num <- screen-width screen
 392   {
 393   ¦ # hit right margin? give up and let caller render
 394   ¦ at-right?:bool <- greater-or-equal curr-column, screen-width
 395   ¦ return-if at-right?, 1/go-render
 396   ¦ break-unless curr
 397   ¦ # newline? done.
 398   ¦ currc:char <- get *curr, value:offset
 399   ¦ at-newline?:bool <- equal currc, 10/newline
 400   ¦ break-if at-newline?
 401   ¦ screen <- print screen, currc
 402   ¦ curr-column <- add curr-column, 1
 403   ¦ curr <- next curr
 404   ¦ loop
 405   }
 406   # we're guaranteed not to be at the right margin
 407   space:char <- copy 32/space
 408   screen <- print screen, space
 409   go-render? <- copy 0/false
 410 ]
 411 
 412 # right arrow
 413 
 414 scenario editor-moves-cursor-right-with-key [
 415   local-scope
 416   assume-screen 10/width, 5/height
 417   e:&:editor <- new-editor [abc], 0/left, 10/right
 418   editor-render screen, e
 419   $clear-trace
 420   assume-console [
 421   ¦ press right-arrow
 422   ¦ type [0]
 423   ]
 424   run [
 425   ¦ editor-event-loop screen, console, e
 426   ]
 427   screen-should-contain [
 428   ¦ .          .
 429   ¦ .a0bc      .
 430   ¦ .╌╌╌╌╌╌╌╌╌╌.
 431   ¦ .          .
 432   ]
 433   check-trace-count-for-label 3, [print-character]  # 0 and following characters
 434 ]
 435 
 436 after <handle-special-key> [
 437   {
 438   ¦ move-to-next-character?:bool <- equal k, 65514/right-arrow
 439   ¦ break-unless move-to-next-character?
 440   ¦ # if not at end of text
 441   ¦ next-cursor:&:duplex-list:char <- next before-cursor
 442   ¦ break-unless next-cursor
 443   ¦ # scan to next character
 444   ¦ <move-cursor-begin>
 445   ¦ before-cursor <- copy next-cursor
 446   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 447   ¦ go-render?:bool <- move-cursor-coordinates-right editor, screen-height
 448   ¦ screen <- move-cursor screen, cursor-row, cursor-column
 449   ¦ undo-coalesce-tag:num <- copy 2/right-arrow
 450   ¦ <move-cursor-end>
 451   ¦ return
 452   }
 453 ]
 454 
 455 def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
 456   local-scope
 457   load-ingredients
 458   before-cursor:&:duplex-list:char <- get *editor before-cursor:offset
 459   cursor-row:num <- get *editor, cursor-row:offset
 460   cursor-column:num <- get *editor, cursor-column:offset
 461   left:num <- get *editor, left:offset
 462   right:num <- get *editor, right:offset
 463   # if crossed a newline, move cursor to start of next row
 464   {
 465   ¦ old-cursor-character:char <- get *before-cursor, value:offset
 466   ¦ was-at-newline?:bool <- equal old-cursor-character, 10/newline
 467   ¦ break-unless was-at-newline?
 468   ¦ cursor-row <- add cursor-row, 1
 469   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 470   ¦ cursor-column <- copy left
 471   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 472   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 473   ¦ return-unless below-screen?, 0/don't-render
 474   ¦ <scroll-down>
 475   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 476   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 477   ¦ return 1/go-render
 478   }
 479   # if the line wraps, move cursor to start of next row
 480   {
 481   ¦ # if we're at the column just before the wrap indicator
 482   ¦ wrap-column:num <- subtract right, 1
 483   ¦ at-wrap?:bool <- equal cursor-column, wrap-column
 484   ¦ break-unless at-wrap?
 485   ¦ # and if next character isn't newline
 486   ¦ next:&:duplex-list:char <- next before-cursor
 487   ¦ break-unless next
 488   ¦ next-character:char <- get *next, value:offset
 489   ¦ newline?:bool <- equal next-character, 10/newline
 490   ¦ break-if newline?
 491   ¦ cursor-row <- add cursor-row, 1
 492   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 493   ¦ cursor-column <- copy left
 494   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 495   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 496   ¦ return-unless below-screen?, 0/no-more-render
 497   ¦ <scroll-down>
 498   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 499   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 500   ¦ return 1/go-render
 501   }
 502   # otherwise move cursor one character right
 503   cursor-column <- add cursor-column, 1
 504   *editor <- put *editor, cursor-column:offset, cursor-column
 505   go-render? <- copy 0/false
 506 ]
 507 
 508 scenario editor-moves-cursor-to-next-line-with-right-arrow [
 509   local-scope
 510   assume-screen 10/width, 5/height
 511   s:text <- new [abc
 512 d]
 513   e:&:editor <- new-editor s, 0/left, 10/right
 514   editor-render screen, e
 515   $clear-trace
 516   # type right-arrow a few times to get to start of second line
 517   assume-console [
 518   ¦ press right-arrow
 519   ¦ press right-arrow
 520   ¦ press right-arrow
 521   ¦ press right-arrow  # next line
 522   ]
 523   run [
 524   ¦ editor-event-loop screen, console, e
 525   ]
 526   check-trace-count-for-label 0, [print-character]
 527   # type something and ensure it goes where it should
 528   assume-console [
 529   ¦ type [0]
 530   ]
 531   run [
 532   ¦ editor-event-loop screen, console, e
 533   ]
 534   screen-should-contain [
 535   ¦ .          .
 536   ¦ .abc       .
 537   ¦ .0d        .
 538   ¦ .╌╌╌╌╌╌╌╌╌╌.
 539   ¦ .          .
 540   ]
 541   check-trace-count-for-label 2, [print-character]  # new length of second line
 542 ]
 543 
 544 scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [
 545   local-scope
 546   assume-screen 10/width, 5/height
 547   s:text <- new [abc
 548 d]
 549   e:&:editor <- new-editor s, 1/left, 10/right
 550   editor-render screen, e
 551   assume-console [
 552   ¦ press right-arrow
 553   ¦ press right-arrow
 554   ¦ press right-arrow
 555   ¦ press right-arrow  # next line
 556   ¦ type [0]
 557   ]
 558   run [
 559   ¦ editor-event-loop screen, console, e
 560   ]
 561   screen-should-contain [
 562   ¦ .          .
 563   ¦ . abc      .
 564   ¦ . 0d       .
 565   ¦ . ╌╌╌╌╌╌╌╌╌.
 566   ¦ .          .
 567   ]
 568 ]
 569 
 570 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
 571   local-scope
 572   assume-screen 10/width, 5/height
 573   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 574   editor-render screen, e
 575   $clear-trace
 576   assume-console [
 577   ¦ left-click 1, 3
 578   ¦ press right-arrow
 579   ]
 580   run [
 581   ¦ editor-event-loop screen, console, e
 582   ¦ 3:num/raw <- get *e, cursor-row:offset
 583   ¦ 4:num/raw <- get *e, cursor-column:offset
 584   ]
 585   screen-should-contain [
 586   ¦ .          .
 587   ¦ .abcd↩     .
 588   ¦ .ef        .
 589   ¦ .╌╌╌╌╌     .
 590   ¦ .          .
 591   ]
 592   memory-should-contain [
 593   ¦ 3 <- 2
 594   ¦ 4 <- 0
 595   ]
 596   check-trace-count-for-label 0, [print-character]
 597 ]
 598 
 599 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
 600   local-scope
 601   assume-screen 10/width, 5/height
 602   # line just barely wrapping
 603   e:&:editor <- new-editor [abcde], 0/left, 5/right
 604   editor-render screen, e
 605   $clear-trace
 606   # position cursor at last character before wrap and hit right-arrow
 607   assume-console [
 608   ¦ left-click 1, 3
 609   ¦ press right-arrow
 610   ]
 611   run [
 612   ¦ editor-event-loop screen, console, e
 613   ¦ 3:num/raw <- get *e, cursor-row:offset
 614   ¦ 4:num/raw <- get *e, cursor-column:offset
 615   ]
 616   memory-should-contain [
 617   ¦ 3 <- 2
 618   ¦ 4 <- 0
 619   ]
 620   # now hit right arrow again
 621   assume-console [
 622   ¦ press right-arrow
 623   ]
 624   run [
 625   ¦ editor-event-loop screen, console, e
 626   ¦ 3:num/raw <- get *e, cursor-row:offset
 627   ¦ 4:num/raw <- get *e, cursor-column:offset
 628   ]
 629   memory-should-contain [
 630   ¦ 3 <- 2
 631   ¦ 4 <- 1
 632   ]
 633   check-trace-count-for-label 0, [print-character]
 634 ]
 635 
 636 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
 637   local-scope
 638   assume-screen 10/width, 5/height
 639   e:&:editor <- new-editor [abcdef], 1/left, 6/right
 640   editor-render screen, e
 641   $clear-trace
 642   assume-console [
 643   ¦ left-click 1, 4
 644   ¦ press right-arrow
 645   ]
 646   run [
 647   ¦ editor-event-loop screen, console, e
 648   ¦ 3:num/raw <- get *e, cursor-row:offset
 649   ¦ 4:num/raw <- get *e, cursor-column:offset
 650   ]
 651   screen-should-contain [
 652   ¦ .          .
 653   ¦ . abcd↩    .
 654   ¦ . ef       .
 655   ¦ . ╌╌╌╌╌    .
 656   ¦ .          .
 657   ]
 658   memory-should-contain [
 659   ¦ 3 <- 2
 660   ¦ 4 <- 1
 661   ]
 662   check-trace-count-for-label 0, [print-character]
 663 ]
 664 
 665 scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
 666   local-scope
 667   assume-screen 10/width, 5/height
 668   s:text <- new [abc
 669 d]
 670   e:&:editor <- new-editor s, 0/left, 10/right
 671   editor-render screen, e
 672   $clear-trace
 673   # move to end of line, press right-arrow, type a character
 674   assume-console [
 675   ¦ left-click 1, 3
 676   ¦ press right-arrow
 677   ¦ type [0]
 678   ]
 679   run [
 680   ¦ editor-event-loop screen, console, e
 681   ]
 682   # new character should be in next line
 683   screen-should-contain [
 684   ¦ .          .
 685   ¦ .abc       .
 686   ¦ .0d        .
 687   ¦ .╌╌╌╌╌╌╌╌╌╌.
 688   ¦ .          .
 689   ]
 690   check-trace-count-for-label 2, [print-character]
 691 ]
 692 
 693 # todo: ctrl-right: next word-end
 694 
 695 # left arrow
 696 
 697 scenario editor-moves-cursor-left-with-key [
 698   local-scope
 699   assume-screen 10/width, 5/height
 700   e:&:editor <- new-editor [abc], 0/left, 10/right
 701   editor-render screen, e
 702   $clear-trace
 703   assume-console [
 704   ¦ left-click 1, 2
 705   ¦ press left-arrow
 706   ¦ type [0]
 707   ]
 708   run [
 709   ¦ editor-event-loop screen, console, e
 710   ]
 711   screen-should-contain [
 712   ¦ .          .
 713   ¦ .a0bc      .
 714   ¦ .╌╌╌╌╌╌╌╌╌╌.
 715   ¦ .          .
 716   ]
 717   check-trace-count-for-label 3, [print-character]
 718 ]
 719 
 720 after <handle-special-key> [
 721   {
 722   ¦ move-to-previous-character?:bool <- equal k, 65515/left-arrow
 723   ¦ break-unless move-to-previous-character?
 724   ¦ trace 10, [app], [left arrow]
 725   ¦ # if not at start of text (before-cursor at § sentinel)
 726   ¦ prev:&:duplex-list:char <- prev before-cursor
 727   ¦ return-unless prev, 0/don't-render
 728   ¦ <move-cursor-begin>
 729   ¦ go-render? <- move-cursor-coordinates-left editor
 730   ¦ before-cursor <- copy prev
 731   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 732   ¦ undo-coalesce-tag:num <- copy 1/left-arrow
 733   ¦ <move-cursor-end>
 734   ¦ return
 735   }
 736 ]
 737 
 738 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
 739   local-scope
 740   assume-screen 10/width, 5/height
 741   # initialize editor with two lines
 742   s:text <- new [abc
 743 d]
 744   e:&:editor <- new-editor s, 0/left, 10/right
 745   editor-render screen, e
 746   $clear-trace
 747   # position cursor at start of second line (so there's no previous newline)
 748   assume-console [
 749   ¦ left-click 2, 0
 750   ¦ press left-arrow
 751   ]
 752   run [
 753   ¦ editor-event-loop screen, console, e
 754   ¦ 3:num/raw <- get *e, cursor-row:offset
 755   ¦ 4:num/raw <- get *e, cursor-column:offset
 756   ]
 757   memory-should-contain [
 758   ¦ 3 <- 1
 759   ¦ 4 <- 3
 760   ]
 761   check-trace-count-for-label 0, [print-character]
 762 ]
 763 
 764 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
 765   local-scope
 766   assume-screen 10/width, 5/height
 767   # initialize editor with three lines
 768   s:text <- new [abc
 769 def
 770 g]
 771   e:&:editor <- new-editor s:text, 0/left, 10/right
 772   editor-render screen, e
 773   $clear-trace
 774   # position cursor further down (so there's a newline before the character at
 775   # the cursor)
 776   assume-console [
 777   ¦ left-click 3, 0
 778   ¦ press left-arrow
 779   ¦ type [0]
 780   ]
 781   run [
 782   ¦ editor-event-loop screen, console, e
 783   ]
 784   screen-should-contain [
 785   ¦ .          .
 786   ¦ .abc       .
 787   ¦ .def0      .
 788   ¦ .g         .
 789   ¦ .╌╌╌╌╌╌╌╌╌╌.
 790   ]
 791   check-trace-count-for-label 1, [print-character]  # just the '0'
 792 ]
 793 
 794 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
 795   local-scope
 796   assume-screen 10/width, 5/height
 797   s:text <- new [abc
 798 def
 799 g]
 800   e:&:editor <- new-editor s, 0/left, 10/right
 801   editor-render screen, e
 802   $clear-trace
 803   # position cursor at start of text, press left-arrow, then type a character
 804   assume-console [
 805   ¦ left-click 1, 0
 806   ¦ press left-arrow
 807   ¦ type [0]
 808   ]
 809   run [
 810   ¦ editor-event-loop screen, console, e
 811   ]
 812   # left-arrow should have had no effect
 813   screen-should-contain [
 814   ¦ .          .
 815   ¦ .0abc      .
 816   ¦ .def       .
 817   ¦ .g         .
 818   ¦ .╌╌╌╌╌╌╌╌╌╌.
 819   ]
 820   check-trace-count-for-label 4, [print-character]  # length of first line
 821 ]
 822 
 823 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
 824   local-scope
 825   assume-screen 10/width, 5/height
 826   # initialize editor with text containing an empty line
 827   s:text <- new [abc
 828 
 829 d]
 830   e:&:editor <- new-editor s, 0/left, 10/right
 831   editor-render screen, e:&:editor
 832   $clear-trace
 833   # position cursor right after empty line
 834   assume-console [
 835   ¦ left-click 3, 0
 836   ¦ press left-arrow
 837   ¦ type [0]
 838   ]
 839   run [
 840   ¦ editor-event-loop screen, console, e
 841   ]
 842   screen-should-contain [
 843   ¦ .          .
 844   ¦ .abc       .
 845   ¦ .0         .
 846   ¦ .d         .
 847   ¦ .╌╌╌╌╌╌╌╌╌╌.
 848   ]
 849   check-trace-count-for-label 1, [print-character]  # just the '0'
 850 ]
 851 
 852 scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
 853   local-scope
 854   assume-screen 10/width, 5/height
 855   # initialize editor with a wrapping line
 856   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 857   editor-render screen, e
 858   $clear-trace
 859   screen-should-contain [
 860   ¦ .          .
 861   ¦ .abcd↩     .
 862   ¦ .ef        .
 863   ¦ .╌╌╌╌╌     .
 864   ¦ .          .
 865   ]
 866   # position cursor right after empty line
 867   assume-console [
 868   ¦ left-click 2, 0
 869   ¦ press left-arrow
 870   ]
 871   run [
 872   ¦ editor-event-loop screen, console, e
 873   ¦ 3:num/raw <- get *e, cursor-row:offset
 874   ¦ 4:num/raw <- get *e, cursor-column:offset
 875   ]
 876   memory-should-contain [
 877   ¦ 3 <- 1  # previous row
 878   ¦ 4 <- 3  # right margin except wrap icon
 879   ]
 880   check-trace-count-for-label 0, [print-character]
 881 ]
 882 
 883 scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [
 884   local-scope
 885   assume-screen 10/width, 5/height
 886   # initialize editor with a wrapping line followed by a second line
 887   s:text <- new [abcdef
 888 g]
 889   e:&:editor <- new-editor s, 0/left, 5/right
 890   editor-render screen, e
 891   $clear-trace
 892   screen-should-contain [
 893   ¦ .          .
 894   ¦ .abcd↩     .
 895   ¦ .ef        .
 896   ¦ .g         .
 897   ¦ .╌╌╌╌╌     .
 898   ]
 899   # position cursor right after empty line
 900   assume-console [
 901   ¦ left-click 3, 0
 902   ¦ press left-arrow
 903   ]
 904   run [
 905   ¦ editor-event-loop screen, console, e
 906   ¦ 3:num/raw <- get *e, cursor-row:offset
 907   ¦ 4:num/raw <- get *e, cursor-column:offset
 908   ]
 909   memory-should-contain [
 910   ¦ 3 <- 2  # previous row
 911   ¦ 4 <- 2  # end of wrapped line
 912   ]
 913   check-trace-count-for-label 0, [print-character]
 914 ]
 915 
 916 scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [
 917   local-scope
 918   assume-screen 10/width, 5/height
 919   # initialize editor with a line on the verge of wrapping, followed by a second line
 920   s:text <- new [abcd
 921 e]
 922   e:&:editor <- new-editor s, 0/left, 5/right
 923   editor-render screen, e
 924   $clear-trace
 925   screen-should-contain [
 926   ¦ .          .
 927   ¦ .abcd      .
 928   ¦ .e         .
 929   ¦ .╌╌╌╌╌     .
 930   ¦ .          .
 931   ]
 932   # position cursor right after empty line
 933   assume-console [
 934   ¦ left-click 2, 0
 935   ¦ press left-arrow
 936   ]
 937   run [
 938   ¦ editor-event-loop screen, console, e
 939   ¦ 3:num/raw <- get *e, cursor-row:offset
 940   ¦ 4:num/raw <- get *e, cursor-column:offset
 941   ]
 942   memory-should-contain [
 943   ¦ 3 <- 1  # previous row
 944   ¦ 4 <- 4  # end of wrapped line
 945   ]
 946   check-trace-count-for-label 0, [print-character]
 947 ]
 948 
 949 # todo: ctrl-left: previous word-start
 950 
 951 # up arrow
 952 
 953 scenario editor-moves-to-previous-line-with-up-arrow [
 954   local-scope
 955   assume-screen 10/width, 5/height
 956   s:text <- new [abc
 957 def]
 958   e:&:editor <- new-editor s, 0/left, 10/right
 959   editor-render screen, e
 960   $clear-trace
 961   assume-console [
 962   ¦ left-click 2, 1
 963   ¦ press up-arrow
 964   ]
 965   run [
 966   ¦ editor-event-loop screen, console, e
 967   ¦ 3:num/raw <- get *e, cursor-row:offset
 968   ¦ 4:num/raw <- get *e, cursor-column:offset
 969   ]
 970   memory-should-contain [
 971   ¦ 3 <- 1
 972   ¦ 4 <- 1
 973   ]
 974   check-trace-count-for-label 0, [print-character]
 975   assume-console [
 976   ¦ type [0]
 977   ]
 978   run [
 979   ¦ editor-event-loop screen, console, e
 980   ]
 981   screen-should-contain [
 982   ¦ .          .
 983   ¦ .a0bc      .
 984   ¦ .def       .
 985   ¦ .╌╌╌╌╌╌╌╌╌╌.
 986   ¦ .          .
 987   ]
 988 ]
 989 
 990 after <handle-special-key> [
 991   {
 992   ¦ move-to-previous-line?:bool <- equal k, 65517/up-arrow
 993   ¦ break-unless move-to-previous-line?
 994   ¦ <move-cursor-begin>
 995   ¦ go-render? <- move-to-previous-line editor
 996   ¦ undo-coalesce-tag:num <- copy 3/up-arrow
 997   ¦ <move-cursor-end>
 998   ¦ return
 999   }
1000 ]
1001 
1002 def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [
1003   local-scope
1004   load-ingredients
1005   go-render?:bool <- copy 0/false
1006   cursor-row:num <- get *editor, cursor-row:offset
1007   cursor-column:num <- get *editor, cursor-column:offset
1008   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1009   left:num <- get *editor, left:offset
1010   right:num <- get *editor, right:offset
1011   already-at-top?:bool <- lesser-or-equal cursor-row, 1/top
1012   {
1013   ¦ # if cursor not at top, move it
1014   ¦ break-if already-at-top?
1015   ¦ # if not at newline, move to start of line (previous newline)
1016   ¦ # then scan back another line
1017   ¦ # if either step fails, give up without modifying cursor or coordinates
1018   ¦ curr:&:duplex-list:char <- copy before-cursor
1019   ¦ {
1020   ¦ ¦ old:&:duplex-list:char <- copy curr
1021   ¦ ¦ c2:char <- get *curr, value:offset
1022   ¦ ¦ at-newline?:bool <- equal c2, 10/newline
1023   ¦ ¦ break-if at-newline?
1024   ¦ ¦ curr:&:duplex-list:char <- before-previous-line curr, editor
1025   ¦ ¦ no-motion?:bool <- equal curr, old
1026   ¦ ¦ return-if no-motion?
1027   ¦ }
1028   ¦ {
1029   ¦ ¦ old <- copy curr
1030   ¦ ¦ curr <- before-previous-line curr, editor
1031   ¦ ¦ no-motion?:bool <- equal curr, old
1032   ¦ ¦ return-if no-motion?
1033   ¦ }
1034   ¦ before-cursor <- copy curr
1035   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1036   ¦ cursor-row <- subtract cursor-row, 1
1037   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
1038   ¦ # scan ahead to right column or until end of line
1039   ¦ target-column:num <- copy cursor-column
1040   ¦ cursor-column <- copy left
1041   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1042   ¦ {
1043   ¦ ¦ done?:bool <- greater-or-equal cursor-column, target-column
1044   ¦ ¦ break-if done?
1045   ¦ ¦ curr:&:duplex-list:char <- next before-cursor
1046   ¦ ¦ break-unless curr
1047   ¦ ¦ currc:char <- get *curr, value:offset
1048   ¦ ¦ at-newline?:bool <- equal currc, 10/newline
1049   ¦ ¦ break-if at-newline?
1050   ¦ ¦ #
1051   ¦ ¦ before-cursor <- copy curr
1052   ¦ ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1053   ¦ ¦ cursor-column <- add cursor-column, 1
1054   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1055   ¦ ¦ loop
1056   ¦ }
1057   ¦ return
1058   }
1059   {
1060   ¦ # if cursor already at top, scroll up
1061   ¦ break-unless already-at-top?
1062   ¦ <scroll-up>
1063   ¦ return 1/go-render
1064   }
1065 ]
1066 
1067 scenario editor-adjusts-column-at-previous-line [
1068   local-scope
1069   assume-screen 10/width, 5/height
1070   s:text <- new [ab
1071 def]
1072   e:&:editor <- new-editor s, 0/left, 10/right
1073   editor-render screen, e
1074   $clear-trace
1075   assume-console [
1076   ¦ left-click 2, 3
1077   ¦ press up-arrow
1078   ]
1079   run [
1080   ¦ editor-event-loop screen, console, e
1081   ¦ 3:num/raw <- get *e, cursor-row:offset
1082   ¦ 4:num/raw <- get *e, cursor-column:offset
1083   ]
1084   memory-should-contain [
1085   ¦ 3 <- 1
1086   ¦ 4 <- 2
1087   ]
1088   check-trace-count-for-label 0, [print-character]
1089   assume-console [
1090   ¦ type [0]
1091   ]
1092   run [
1093   ¦ editor-event-loop screen, console, e
1094   ]
1095   screen-should-contain [
1096   ¦ .          .
1097   ¦ .ab0       .
1098   ¦ .def       .
1099   ¦ .╌╌╌╌╌╌╌╌╌╌.
1100   ¦ .          .
1101   ]
1102 ]
1103 
1104 scenario editor-adjusts-column-at-empty-line [
1105   local-scope
1106   assume-screen 10/width, 5/height
1107   s:text <- new [
1108 def]
1109   e:&:editor <- new-editor s, 0/left, 10/right
1110   editor-render screen, e
1111   $clear-trace
1112   assume-console [
1113   ¦ left-click 2, 3
1114   ¦ press up-arrow
1115   ]
1116   run [
1117   ¦ editor-event-loop screen, console, e
1118   ¦ 3:num/raw <- get *e, cursor-row:offset
1119   ¦ 4:num/raw <- get *e, cursor-column:offset
1120   ]
1121   memory-should-contain [
1122   ¦ 3 <- 1
1123   ¦ 4 <- 0
1124   ]
1125   check-trace-count-for-label 0, [print-character]
1126   assume-console [
1127   ¦ type [0]
1128   ]
1129   run [
1130   ¦ editor-event-loop screen, console, e
1131   ]
1132   screen-should-contain [
1133   ¦ .          .
1134   ¦ .0         .
1135   ¦ .def       .
1136   ¦ .╌╌╌╌╌╌╌╌╌╌.
1137   ¦ .          .
1138   ]
1139 ]
1140 
1141 scenario editor-moves-to-previous-line-from-left-margin [
1142   local-scope
1143   assume-screen 10/width, 5/height
1144   # start out with three lines
1145   s:text <- new [abc
1146 def
1147 ghi]
1148   e:&:editor <- new-editor s, 0/left, 10/right
1149   editor-render screen, e
1150   $clear-trace
1151   # click on the third line and hit up-arrow, so you end up just after a newline
1152   assume-console [
1153   ¦ left-click 3, 0
1154   ¦ press up-arrow
1155   ]
1156   run [
1157   ¦ editor-event-loop screen, console, e
1158   ¦ 3:num/raw <- get *e, cursor-row:offset
1159   ¦ 4:num/raw <- get *e, cursor-column:offset
1160   ]
1161   memory-should-contain [
1162   ¦ 3 <- 2
1163   ¦ 4 <- 0
1164   ]
1165   check-trace-count-for-label 0, [print-character]
1166   assume-console [
1167   ¦ type [0]
1168   ]
1169   run [
1170   ¦ editor-event-loop screen, console, e
1171   ]
1172   screen-should-contain [
1173   ¦ .          .
1174   ¦ .abc       .
1175   ¦ .0def      .
1176   ¦ .ghi       .
1177   ¦ .╌╌╌╌╌╌╌╌╌╌.
1178   ]
1179 ]
1180 
1181 # down arrow
1182 
1183 scenario editor-moves-to-next-line-with-down-arrow [
1184   local-scope
1185   assume-screen 10/width, 5/height
1186   s:text <- new [abc
1187 def]
1188   e:&:editor <- new-editor s, 0/left, 10/right
1189   editor-render screen, e
1190   $clear-trace
1191   # cursor starts out at (1, 0)
1192   assume-console [
1193   ¦ press down-arrow
1194   ]
1195   run [
1196   ¦ editor-event-loop screen, console, e
1197   ¦ 3:num/raw <- get *e, cursor-row:offset
1198   ¦ 4:num/raw <- get *e, cursor-column:offset
1199   ]
1200   # ..and ends at (2, 0)
1201   memory-should-contain [
1202   ¦ 3 <- 2
1203   ¦ 4 <- 0
1204   ]
1205   check-trace-count-for-label 0, [print-character]
1206   assume-console [
1207   ¦ type [0]
1208   ]
1209   run [
1210   ¦ editor-event-loop screen, console, e
1211   ]
1212   screen-should-contain [
1213   ¦ .          .
1214   ¦ .abc       .
1215   ¦ .0def      .
1216   ¦ .╌╌╌╌╌╌╌╌╌╌.
1217   ¦ .          .
1218   ]
1219 ]
1220 
1221 after <handle-special-key> [
1222   {
1223   ¦ move-to-next-line?:bool <- equal k, 65516/down-arrow
1224   ¦ break-unless move-to-next-line?
1225   ¦ <move-cursor-begin>
1226   ¦ go-render? <- move-to-next-line editor, screen-height
1227   ¦ undo-coalesce-tag:num <- copy 4/down-arrow
1228   ¦ <move-cursor-end>
1229   ¦ return
1230   }
1231 ]
1232 
1233 def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
1234   local-scope
1235   load-ingredients
1236   cursor-row:num <- get *editor, cursor-row:offset
1237   cursor-column:num <- get *editor, cursor-column:offset
1238   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1239   left:num <- get *editor, left:offset
1240   right:num <- get *editor, right:offset
1241   last-line:num <- subtract screen-height, 1
1242   already-at-bottom?:bool <- greater-or-equal cursor-row, last-line
1243   {
1244   ¦ # if cursor not at bottom, move it
1245   ¦ break-if already-at-bottom?
1246   ¦ # scan to start of next line, then to right column or until end of line
1247   ¦ max:num <- subtract right, left
1248   ¦ next-line:&:duplex-list:char <- before-start-of-next-line before-cursor, max
1249   ¦ {
1250   ¦ ¦ # already at end of buffer? try to scroll up (so we can see more
1251   ¦ ¦ # warnings or sandboxes below)
1252   ¦ ¦ no-motion?:bool <- equal next-line, before-cursor
1253   ¦ ¦ break-unless no-motion?
1254   ¦ ¦ scroll?:bool <- greater-than cursor-row, 1
1255   ¦ ¦ break-if scroll?, +try-to-scroll
1256   ¦ ¦ return 0/don't-render
1257   ¦ }
1258   ¦ cursor-row <- add cursor-row, 1
1259   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
1260   ¦ before-cursor <- copy next-line
1261   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1262   ¦ target-column:num <- copy cursor-column
1263   ¦ cursor-column <- copy left
1264   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1265   ¦ {
1266   ¦ ¦ done?:bool <- greater-or-equal cursor-column, target-column
1267   ¦ ¦ break-if done?
1268   ¦ ¦ curr:&:duplex-list:char <- next before-cursor
1269   ¦ ¦ break-unless curr
1270   ¦ ¦ currc:char <- get *curr, value:offset
1271   ¦ ¦ at-newline?:bool <- equal currc, 10/newline
1272   ¦ ¦ break-if at-newline?
1273   ¦ ¦ #
1274   ¦ ¦ before-cursor <- copy curr
1275   ¦ ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1276   ¦ ¦ cursor-column <- add cursor-column, 1
1277   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1278   ¦ ¦ loop
1279   ¦ }
1280   ¦ return 0/don't-render
1281   }
1282   +try-to-scroll
1283   <scroll-down>
1284   go-render? <- copy 1/true
1285 ]
1286 
1287 scenario editor-adjusts-column-at-next-line [
1288   local-scope
1289   assume-screen 10/width, 5/height
1290   s:text <- new [abc
1291 de]
1292   e:&:editor <- new-editor s, 0/left, 10/right
1293   editor-render screen, e
1294   $clear-trace
1295   assume-console [
1296   ¦ left-click 1, 3
1297   ¦ press down-arrow
1298   ]
1299   run [
1300   ¦ editor-event-loop screen, console, e
1301   ¦ 3:num/raw <- get *e, cursor-row:offset
1302   ¦ 4:num/raw <- get *e, cursor-column:offset
1303   ]
1304   memory-should-contain [
1305   ¦ 3 <- 2
1306   ¦ 4 <- 2
1307   ]
1308   check-trace-count-for-label 0, [print-character]
1309   assume-console [
1310   ¦ type [0]
1311   ]
1312   run [
1313   ¦ editor-event-loop screen, console, e
1314   ]
1315   screen-should-contain [
1316   ¦ .          .
1317   ¦ .abc       .
1318   ¦ .de0       .
1319   ¦ .╌╌╌╌╌╌╌╌╌╌.
1320   ¦ .          .
1321   ]
1322 ]
1323 
1324 # ctrl-a/home - move cursor to start of line
1325 
1326 scenario editor-moves-to-start-of-line-with-ctrl-a [
1327   local-scope
1328   assume-screen 10/width, 5/height
1329   s:text <- new [123
1330 456]
1331   e:&:editor <- new-editor s, 0/left, 10/right
1332   editor-render screen, e
1333   $clear-trace
1334   # start on second line, press ctrl-a
1335   assume-console [
1336   ¦ left-click 2, 3
1337   ¦ press ctrl-a
1338   ]
1339   run [
1340   ¦ editor-event-loop screen, console, e
1341   ¦ 4:num/raw <- get *e, cursor-row:offset
1342   ¦ 5:num/raw <- get *e, cursor-column:offset
1343   ]
1344   # cursor moves to start of line
1345   memory-should-contain [
1346   ¦ 4 <- 2
1347   ¦ 5 <- 0
1348   ]
1349   check-trace-count-for-label 0, [print-character]
1350 ]
1351 
1352 after <handle-special-character> [
1353   {
1354   ¦ move-to-start-of-line?:bool <- equal c, 1/ctrl-a
1355   ¦ break-unless move-to-start-of-line?
1356   ¦ <move-cursor-begin>
1357   ¦ move-to-start-of-line editor
1358   ¦ undo-coalesce-tag:num <- copy 0/never
1359   ¦ <move-cursor-end>
1360   ¦ return 0/don't-render
1361   }
1362 ]
1363 
1364 after <handle-special-key> [
1365   {
1366   ¦ move-to-start-of-line?:bool <- equal k, 65521/home
1367   ¦ break-unless move-to-start-of-line?
1368   ¦ <move-cursor-begin>
1369   ¦ move-to-start-of-line editor
1370   ¦ undo-coalesce-tag:num <- copy 0/never
1371   ¦ <move-cursor-end>
1372   ¦ return 0/don't-render
1373   }
1374 ]
1375 
1376 def move-to-start-of-line editor:&:editor -> editor:&:editor [
1377   local-scope
1378   load-ingredients
1379   # update cursor column
1380   left:num <- get *editor, left:offset
1381   cursor-column:num <- copy left
1382   *editor <- put *editor, cursor-column:offset, cursor-column
1383   # update before-cursor
1384   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1385   init:&:duplex-list:char <- get *editor, data:offset
1386   # while not at start of line, move
1387   {
1388   ¦ at-start-of-text?:bool <- equal before-cursor, init
1389   ¦ break-if at-start-of-text?
1390   ¦ prev:char <- get *before-cursor, value:offset
1391   ¦ at-start-of-line?:bool <- equal prev, 10/newline
1392   ¦ break-if at-start-of-line?
1393   ¦ before-cursor <- prev before-cursor
1394   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1395   ¦ assert before-cursor, [move-to-start-of-line tried to move before start of text]
1396   ¦ loop
1397   }
1398 ]
1399 
1400 scenario editor-moves-to-start-of-line-with-ctrl-a-2 [
1401   local-scope
1402   assume-screen 10/width, 5/height
1403   s:text <- new [123
1404 456]
1405   e:&:editor <- new-editor s, 0/left, 10/right
1406   editor-render screen, e
1407   $clear-trace
1408   # start on first line (no newline before), press ctrl-a
1409   assume-console [
1410   ¦ left-click 1, 3
1411   ¦ press ctrl-a
1412   ]
1413   run [
1414   ¦ editor-event-loop screen, console, e
1415   ¦ 4:num/raw <- get *e, cursor-row:offset
1416   ¦ 5:num/raw <- get *e, cursor-column:offset
1417   ]
1418   # cursor moves to start of line
1419   memory-should-contain [
1420   ¦ 4 <- 1
1421   ¦ 5 <- 0
1422   ]
1423   check-trace-count-for-label 0, [print-character]
1424 ]
1425 
1426 scenario editor-moves-to-start-of-line-with-home [
1427   local-scope
1428   assume-screen 10/width, 5/height
1429   s:text <- new [123
1430 456]
1431   e:&:editor <- new-editor s, 0/left, 10/right
1432   $clear-trace
1433   # start on second line, press 'home'
1434   assume-console [
1435   ¦ left-click 2, 3
1436   ¦ press home
1437   ]
1438   run [
1439   ¦ editor-event-loop screen, console, e
1440   ¦ 3:num/raw <- get *e, cursor-row:offset
1441   ¦ 4:num/raw <- get *e, cursor-column:offset
1442   ]
1443   # cursor moves to start of line
1444   memory-should-contain [
1445   ¦ 3 <- 2
1446   ¦ 4 <- 0
1447   ]
1448   check-trace-count-for-label 0, [print-character]
1449 ]
1450 
1451 scenario editor-moves-to-start-of-line-with-home-2 [
1452   local-scope
1453   assume-screen 10/width, 5/height
1454   s:text <- new [123
1455 456]
1456   e:&:editor <- new-editor s, 0/left, 10/right
1457   editor-render screen, e
1458   $clear-trace
1459   # start on first line (no newline before), press 'home'
1460   assume-console [
1461   ¦ left-click 1, 3
1462   ¦ press home
1463   ]
1464   run [
1465   ¦ editor-event-loop screen, console, e
1466   ¦ 3:num/raw <- get *e, cursor-row:offset
1467   ¦ 4:num/raw <- get *e, cursor-column:offset
1468   ]
1469   # cursor moves to start of line
1470   memory-should-contain [
1471   ¦ 3 <- 1
1472   ¦ 4 <- 0
1473   ]
1474   check-trace-count-for-label 0, [print-character]
1475 ]
1476 
1477 # ctrl-e/end - move cursor to end of line
1478 
1479 scenario editor-moves-to-end-of-line-with-ctrl-e [
1480   local-scope
1481   assume-screen 10/width, 5/height
1482   s:text <- new [123
1483 456]
1484   e:&:editor <- new-editor s, 0/left, 10/right
1485   editor-render screen, e
1486   $clear-trace
1487   # start on first line, press ctrl-e
1488   assume-console [
1489   ¦ left-click 1, 1
1490   ¦ press ctrl-e
1491   ]
1492   run [
1493   ¦ editor-event-loop screen, console, e
1494   ¦ 4:num/raw <- get *e, cursor-row:offset
1495   ¦ 5:num/raw <- get *e, cursor-column:offset
1496   ]
1497   # cursor moves to end of line
1498   memory-should-contain [
1499   ¦ 4 <- 1
1500   ¦ 5 <- 3
1501   ]
1502   check-trace-count-for-label 0, [print-character]
1503   # editor inserts future characters at cursor
1504   assume-console [
1505   ¦ type [z]
1506   ]
1507   run [
1508   ¦ editor-event-loop screen, console, e
1509   ¦ 4:num/raw <- get *e, cursor-row:offset
1510   ¦ 5:num/raw <- get *e, cursor-column:offset
1511   ]
1512   memory-should-contain [
1513   ¦ 4 <- 1
1514   ¦ 5 <- 4
1515   ]
1516   screen-should-contain [
1517   ¦ .          .
1518   ¦ .123z      .
1519   ¦ .456       .
1520   ¦ .╌╌╌╌╌╌╌╌╌╌.
1521   ¦ .          .
1522   ]
1523   check-trace-count-for-label 1, [print-character]
1524 ]
1525 
1526 after <handle-special-character> [
1527   {
1528   ¦ move-to-end-of-line?:bool <- equal c, 5/ctrl-e
1529   ¦ break-unless move-to-end-of-line?
1530   ¦ <move-cursor-begin>
1531   ¦ move-to-end-of-line editor
1532   ¦ undo-coalesce-tag:num <- copy 0/never
1533   ¦ <move-cursor-end>
1534   ¦ return 0/don't-render
1535   }
1536 ]
1537 
1538 after <handle-special-key> [
1539   {
1540   ¦ move-to-end-of-line?:bool <- equal k, 65520/end
1541   ¦ break-unless move-to-end-of-line?
1542   ¦ <move-cursor-begin>
1543   ¦ move-to-end-of-line editor
1544   ¦ undo-coalesce-tag:num <- copy 0/never
1545   ¦ <move-cursor-end>
1546   ¦ return 0/don't-render
1547   }
1548 ]
1549 
1550 def move-to-end-of-line editor:&:editor -> editor:&:editor [
1551   local-scope
1552   load-ingredients
1553   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1554   cursor-column:num <- get *editor, cursor-column:offset
1555   # while not at start of line, move
1556   {
1557   ¦ next:&:duplex-list:char <- next before-cursor
1558   ¦ break-unless next  # end of text
1559   ¦ nextc:char <- get *next, value:offset
1560   ¦ at-end-of-line?:bool <- equal nextc, 10/newline
1561   ¦ break-if at-end-of-line?
1562   ¦ before-cursor <- copy next
1563   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1564   ¦ cursor-column <- add cursor-column, 1
1565   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1566   ¦ loop
1567   }
1568 ]
1569 
1570 scenario editor-moves-to-end-of-line-with-ctrl-e-2 [
1571   local-scope
1572   assume-screen 10/width, 5/height
1573   s:text <- new [123
1574 456]
1575   e:&:editor <- new-editor s, 0/left, 10/right
1576   editor-render screen, e
1577   $clear-trace
1578   # start on second line (no newline after), press ctrl-e
1579   assume-console [
1580   ¦ left-click 2, 1
1581   ¦ press ctrl-e
1582   ]
1583   run [
1584   ¦ editor-event-loop screen, console, e
1585   ¦ 4:num/raw <- get *e, cursor-row:offset
1586   ¦ 5:num/raw <- get *e, cursor-column:offset
1587   ]
1588   # cursor moves to end of line
1589   memory-should-contain [
1590   ¦ 4 <- 2
1591   ¦ 5 <- 3
1592   ]
1593   check-trace-count-for-label 0, [print-character]
1594 ]
1595 
1596 scenario editor-moves-to-end-of-line-with-end [
1597   local-scope
1598   assume-screen 10/width, 5/height
1599   s:text <- new [123
1600 456]
1601   e:&:editor <- new-editor s, 0/left, 10/right
1602   editor-render screen, e
1603   $clear-trace
1604   # start on first line, press 'end'
1605   assume-console [
1606   ¦ left-click 1, 1
1607   ¦ press end
1608   ]
1609   run [
1610   ¦ editor-event-loop screen, console, e
1611   ¦ 3:num/raw <- get *e, cursor-row:offset
1612   ¦ 4:num/raw <- get *e, cursor-column:offset
1613   ]
1614   # cursor moves to end of line
1615   memory-should-contain [
1616   ¦ 3 <- 1
1617   ¦ 4 <- 3
1618   ]
1619   check-trace-count-for-label 0, [print-character]
1620 ]
1621 
1622 scenario editor-moves-to-end-of-line-with-end-2 [
1623   local-scope
1624   assume-screen 10/width, 5/height
1625   s:text <- new [123
1626 456]
1627   e:&:editor <- new-editor s, 0/left, 10/right
1628   editor-render screen, e
1629   $clear-trace
1630   # start on second line (no newline after), press 'end'
1631   assume-console [
1632   ¦ left-click 2, 1
1633   ¦ press end
1634   ]
1635   run [
1636   ¦ editor-event-loop screen, console, e
1637   ¦ 3:num/raw <- get *e, cursor-row:offset
1638   ¦ 4:num/raw <- get *e, cursor-column:offset
1639   ]
1640   # cursor moves to end of line
1641   memory-should-contain [
1642   ¦ 3 <- 2
1643   ¦ 4 <- 3
1644   ]
1645   check-trace-count-for-label 0, [print-character]
1646 ]
1647 
1648 # ctrl-u - delete text from start of line until (but not at) cursor
1649 
1650 scenario editor-deletes-to-start-of-line-with-ctrl-u [
1651   local-scope
1652   assume-screen 10/width, 5/height
1653   s:text <- new [123
1654 456]
1655   e:&:editor <- new-editor s, 0/left, 10/right
1656   editor-render screen, e
1657   $clear-trace
1658   # start on second line, press ctrl-u
1659   assume-console [
1660   ¦ left-click 2, 2
1661   ¦ press ctrl-u
1662   ]
1663   run [
1664   ¦ editor-event-loop screen, console, e
1665   ]
1666   # cursor deletes to start of line
1667   screen-should-contain [
1668   ¦ .          .
1669   ¦ .123       .
1670   ¦ .6         .
1671   ¦ .╌╌╌╌╌╌╌╌╌╌.
1672   ¦ .          .
1673   ]
1674   check-trace-count-for-label 10, [print-character]
1675 ]
1676 
1677 after <handle-special-character> [
1678   {
1679   ¦ delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
1680   ¦ break-unless delete-to-start-of-line?
1681   ¦ <delete-to-start-of-line-begin>
1682   ¦ deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
1683   ¦ <delete-to-start-of-line-end>
1684   ¦ go-render?:bool <- minimal-render-for-ctrl-u screen, editor
1685   ¦ return
1686   }
1687 ]
1688 
1689 def minimal-render-for-ctrl-u screen:&:screen, editor:&:editor -> go-render?:bool, screen:&:screen [
1690   local-scope
1691   load-ingredients
1692   curr-column:num <- get *editor, cursor-column:offset
1693   # accumulate the current line as text and render it
1694   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1695   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1696   i:num <- copy curr-column
1697   right:num <- get *editor, right:offset
1698   {
1699   ¦ # if we have a wrapped line, give up and render the whole screen
1700   ¦ wrap?:bool <- greater-or-equal i, right
1701   ¦ return-if wrap?, 1/go-render
1702   ¦ curr <- next curr
1703   ¦ break-unless curr
1704   ¦ c:char <- get *curr, value:offset
1705   ¦ b:bool <- equal c, 10
1706   ¦ break-if b
1707   ¦ buf <- append buf, c
1708   ¦ i <- add i, 1
1709   ¦ loop
1710   }
1711   curr-line:text <- buffer-to-array buf
1712   curr-row:num <- get *editor, cursor-row:offset
1713   render-code screen, curr-line, curr-column, right, curr-row
1714   return 0/dont-render
1715 ]
1716 
1717 def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1718   local-scope
1719   load-ingredients
1720   # compute range to delete
1721   init:&:duplex-list:char <- get *editor, data:offset
1722   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1723   start:&:duplex-list:char <- copy before-cursor
1724   end:&:duplex-list:char <- next before-cursor
1725   {
1726   ¦ at-start-of-text?:bool <- equal start, init
1727   ¦ break-if at-start-of-text?
1728   ¦ curr:char <- get *start, value:offset
1729   ¦ at-start-of-line?:bool <- equal curr, 10/newline
1730   ¦ break-if at-start-of-line?
1731   ¦ start <- prev start
1732   ¦ assert start, [delete-to-start-of-line tried to move before start of text]
1733   ¦ loop
1734   }
1735   # snip it out
1736   result:&:duplex-list:char <- next start
1737   remove-between start, end
1738   # adjust cursor
1739   before-cursor <- copy start
1740   *editor <- put *editor, before-cursor:offset, before-cursor
1741   left:num <- get *editor, left:offset
1742   *editor <- put *editor, cursor-column:offset, left
1743 ]
1744 
1745 def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [
1746   local-scope
1747   load-ingredients
1748   return-unless s
1749   color:num <- copy 7/white
1750   column:num <- copy left
1751   screen <- move-cursor screen, row, column
1752   screen-height:num <- screen-height screen
1753   i:num <- copy 0
1754   len:num <- length *s
1755   {
1756   ¦ +next-character
1757   ¦ done?:bool <- greater-or-equal i, len
1758   ¦ break-if done?
1759   ¦ done? <- greater-or-equal row, screen-height
1760   ¦ break-if done?
1761   ¦ c:char <- index *s, i
1762   ¦ <character-c-received>
1763   ¦ {
1764   ¦ ¦ # newline? move to left rather than 0
1765   ¦ ¦ newline?:bool <- equal c, 10/newline
1766   ¦ ¦ break-unless newline?
1767   ¦ ¦ # clear rest of line in this window
1768   ¦ ¦ {
1769   ¦ ¦ ¦ done?:bool <- greater-than column, right
1770   ¦ ¦ ¦ break-if done?
1771   ¦ ¦ ¦ space:char <- copy 32/space
1772   ¦ ¦ ¦ print screen, space
1773   ¦ ¦ ¦ column <- add column, 1
1774   ¦ ¦ ¦ loop
1775   ¦ ¦ }
1776   ¦ ¦ row <- add row, 1
1777   ¦ ¦ column <- copy left
1778   ¦ ¦ screen <- move-cursor screen, row, column
1779   ¦ ¦ i <- add i, 1
1780   ¦ ¦ loop +next-character
1781   ¦ }
1782   ¦ {
1783   ¦ ¦ # at right? wrap.
1784   ¦ ¦ at-right?:bool <- equal column, right
1785   ¦ ¦ break-unless at-right?
1786   ¦ ¦ # print wrap icon
1787   ¦ ¦ wrap-icon:char <- copy 8617/loop-back-to-left
1788   ¦ ¦ print screen, wrap-icon, 245/grey
1789   ¦ ¦ column <- copy left
1790   ¦ ¦ row <- add row, 1
1791   ¦ ¦ screen <- move-cursor screen, row, column
1792   ¦ ¦ # don't increment i
1793   ¦ ¦ loop +next-character
1794   ¦ }
1795   ¦ i <- add i, 1
1796   ¦ print screen, c, color
1797   ¦ column <- add column, 1
1798   ¦ loop
1799   }
1800   was-at-left?:bool <- equal column, left
1801   clear-line-until screen, right
1802   {
1803   ¦ break-if was-at-left?
1804   ¦ row <- add row, 1
1805   }
1806   move-cursor screen, row, left
1807 ]
1808 
1809 scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [
1810   local-scope
1811   assume-screen 10/width, 5/height
1812   s:text <- new [123
1813 456]
1814   e:&:editor <- new-editor s, 0/left, 10/right
1815   editor-render screen, e
1816   $clear-trace
1817   # start on first line (no newline before), press ctrl-u
1818   assume-console [
1819   ¦ left-click 1, 2
1820   ¦ press ctrl-u
1821   ]
1822   run [
1823   ¦ editor-event-loop screen, console, e
1824   ]
1825   # cursor deletes to start of line
1826   screen-should-contain [
1827   ¦ .          .
1828   ¦ .3         .
1829   ¦ .456       .
1830   ¦ .╌╌╌╌╌╌╌╌╌╌.
1831   ¦ .          .
1832   ]
1833   check-trace-count-for-label 10, [print-character]
1834 ]
1835 
1836 scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [
1837   local-scope
1838   assume-screen 10/width, 5/height
1839   s:text <- new [123
1840 456]
1841   e:&:editor <- new-editor s, 0/left, 10/right
1842   editor-render screen, e
1843   $clear-trace
1844   # start past end of line, press ctrl-u
1845   assume-console [
1846   ¦ left-click 1, 3
1847   ¦ press ctrl-u
1848   ]
1849   run [
1850   ¦ editor-event-loop screen, console, e
1851   ]
1852   # cursor deletes to start of line
1853   screen-should-contain [
1854   ¦ .          .
1855   ¦ .          .
1856   ¦ .456       .
1857   ¦ .╌╌╌╌╌╌╌╌╌╌.
1858   ¦ .          .
1859   ]
1860   check-trace-count-for-label 10, [print-character]
1861 ]
1862 
1863 scenario editor-deletes-to-start-of-final-line-with-ctrl-u [
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 past end of final line, press ctrl-u
1872   assume-console [
1873   ¦ left-click 2, 3
1874   ¦ press ctrl-u
1875   ]
1876   run [
1877   ¦ editor-event-loop screen, console, e
1878   ]
1879   # cursor deletes to start of line
1880   screen-should-contain [
1881   ¦ .          .
1882   ¦ .123       .
1883   ¦ .          .
1884   ¦ .╌╌╌╌╌╌╌╌╌╌.
1885   ¦ .          .
1886   ]
1887   check-trace-count-for-label 10, [print-character]
1888 ]
1889 
1890 # ctrl-k - delete text from cursor to end of line (but not the newline)
1891 
1892 scenario editor-deletes-to-end-of-line-with-ctrl-k [
1893   local-scope
1894   assume-screen 10/width, 5/height
1895   s:text <- new [123
1896 456]
1897   e:&:editor <- new-editor s, 0/left, 10/right
1898   editor-render screen, e
1899   $clear-trace
1900   # start on first line, press ctrl-k
1901   assume-console [
1902   ¦ left-click 1, 1
1903   ¦ press ctrl-k
1904   ]
1905   run [
1906   ¦ editor-event-loop screen, console, e
1907   ]
1908   # cursor deletes to end of line
1909   screen-should-contain [
1910   ¦ .          .
1911   ¦ .1         .
1912   ¦ .456       .
1913   ¦ .╌╌╌╌╌╌╌╌╌╌.
1914   ¦ .          .
1915   ]
1916   check-trace-count-for-label 9, [print-character]
1917 ]
1918 
1919 after <handle-special-character> [
1920   {
1921   ¦ delete-to-end-of-line?:bool <- equal c, 11/ctrl-k
1922   ¦ break-unless delete-to-end-of-line?
1923   ¦ <delete-to-end-of-line-begin>
1924   ¦ deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor
1925   ¦ <delete-to-end-of-line-end>
1926   ¦ # checks if we can do a minimal render and if we can it will do a minimal render
1927   ¦ go-render?:bool <- minimal-render-for-ctrl-k screen, editor, deleted-cells
1928   ¦ return
1929   }
1930 ]
1931 
1932 def minimal-render-for-ctrl-k screen:&:screen, editor:&:editor, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
1933   local-scope
1934   load-ingredients
1935   # if we deleted nothing, there's nothing to render
1936   return-unless deleted-cells, 0/dont-render
1937   # if we have a wrapped line, give up and render the whole screen
1938   curr-column:num <- get *editor, cursor-column:offset
1939   num-deleted-cells:num <- length, deleted-cells
1940   old-row-len:num <- add curr-column, num-deleted-cells
1941   left:num <- get *editor, left:offset
1942   right:num <- get *editor, right:offset
1943   end:num <- subtract right, left
1944   wrap?:bool <- greater-or-equal old-row-len, end
1945   return-if wrap?, 1/go-render
1946   # accumulate the current line as text and render it
1947   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1948   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1949   {
1950   ¦ # check if we are at the end of the line
1951   ¦ curr <- next curr
1952   ¦ break-unless curr
1953   ¦ c:char <- get *curr, value:offset
1954   ¦ # check if we have a newline
1955   ¦ b:bool <- equal c, 10
1956   ¦ break-if b
1957   ¦ buf <- append buf, c
1958   ¦ loop
1959   }
1960   curr-line:text <- buffer-to-array buf
1961   curr-row:num <- get *editor, cursor-row:offset
1962   render-code screen, curr-line, curr-column, right, curr-row
1963   return 0/dont-render
1964 ]
1965 
1966 def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1967   local-scope
1968   load-ingredients
1969   # compute range to delete
1970   start:&:duplex-list:char <- get *editor, before-cursor:offset
1971   end:&:duplex-list:char <- next start
1972   {
1973   ¦ at-end-of-text?:bool <- equal end, 0/null
1974   ¦ break-if at-end-of-text?
1975   ¦ curr:char <- get *end, value:offset
1976   ¦ at-end-of-line?:bool <- equal curr, 10/newline
1977   ¦ break-if at-end-of-line?
1978   ¦ end <- next end
1979   ¦ loop
1980   }
1981   # snip it out
1982   result <- next start
1983   remove-between start, end
1984 ]
1985 
1986 scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [
1987   local-scope
1988   assume-screen 10/width, 5/height
1989   s:text <- new [123
1990 456]
1991   e:&:editor <- new-editor s, 0/left, 10/right
1992   editor-render screen, e
1993   $clear-trace
1994   # start on second line (no newline after), press ctrl-k
1995   assume-console [
1996   ¦ left-click 2, 1
1997   ¦ press ctrl-k
1998   ]
1999   run [
2000   ¦ editor-event-loop screen, console, e
2001   ]
2002   # cursor deletes to end of line
2003   screen-should-contain [
2004   ¦ .          .
2005   ¦ .123       .
2006   ¦ .4         .
2007   ¦ .╌╌╌╌╌╌╌╌╌╌.
2008   ¦ .          .
2009   ]
2010   check-trace-count-for-label 9, [print-character]
2011 ]
2012 
2013 scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [
2014   local-scope
2015   assume-screen 10/width, 5/height
2016   s:text <- new [123
2017 456]
2018   e:&:editor <- new-editor s, 0/left, 10/right
2019   editor-render screen, e
2020   $clear-trace
2021   # start at end of line
2022   assume-console [
2023   ¦ left-click 1, 2
2024   ¦ press ctrl-k
2025   ]
2026   run [
2027   ¦ editor-event-loop screen, console, e
2028   ]
2029   # cursor deletes just last character
2030   screen-should-contain [
2031   ¦ .          .
2032   ¦ .12        .
2033   ¦ .456       .
2034   ¦ .╌╌╌╌╌╌╌╌╌╌.
2035   ¦ .          .
2036   ]
2037   check-trace-count-for-label 8, [print-character]
2038 ]
2039 
2040 scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [
2041   local-scope
2042   assume-screen 10/width, 5/height
2043   s:text <- new [123
2044 456]
2045   e:&:editor <- new-editor s, 0/left, 10/right
2046   editor-render screen, e
2047   $clear-trace
2048   # start past end of line
2049   assume-console [
2050   ¦ left-click 1, 3
2051   ¦ press ctrl-k
2052   ]
2053   run [
2054   ¦ editor-event-loop screen, console, e
2055   ]
2056   # cursor deletes nothing
2057   screen-should-contain [
2058   ¦ .          .
2059   ¦ .123       .
2060   ¦ .456       .
2061   ¦ .╌╌╌╌╌╌╌╌╌╌.
2062   ¦ .          .
2063   ]
2064   check-trace-count-for-label 7, [print-character]
2065 ]
2066 
2067 scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [
2068   local-scope
2069   assume-screen 10/width, 5/height
2070   s:text <- new [123
2071 456]
2072   e:&:editor <- new-editor s, 0/left, 10/right
2073   editor-render screen, e
2074   $clear-trace
2075   # start at end of text
2076   assume-console [
2077   ¦ left-click 2, 2
2078   ¦ press ctrl-k
2079   ]
2080   run [
2081   ¦ editor-event-loop screen, console, e
2082   ]
2083   # cursor deletes just the final character
2084   screen-should-contain [
2085   ¦ .          .
2086   ¦ .123       .
2087   ¦ .45        .
2088   ¦ .╌╌╌╌╌╌╌╌╌╌.
2089   ¦ .          .
2090   ]
2091   check-trace-count-for-label 8, [print-character]
2092 ]
2093 
2094 scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
2095   local-scope
2096   assume-screen 10/width, 5/height
2097   s:text <- new [123
2098 456]
2099   e:&:editor <- new-editor s, 0/left, 10/right
2100   editor-render screen, e
2101   $clear-trace
2102   # start past end of text
2103   assume-console [
2104   ¦ left-click 2, 3
2105   ¦ press ctrl-k
2106   ]
2107   run [
2108   ¦ editor-event-loop screen, console, e
2109   ]
2110   # cursor deletes nothing
2111   screen-should-contain [
2112   ¦ .          .
2113   ¦ .123       .
2114   ¦ .456       .
2115   ¦ .╌╌╌╌╌╌╌╌╌╌.
2116   ¦ .          .
2117   ]
2118   # no prints necessary
2119   check-trace-count-for-label 0, [print-character]
2120 ]
2121 
2122 scenario editor-deletes-to-end-of-wrapped-line-with-ctrl-k [
2123   local-scope
2124   assume-screen 10/width, 5/height
2125   # create an editor with the first line wrapping to a second screen row
2126   s:text <- new [1234
2127 567]
2128   e:&:editor <- new-editor s, 0/left, 4/right
2129   editor-render screen, e
2130   $clear-trace
2131   # delete all of the first wrapped line
2132   assume-console [
2133   ¦ press ctrl-k
2134   ]
2135   run [
2136   ¦ editor-event-loop screen, console, e
2137   ]
2138   # screen shows an empty unwrapped first line
2139   screen-should-contain [
2140   ¦ .          .
2141   ¦ .          .
2142   ¦ .567       .
2143   ¦ .╌╌╌╌      .
2144   ¦ .          .
2145   ]
2146   # entire screen is refreshed
2147   check-trace-count-for-label 16, [print-character]
2148 ]
2149 
2150 # cursor-down can scroll if necessary
2151 
2152 scenario editor-can-scroll-down-using-arrow-keys [
2153   local-scope
2154   # screen has 1 line for menu + 3 lines
2155   assume-screen 10/width, 4/height
2156   # initialize editor with >3 lines
2157   s:text <- new [a
2158 b
2159 c
2160 d]
2161   e:&:editor <- new-editor s, 0/left, 10/right
2162   editor-render screen, e
2163   screen-should-contain [
2164   ¦ .          .
2165   ¦ .a         .
2166   ¦ .b         .
2167   ¦ .c         .
2168   ]
2169   # position cursor at last line, then try to move further down
2170   assume-console [
2171   ¦ left-click 3, 0
2172   ¦ press down-arrow
2173   ]
2174   run [
2175   ¦ editor-event-loop screen, console, e
2176   ]
2177   # screen slides by one line
2178   screen-should-contain [
2179   ¦ .          .
2180   ¦ .b         .
2181   ¦ .c         .
2182   ¦ .d         .
2183   ]
2184 ]
2185 
2186 after <scroll-down> [
2187   trace 10, [app], [scroll down]
2188   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2189   left:num <- get *editor, left:offset
2190   right:num <- get *editor, right:offset
2191   max:num <- subtract right, left
2192   old-top:&:duplex-list:char <- copy top-of-screen
2193   top-of-screen <- before-start-of-next-line top-of-screen, max
2194   *editor <- put *editor, top-of-screen:offset, top-of-screen
2195   no-movement?:bool <- equal old-top, top-of-screen
2196   return-if no-movement?, 0/don't-render
2197 ]
2198 
2199 # takes a pointer into the doubly-linked list, scans ahead at most 'max'
2200 # positions until the next newline
2201 # returns original if no next newline
2202 # beware: never return null pointer.
2203 def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [
2204   local-scope
2205   load-ingredients
2206   count:num <- copy 0
2207   curr:&:duplex-list:char <- copy original
2208   # skip the initial newline if it exists
2209   {
2210   ¦ c:char <- get *curr, value:offset
2211   ¦ at-newline?:bool <- equal c, 10/newline
2212   ¦ break-unless at-newline?
2213   ¦ curr <- next curr
2214   ¦ count <- add count, 1
2215   }
2216   {
2217   ¦ return-unless curr, original
2218   ¦ done?:bool <- greater-or-equal count, max
2219   ¦ break-if done?
2220   ¦ c:char <- get *curr, value:offset
2221   ¦ at-newline?:bool <- equal c, 10/newline
2222   ¦ break-if at-newline?
2223   ¦ curr <- next curr
2224   ¦ count <- add count, 1
2225   ¦ loop
2226   }
2227   return-unless curr, original
2228   return curr
2229 ]
2230 
2231 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
2232   local-scope
2233   # screen has 1 line for menu + 3 lines
2234   assume-screen 10/width, 4/height
2235   # initialize editor with a long, wrapped line and more than a screen of
2236   # other lines
2237   s:text <- new [abcdef
2238 g
2239 h
2240 i]
2241   e:&:editor <- new-editor s, 0/left, 5/right
2242   editor-render screen, e
2243   screen-should-contain [
2244   ¦ .          .
2245   ¦ .abcd↩     .
2246   ¦ .ef        .
2247   ¦ .g         .
2248   ]
2249   # position cursor at last line, then try to move further down
2250   assume-console [
2251   ¦ left-click 3, 0
2252   ¦ press down-arrow
2253   ]
2254   run [
2255   ¦ editor-event-loop screen, console, e
2256   ]
2257   # screen shows partial wrapped line
2258   screen-should-contain [
2259   ¦ .          .
2260   ¦ .ef        .
2261   ¦ .g         .
2262   ¦ .h         .
2263   ]
2264 ]
2265 
2266 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
2267   local-scope
2268   # screen has 1 line for menu + 3 lines
2269   assume-screen 10/width, 4/height
2270   # editor starts with a long line wrapping twice
2271   s:text <- new [abcdefghij
2272 k
2273 l
2274 m]
2275   e:&:editor <- new-editor s, 0/left, 5/right
2276   # position cursor at last line, then try to move further down
2277   assume-console [
2278   ¦ left-click 3, 0
2279   ¦ press down-arrow
2280   ]
2281   run [
2282   ¦ editor-event-loop screen, console, e
2283   ]
2284   # screen shows partial wrapped line containing a wrap icon
2285   screen-should-contain [
2286   ¦ .          .
2287   ¦ .efgh↩     .
2288   ¦ .ij        .
2289   ¦ .k         .
2290   ]
2291   # scroll down again
2292   assume-console [
2293   ¦ press down-arrow
2294   ]
2295   run [
2296   ¦ editor-event-loop screen, console, e
2297   ]
2298   # screen shows partial wrapped line
2299   screen-should-contain [
2300   ¦ .          .
2301   ¦ .ij        .
2302   ¦ .k         .
2303   ¦ .l         .
2304   ]
2305 ]
2306 
2307 scenario editor-scrolls-down-when-line-wraps [
2308   local-scope
2309   # screen has 1 line for menu + 3 lines
2310   assume-screen 5/width, 4/height
2311   # editor contains a long line in the third line
2312   s:text <- new [a
2313 b
2314 cdef]
2315   e:&:editor <- new-editor s, 0/left, 5/right
2316   # position cursor at end, type a character
2317   assume-console [
2318   ¦ left-click 3, 4
2319   ¦ type [g]
2320   ]
2321   run [
2322   ¦ editor-event-loop screen, console, e
2323   ¦ 3:num/raw <- get *e, cursor-row:offset
2324   ¦ 4:num/raw <- get *e, cursor-column:offset
2325   ]
2326   # screen scrolls
2327   screen-should-contain [
2328   ¦ .     .
2329   ¦ .b    .
2330   ¦ .cdef↩.
2331   ¦ .g    .
2332   ]
2333   memory-should-contain [
2334   ¦ 3 <- 3
2335   ¦ 4 <- 1
2336   ]
2337 ]
2338 
2339 scenario editor-scrolls-down-on-newline [
2340   local-scope
2341   assume-screen 5/width, 4/height
2342   # position cursor after last line and type newline
2343   s:text <- new [a
2344 b
2345 c]
2346   e:&:editor <- new-editor s, 0/left, 5/right
2347   assume-console [
2348   ¦ left-click 3, 4
2349   ¦ type [
2350 ]
2351   ]
2352   run [
2353   ¦ editor-event-loop screen, console, e
2354   ¦ 3:num/raw <- get *e, cursor-row:offset
2355   ¦ 4:num/raw <- get *e, cursor-column:offset
2356   ]
2357   # screen scrolls
2358   screen-should-contain [
2359   ¦ .     .
2360   ¦ .b    .
2361   ¦ .c    .
2362   ¦ .     .
2363   ]
2364   memory-should-contain [
2365   ¦ 3 <- 3
2366   ¦ 4 <- 0
2367   ]
2368 ]
2369 
2370 scenario editor-scrolls-down-on-right-arrow [
2371   local-scope
2372   # screen has 1 line for menu + 3 lines
2373   assume-screen 5/width, 4/height
2374   # editor contains a wrapped line
2375   s:text <- new [a
2376 b
2377 cdefgh]
2378   e:&:editor <- new-editor s, 0/left, 5/right
2379   # position cursor at end of screen and try to move right
2380   assume-console [
2381   ¦ left-click 3, 3
2382   ¦ press right-arrow
2383   ]
2384   run [
2385   ¦ editor-event-loop screen, console, e
2386   ¦ 3:num/raw <- get *e, cursor-row:offset
2387   ¦ 4:num/raw <- get *e, cursor-column:offset
2388   ]
2389   # screen scrolls
2390   screen-should-contain [
2391   ¦ .     .
2392   ¦ .b    .
2393   ¦ .cdef↩.
2394   ¦ .gh   .
2395   ]
2396   memory-should-contain [
2397   ¦ 3 <- 3
2398   ¦ 4 <- 0
2399   ]
2400 ]
2401 
2402 scenario editor-scrolls-down-on-right-arrow-2 [
2403   local-scope
2404   # screen has 1 line for menu + 3 lines
2405   assume-screen 5/width, 4/height
2406   # editor contains more lines than can fit on screen
2407   s:text <- new [a
2408 b
2409 c
2410 d]
2411   e:&:editor <- new-editor s, 0/left, 5/right
2412   # position cursor at end of screen and try to move right
2413   assume-console [
2414   ¦ left-click 3, 3
2415   ¦ press right-arrow
2416   ]
2417   run [
2418   ¦ editor-event-loop screen, console, e
2419   ¦ 3:num/raw <- get *e, cursor-row:offset
2420   ¦ 4:num/raw <- get *e, cursor-column:offset
2421   ]
2422   # screen scrolls
2423   screen-should-contain [
2424   ¦ .     .
2425   ¦ .b    .
2426   ¦ .c    .
2427   ¦ .d    .
2428   ]
2429   memory-should-contain [
2430   ¦ 3 <- 3
2431   ¦ 4 <- 0
2432   ]
2433 ]
2434 
2435 scenario editor-scrolls-at-end-on-down-arrow [
2436   local-scope
2437   assume-screen 10/width, 5/height
2438   s:text <- new [abc
2439 de]
2440   e:&:editor <- new-editor s, 0/left, 10/right
2441   editor-render screen, e
2442   $clear-trace
2443   # try to move down past end of text
2444   assume-console [
2445   ¦ left-click 2, 0
2446   ¦ press down-arrow
2447   ]
2448   run [
2449   ¦ editor-event-loop screen, console, e
2450   ¦ 3:num/raw <- get *e, cursor-row:offset
2451   ¦ 4:num/raw <- get *e, cursor-column:offset
2452   ]
2453   # screen should scroll, moving cursor to end of text
2454   memory-should-contain [
2455   ¦ 3 <- 1
2456   ¦ 4 <- 2
2457   ]
2458   assume-console [
2459   ¦ type [0]
2460   ]
2461   run [
2462   ¦ editor-event-loop screen, console, e
2463   ]
2464   screen-should-contain [
2465   ¦ .          .
2466   ¦ .de0       .
2467   ¦ .╌╌╌╌╌╌╌╌╌╌.
2468   ¦ .          .
2469   ]
2470   # try to move down again
2471   $clear-trace
2472   assume-console [
2473   ¦ left-click 2, 0
2474   ¦ press down-arrow
2475   ]
2476   run [
2477   ¦ editor-event-loop screen, console, e
2478   ¦ 3:num/raw <- get *e, cursor-row:offset
2479   ¦ 4:num/raw <- get *e, cursor-column:offset
2480   ]
2481   # screen stops scrolling because cursor is already at top
2482   memory-should-contain [
2483   ¦ 3 <- 1
2484   ¦ 4 <- 3
2485   ]
2486   check-trace-count-for-label 0, [print-character]
2487   assume-console [
2488   ¦ type [1]
2489   ]
2490   run [
2491   ¦ editor-event-loop screen, console, e
2492   ]
2493   screen-should-contain [
2494   ¦ .          .
2495   ¦ .de01      .
2496   ¦ .╌╌╌╌╌╌╌╌╌╌.
2497   ¦ .          .
2498   ]
2499 ]
2500 
2501 scenario editor-combines-page-and-line-scroll [
2502   local-scope
2503   # screen has 1 line for menu + 3 lines
2504   assume-screen 10/width, 4/height
2505   # initialize editor with a few pages of lines
2506   s:text <- new [a
2507 b
2508 c
2509 d
2510 e
2511 f
2512 g]
2513   e:&:editor <- new-editor s, 0/left, 5/right
2514   editor-render screen, e
2515   # scroll down one page and one line
2516   assume-console [
2517   ¦ press page-down
2518   ¦ left-click 3, 0
2519   ¦ press down-arrow
2520   ]
2521   run [
2522   ¦ editor-event-loop screen, console, e
2523   ]
2524   # screen scrolls down 3 lines
2525   screen-should-contain [
2526   ¦ .          .
2527   ¦ .d         .
2528   ¦ .e         .
2529   ¦ .f         .
2530   ]
2531 ]
2532 
2533 # cursor-up can scroll if necessary
2534 
2535 scenario editor-can-scroll-up-using-arrow-keys [
2536   local-scope
2537   # screen has 1 line for menu + 3 lines
2538   assume-screen 10/width, 4/height
2539   # initialize editor with >3 lines
2540   s:text <- new [a
2541 b
2542 c
2543 d]
2544   e:&:editor <- new-editor s, 0/left, 10/right
2545   editor-render screen, e
2546   screen-should-contain [
2547   ¦ .          .
2548   ¦ .a         .
2549   ¦ .b         .
2550   ¦ .c         .
2551   ]
2552   # position cursor at top of second page, then try to move up
2553   assume-console [
2554   ¦ press page-down
2555   ¦ press up-arrow
2556   ]
2557   run [
2558   ¦ editor-event-loop screen, console, e
2559   ]
2560   # screen slides by one line
2561   screen-should-contain [
2562   ¦ .          .
2563   ¦ .b         .
2564   ¦ .c         .
2565   ¦ .d         .
2566   ]
2567 ]
2568 
2569 after <scroll-up> [
2570   trace 10, [app], [scroll up]
2571   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2572   old-top:&:duplex-list:char <- copy top-of-screen
2573   top-of-screen <- before-previous-line top-of-screen, editor
2574   *editor <- put *editor, top-of-screen:offset, top-of-screen
2575   no-movement?:bool <- equal old-top, top-of-screen
2576   return-if no-movement?, 0/don't-render
2577 ]
2578 
2579 # takes a pointer into the doubly-linked list, scans back to before start of
2580 # previous *wrapped* line
2581 # returns original if no next newline
2582 # beware: never return null pointer
2583 def before-previous-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [
2584   local-scope
2585   load-ingredients
2586   curr:&:duplex-list:char <- copy in
2587   c:char <- get *curr, value:offset
2588   # compute max, number of characters to skip
2589   #   1 + len%(width-1)
2590   #   except rotate second term to vary from 1 to width-1 rather than 0 to width-2
2591   left:num <- get *editor, left:offset
2592   right:num <- get *editor, right:offset
2593   max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon
2594   sentinel:&:duplex-list:char <- get *editor, data:offset
2595   len:num <- previous-line-length curr, sentinel
2596   {
2597   ¦ break-if len
2598   ¦ # empty line; just skip this newline
2599   ¦ prev:&:duplex-list:char <- prev curr
2600   ¦ return-unless prev, curr
2601   ¦ return prev
2602   }
2603   _, max:num <- divide-with-remainder len, max-line-length
2604   # remainder 0 => scan one width-worth
2605   {
2606   ¦ break-if max
2607   ¦ max <- copy max-line-length
2608   }
2609   max <- add max, 1
2610   count:num <- copy 0
2611   # skip 'max' characters
2612   {
2613   ¦ done?:bool <- greater-or-equal count, max
2614   ¦ break-if done?
2615   ¦ prev:&:duplex-list:char <- prev curr
2616   ¦ break-unless prev
2617   ¦ curr <- copy prev
2618   ¦ count <- add count, 1
2619   ¦ loop
2620   }
2621   return curr
2622 ]
2623 
2624 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [
2625   local-scope
2626   # screen has 1 line for menu + 3 lines
2627   assume-screen 10/width, 4/height
2628   # initialize editor with a long, wrapped line and more than a screen of
2629   # other lines
2630   s:text <- new [abcdef
2631 g
2632 h
2633 i]
2634   e:&:editor <- new-editor s, 0/left, 5/right
2635   editor-render screen, e
2636   screen-should-contain [
2637   ¦ .          .
2638   ¦ .abcd↩     .
2639   ¦ .ef        .
2640   ¦ .g         .
2641   ]
2642   # position cursor at top of second page, just below wrapped line
2643   assume-console [
2644   ¦ press page-down
2645   ]
2646   run [
2647   ¦ editor-event-loop screen, console, e
2648   ]
2649   screen-should-contain [
2650   ¦ .          .
2651   ¦ .g         .
2652   ¦ .h         .
2653   ¦ .i         .
2654   ]
2655   # now move up one line
2656   assume-console [
2657   ¦ press up-arrow
2658   ]
2659   run [
2660   ¦ editor-event-loop screen, console, e
2661   ]
2662   # screen shows partial wrapped line
2663   screen-should-contain [
2664   ¦ .          .
2665   ¦ .ef        .
2666   ¦ .g         .
2667   ¦ .h         .
2668   ]
2669 ]
2670 
2671 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
2672   local-scope
2673   # screen has 1 line for menu + 4 lines
2674   assume-screen 10/width, 5/height
2675   # editor starts with a long line wrapping twice, occupying 3 of the 4 lines
2676   s:text <- new [abcdefghij
2677 k
2678 l
2679 m]
2680   e:&:editor <- new-editor s, 0/left, 5/right
2681   editor-render screen, e
2682   # position cursor at top of second page
2683   assume-console [
2684   ¦ press page-down
2685   ]
2686   run [
2687   ¦ editor-event-loop screen, console, e
2688   ]
2689   screen-should-contain [
2690   ¦ .          .
2691   ¦ .k         .
2692   ¦ .l         .
2693   ¦ .m         .
2694   ¦ .╌╌╌╌╌     .
2695   ]
2696   # move up one line
2697   assume-console [
2698   ¦ press up-arrow
2699   ]
2700   run [
2701   ¦ editor-event-loop screen, console, e
2702   ]
2703   # screen shows partial wrapped line
2704   screen-should-contain [
2705   ¦ .          .
2706   ¦ .ij        .
2707   ¦ .k         .
2708   ¦ .l         .
2709   ¦ .m         .
2710   ]
2711   # move up a second line
2712   assume-console [
2713   ¦ press up-arrow
2714   ]
2715   run [
2716   ¦ editor-event-loop screen, console, e
2717   ]
2718   # screen shows partial wrapped line
2719   screen-should-contain [
2720   ¦ .          .
2721   ¦ .efgh↩     .
2722   ¦ .ij        .
2723   ¦ .k         .
2724   ¦ .l         .
2725   ]
2726   # move up a third line
2727   assume-console [
2728   ¦ press up-arrow
2729   ]
2730   run [
2731   ¦ editor-event-loop screen, console, e
2732   ]
2733   # screen shows partial wrapped line
2734   screen-should-contain [
2735   ¦ .          .
2736   ¦ .abcd↩     .
2737   ¦ .efgh↩     .
2738   ¦ .ij        .
2739   ¦ .k         .
2740   ]
2741 ]
2742 
2743 # same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length
2744 # slightly off, just to prevent over-training
2745 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
2746   local-scope
2747   # screen has 1 line for menu + 3 lines
2748   assume-screen 10/width, 4/height
2749   # initialize editor with a long, wrapped line and more than a screen of
2750   # other lines
2751   s:text <- new [abcdef
2752 g
2753 h
2754 i]
2755   e:&:editor <- new-editor s, 0/left, 6/right
2756   editor-render screen, e
2757   screen-should-contain [
2758   ¦ .          .
2759   ¦ .abcde↩    .
2760   ¦ .f         .
2761   ¦ .g         .
2762   ]
2763   # position cursor at top of second page, just below wrapped line
2764   assume-console [
2765   ¦ press page-down
2766   ]
2767   run [
2768   ¦ editor-event-loop screen, console, e
2769   ]
2770   screen-should-contain [
2771   ¦ .          .
2772   ¦ .g         .
2773   ¦ .h         .
2774   ¦ .i         .
2775   ]
2776   # now move up one line
2777   assume-console [
2778   ¦ press up-arrow
2779   ]
2780   run [
2781   ¦ editor-event-loop screen, console, e
2782   ]
2783   # screen shows partial wrapped line
2784   screen-should-contain [
2785   ¦ .          .
2786   ¦ .f         .
2787   ¦ .g         .
2788   ¦ .h         .
2789   ]
2790 ]
2791 
2792 # check empty lines
2793 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
2794   local-scope
2795   assume-screen 10/width, 4/height
2796   # initialize editor with some lines around an empty line
2797   s:text <- new [a
2798 b
2799 
2800 c
2801 d
2802 e]
2803   e:&:editor <- new-editor s, 0/left, 6/right
2804   editor-render screen, e
2805   assume-console [
2806   ¦ press page-down
2807   ]
2808   run [
2809   ¦ editor-event-loop screen, console, e
2810   ]
2811   screen-should-contain [
2812   ¦ .          .
2813   ¦ .          .
2814   ¦ .c         .
2815   ¦ .d         .
2816   ]
2817   assume-console [
2818   ¦ press page-down
2819   ]
2820   run [
2821   ¦ editor-event-loop screen, console, e
2822   ]
2823   screen-should-contain [
2824   ¦ .          .
2825   ¦ .d         .
2826   ¦ .e         .
2827   ¦ .╌╌╌╌╌╌    .
2828   ]
2829   assume-console [
2830   ¦ press page-up
2831   ]
2832   run [
2833   ¦ editor-event-loop screen, console, e
2834   ]
2835   screen-should-contain [
2836   ¦ .          .
2837   ¦ .          .
2838   ¦ .c         .
2839   ¦ .d         .
2840   ]
2841 ]
2842 
2843 scenario editor-scrolls-up-on-left-arrow [
2844   local-scope
2845   # screen has 1 line for menu + 3 lines
2846   assume-screen 5/width, 4/height
2847   # editor contains >3 lines
2848   s:text <- new [a
2849 b
2850 c
2851 d
2852 e]
2853   e:&:editor <- new-editor s, 0/left, 5/right
2854   editor-render screen, e
2855   # position cursor at top of second page
2856   assume-console [
2857   ¦ press page-down
2858   ]
2859   run [
2860   ¦ editor-event-loop screen, console, e
2861   ]
2862   screen-should-contain [
2863   ¦ .     .
2864   ¦ .c    .
2865   ¦ .d    .
2866   ¦ .e    .
2867   ]
2868   # now try to move left
2869   assume-console [
2870   ¦ press left-arrow
2871   ]
2872   run [
2873   ¦ editor-event-loop screen, console, e
2874   ¦ 3:num/raw <- get *e, cursor-row:offset
2875   ¦ 4:num/raw <- get *e, cursor-column:offset
2876   ]
2877   # screen scrolls
2878   screen-should-contain [
2879   ¦ .     .
2880   ¦ .b    .
2881   ¦ .c    .
2882   ¦ .d    .
2883   ]
2884   memory-should-contain [
2885   ¦ 3 <- 1
2886   ¦ 4 <- 1
2887   ]
2888 ]
2889 
2890 scenario editor-can-scroll-up-to-start-of-file [
2891   local-scope
2892   # screen has 1 line for menu + 3 lines
2893   assume-screen 10/width, 4/height
2894   # initialize editor with >3 lines
2895   s:text <- new [a
2896 b
2897 c
2898 d]
2899   e:&:editor <- new-editor s, 0/left, 10/right
2900   editor-render screen, e
2901   screen-should-contain [
2902   ¦ .          .
2903   ¦ .a         .
2904   ¦ .b         .
2905   ¦ .c         .
2906   ]
2907   # position cursor at top of second page, then try to move up to start of
2908   # text
2909   assume-console [
2910   ¦ press page-down
2911   ¦ press up-arrow
2912   ¦ press up-arrow
2913   ]
2914   run [
2915   ¦ editor-event-loop screen, console, e
2916   ]
2917   # screen slides by one line
2918   screen-should-contain [
2919   ¦ .          .
2920   ¦ .a         .
2921   ¦ .b         .
2922   ¦ .c         .
2923   ]
2924   # try to move up again
2925   assume-console [
2926   ¦ press up-arrow
2927   ]
2928   run [
2929   ¦ editor-event-loop screen, console, e
2930   ]
2931   # screen remains unchanged
2932   screen-should-contain [
2933   ¦ .          .
2934   ¦ .a         .
2935   ¦ .b         .
2936   ¦ .c         .
2937   ]
2938 ]
2939 
2940 # ctrl-f/page-down - render next page if it exists
2941 
2942 scenario editor-can-scroll [
2943   local-scope
2944   assume-screen 10/width, 4/height
2945   s:text <- new [a
2946 b
2947 c
2948 d]
2949   e:&:editor <- new-editor s, 0/left, 10/right
2950   editor-render screen, e
2951   screen-should-contain [
2952   ¦ .          .
2953   ¦ .a         .
2954   ¦ .b         .
2955   ¦ .c         .
2956   ]
2957   # scroll down
2958   assume-console [
2959   ¦ press page-down
2960   ]
2961   run [
2962   ¦ editor-event-loop screen, console, e
2963   ]
2964   # screen shows next page
2965   screen-should-contain [
2966   ¦ .          .
2967   ¦ .c         .
2968   ¦ .d         .
2969   ¦ .╌╌╌╌╌╌╌╌╌╌.
2970   ]
2971 ]
2972 
2973 after <handle-special-character> [
2974   {
2975   ¦ page-down?:bool <- equal c, 6/ctrl-f
2976   ¦ break-unless page-down?
2977   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
2978   ¦ <move-cursor-begin>
2979   ¦ page-down editor
2980   ¦ undo-coalesce-tag:num <- copy 0/never
2981   ¦ <move-cursor-end>
2982   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2983   ¦ movement?:bool <- not-equal top-of-screen, old-top
2984   ¦ return movement?/go-render
2985   }
2986 ]
2987 
2988 after <handle-special-key> [
2989   {
2990   ¦ page-down?:bool <- equal k, 65518/page-down
2991   ¦ break-unless page-down?
2992   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
2993   ¦ <move-cursor-begin>
2994   ¦ page-down editor
2995   ¦ undo-coalesce-tag:num <- copy 0/never
2996   ¦ <move-cursor-end>
2997   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2998   ¦ movement?:bool <- not-equal top-of-screen, old-top
2999   ¦ return movement?/go-render
3000   }
3001 ]
3002 
3003 # page-down skips entire wrapped lines, so it can't scroll past lines
3004 # taking up the entire screen
3005 def page-down editor:&:editor -> editor:&:editor [
3006   local-scope
3007   load-ingredients
3008   # if editor contents don't overflow screen, do nothing
3009   bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset
3010   return-unless bottom-of-screen
3011   # if not, position cursor at final character
3012   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3013   before-cursor:&:duplex-list:char <- prev bottom-of-screen
3014   *editor <- put *editor, before-cursor:offset, before-cursor
3015   # keep one line in common with previous page
3016   {
3017   ¦ last:char <- get *before-cursor, value:offset
3018   ¦ newline?:bool <- equal last, 10/newline
3019   ¦ break-unless newline?:bool
3020   ¦ before-cursor <- prev before-cursor
3021   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
3022   }
3023   # move cursor and top-of-screen to start of that line
3024   move-to-start-of-line editor
3025   before-cursor <- get *editor, before-cursor:offset
3026   *editor <- put *editor, top-of-screen:offset, before-cursor
3027 ]
3028 
3029 scenario editor-does-not-scroll-past-end [
3030   local-scope
3031   assume-screen 10/width, 4/height
3032   s:text <- new [a
3033 b]
3034   e:&:editor <- new-editor s, 0/left, 10/right
3035   editor-render screen, e
3036   screen-should-contain [
3037   ¦ .          .
3038   ¦ .a         .
3039   ¦ .b         .
3040   ¦ .╌╌╌╌╌╌╌╌╌╌.
3041   ]
3042   # scroll down
3043   assume-console [
3044   ¦ press page-down
3045   ]
3046   run [
3047   ¦ editor-event-loop screen, console, e
3048   ]
3049   # screen remains unmodified
3050   screen-should-contain [
3051   ¦ .          .
3052   ¦ .a         .
3053   ¦ .b         .
3054   ¦ .╌╌╌╌╌╌╌╌╌╌.
3055   ]
3056 ]
3057 
3058 scenario editor-starts-next-page-at-start-of-wrapped-line [
3059   local-scope
3060   # screen has 1 line for menu + 3 lines for text
3061   assume-screen 10/width, 4/height
3062   # editor contains a long last line
3063   s:text <- new [a
3064 b
3065 cdefgh]
3066   # editor screen triggers wrap of last line
3067   e:&:editor <- new-editor s, 0/left, 4/right
3068   editor-render screen, e
3069   # some part of last line is not displayed
3070   screen-should-contain [
3071   ¦ .          .
3072   ¦ .a         .
3073   ¦ .b         .
3074   ¦ .cde↩      .
3075   ]
3076   # scroll down
3077   assume-console [
3078   ¦ press page-down
3079   ]
3080   run [
3081   ¦ editor-event-loop screen, console, e
3082   ]
3083   # screen shows entire wrapped line
3084   screen-should-contain [
3085   ¦ .          .
3086   ¦ .cde↩      .
3087   ¦ .fgh       .
3088   ¦ .╌╌╌╌      .
3089   ]
3090 ]
3091 
3092 scenario editor-starts-next-page-at-start-of-wrapped-line-2 [
3093   local-scope
3094   # screen has 1 line for menu + 3 lines for text
3095   assume-screen 10/width, 4/height
3096   # editor contains a very long line that occupies last two lines of screen
3097   # and still has something left over
3098   s:text <- new [a
3099 bcdefgh]
3100   e:&:editor <- new-editor s, 0/left, 4/right
3101   editor-render screen, e
3102   # some part of last line is not displayed
3103   screen-should-contain [
3104   ¦ .          .
3105   ¦ .a         .
3106   ¦ .bcd↩      .
3107   ¦ .efg↩      .
3108   ]
3109   # scroll down
3110   assume-console [
3111   ¦ press page-down
3112   ]
3113   run [
3114   ¦ editor-event-loop screen, console, e
3115   ]
3116   # screen shows entire wrapped line
3117   screen-should-contain [
3118   ¦ .          .
3119   ¦ .bcd↩      .
3120   ¦ .efg↩      .
3121   ¦ .h         .
3122   ]
3123 ]
3124 
3125 # ctrl-b/page-up - render previous page if it exists
3126 
3127 scenario editor-can-scroll-up [
3128   local-scope
3129   assume-screen 10/width, 4/height
3130   s:text <- new [a
3131 b
3132 c
3133 d]
3134   e:&:editor <- new-editor s, 0/left, 10/right
3135   editor-render screen, e
3136   screen-should-contain [
3137   ¦ .          .
3138   ¦ .a         .
3139   ¦ .b         .
3140   ¦ .c         .
3141   ]
3142   # scroll down
3143   assume-console [
3144   ¦ press page-down
3145   ]
3146   run [
3147   ¦ editor-event-loop screen, console, e
3148   ]
3149   # screen shows next page
3150   screen-should-contain [
3151   ¦ .          .
3152   ¦ .c         .
3153   ¦ .d         .
3154   ¦ .╌╌╌╌╌╌╌╌╌╌.
3155   ]
3156   # scroll back up
3157   assume-console [
3158   ¦ press page-up
3159   ]
3160   run [
3161   ¦ editor-event-loop screen, console, e
3162   ]
3163   # screen shows original page again
3164   screen-should-contain [
3165   ¦ .          .
3166   ¦ .a         .
3167   ¦ .b         .
3168   ¦ .c         .
3169   ]
3170 ]
3171 
3172 after <handle-special-character> [
3173   {
3174   ¦ page-up?:bool <- equal c, 2/ctrl-b
3175   ¦ break-unless page-up?
3176   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3177   ¦ <move-cursor-begin>
3178   ¦ editor <- page-up editor, screen-height
3179   ¦ undo-coalesce-tag:num <- copy 0/never
3180   ¦ <move-cursor-end>
3181   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3182   ¦ movement?:bool <- not-equal top-of-screen, old-top
3183   ¦ return movement?/go-render
3184   }
3185 ]
3186 
3187 after <handle-special-key> [
3188   {
3189   ¦ page-up?:bool <- equal k, 65519/page-up
3190   ¦ break-unless page-up?
3191   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3192   ¦ <move-cursor-begin>
3193   ¦ editor <- page-up editor, screen-height
3194   ¦ undo-coalesce-tag:num <- copy 0/never
3195   ¦ <move-cursor-end>
3196   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3197   ¦ movement?:bool <- not-equal top-of-screen, old-top
3198   ¦ # don't bother re-rendering if nothing changed. todo: test this
3199   ¦ return movement?/go-render
3200   }
3201 ]
3202 
3203 def page-up editor:&:editor, screen-height:num -> editor:&:editor [
3204   local-scope
3205   load-ingredients
3206   max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line
3207   count:num <- copy 0
3208   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3209   {
3210   ¦ done?:bool <- greater-or-equal count, max
3211   ¦ break-if done?
3212   ¦ prev:&:duplex-list:char <- before-previous-line top-of-screen, editor
3213   ¦ break-unless prev
3214   ¦ top-of-screen <- copy prev
3215   ¦ *editor <- put *editor, top-of-screen:offset, top-of-screen
3216   ¦ count <- add count, 1
3217   ¦ loop
3218   }
3219 ]
3220 
3221 scenario editor-can-scroll-up-multiple-pages [
3222   local-scope
3223   # screen has 1 line for menu + 3 lines
3224   assume-screen 10/width, 4/height
3225   # initialize editor with 8 lines
3226   s:text <- new [a
3227 b
3228 c
3229 d
3230 e
3231 f
3232 g
3233 h]
3234   e:&:editor <- new-editor s, 0/left, 10/right
3235   editor-render screen, e
3236   screen-should-contain [
3237   ¦ .          .
3238   ¦ .a         .
3239   ¦ .b         .
3240   ¦ .c         .
3241   ]
3242   # scroll down two pages
3243   assume-console [
3244   ¦ press page-down
3245   ¦ press page-down
3246   ]
3247   run [
3248   ¦ editor-event-loop screen, console, e
3249   ]
3250   # screen shows third page
3251   screen-should-contain [
3252   ¦ .          .
3253   ¦ .e         .
3254   ¦ .f         .
3255   ¦ .g         .
3256   ]
3257   # scroll up
3258   assume-console [
3259   ¦ press page-up
3260   ]
3261   run [
3262   ¦ editor-event-loop screen, console, e
3263   ]
3264   # screen shows second page
3265   screen-should-contain [
3266   ¦ .          .
3267   ¦ .c         .
3268   ¦ .d         .
3269   ¦ .e         .
3270   ]
3271   # scroll up again
3272   assume-console [
3273   ¦ press page-up
3274   ]
3275   run [
3276   ¦ editor-event-loop screen, console, e
3277   ]
3278   # screen shows original page again
3279   screen-should-contain [
3280   ¦ .          .
3281   ¦ .a         .
3282   ¦ .b         .
3283   ¦ .c         .
3284   ]
3285 ]
3286 
3287 scenario editor-can-scroll-up-wrapped-lines [
3288   local-scope
3289   # screen has 1 line for menu + 5 lines for text
3290   assume-screen 10/width, 6/height
3291   # editor contains a long line in the first page
3292   s:text <- new [a
3293 b
3294 cdefgh
3295 i
3296 j
3297 k
3298 l
3299 m
3300 n
3301 o]
3302   # editor screen triggers wrap of last line
3303   e:&:editor <- new-editor s, 0/left, 4/right
3304   editor-render screen, e
3305   # some part of last line is not displayed
3306   screen-should-contain [
3307   ¦ .          .
3308   ¦ .a         .
3309   ¦ .b         .
3310   ¦ .cde↩      .
3311   ¦ .fgh       .
3312   ¦ .i         .
3313   ]
3314   # scroll down a page and a line
3315   assume-console [
3316   ¦ press page-down
3317   ¦ left-click 5, 0
3318   ¦ press down-arrow
3319   ]
3320   run [
3321   ¦ editor-event-loop screen, console, e
3322   ]
3323   # screen shows entire wrapped line
3324   screen-should-contain [
3325   ¦ .          .
3326   ¦ .j         .
3327   ¦ .k         .
3328   ¦ .l         .
3329   ¦ .m         .
3330   ¦ .n         .
3331   ]
3332   # now scroll up one page
3333   assume-console [
3334   ¦ press page-up
3335   ]
3336   run [
3337   ¦ editor-event-loop screen, console, e
3338   ]
3339   # screen resets
3340   screen-should-contain [
3341   ¦ .          .
3342   ¦ .b         .
3343   ¦ .cde↩      .
3344   ¦ .fgh       .
3345   ¦ .i         .
3346   ¦ .j         .
3347   ]
3348 ]
3349 
3350 scenario editor-can-scroll-up-wrapped-lines-2 [
3351   local-scope
3352   # screen has 1 line for menu + 3 lines for text
3353   assume-screen 10/width, 4/height
3354   # editor contains a very long line that occupies last two lines of screen
3355   # and still has something left over
3356   s:text <- new [a
3357 bcdefgh]
3358   e:&:editor <- new-editor s, 0/left, 4/right
3359   editor-render screen, e
3360   # some part of last line is not displayed
3361   screen-should-contain [
3362   ¦ .          .
3363   ¦ .a         .
3364   ¦ .bcd↩      .
3365   ¦ .efg↩      .
3366   ]
3367   # scroll down
3368   assume-console [
3369   ¦ press page-down
3370   ]
3371   run [
3372   ¦ editor-event-loop screen, console, e
3373   ]
3374   # screen shows entire wrapped line
3375   screen-should-contain [
3376   ¦ .          .
3377   ¦ .bcd↩      .
3378   ¦ .efg↩      .
3379   ¦ .h         .
3380   ]
3381   # scroll back up
3382   assume-console [
3383   ¦ press page-up
3384   ]
3385   run [
3386   ¦ editor-event-loop screen, console, e
3387   ]
3388   # screen resets
3389   screen-should-contain [
3390   ¦ .          .
3391   ¦ .a         .
3392   ¦ .bcd↩      .
3393   ¦ .efg↩      .
3394   ]
3395 ]
3396 
3397 scenario editor-can-scroll-up-past-nonempty-lines [
3398   local-scope
3399   assume-screen 10/width, 4/height
3400   # text with empty line in second screen
3401   s:text <- new [axx
3402 bxx
3403 cxx
3404 dxx
3405 exx
3406 fxx
3407 gxx
3408 hxx
3409 ]
3410   e:&:editor <- new-editor s, 0/left, 4/right
3411   editor-render screen, e
3412   screen-should-contain [
3413   ¦ .          .
3414   ¦ .axx       .
3415   ¦ .bxx       .
3416   ¦ .cxx       .
3417   ]
3418   assume-console [
3419   ¦ press page-down
3420   ]
3421   run [
3422   ¦ editor-event-loop screen, console, e
3423   ]
3424   screen-should-contain [
3425   ¦ .          .
3426   ¦ .cxx       .
3427   ¦ .dxx       .
3428   ¦ .exx       .
3429   ]
3430   assume-console [
3431   ¦ press page-down
3432   ]
3433   run [
3434   ¦ editor-event-loop screen, console, e
3435   ]
3436   screen-should-contain [
3437   ¦ .          .
3438   ¦ .exx       .
3439   ¦ .fxx       .
3440   ¦ .gxx       .
3441   ]
3442   # scroll back up past empty line
3443   assume-console [
3444   ¦ press page-up
3445   ]
3446   run [
3447   ¦ editor-event-loop screen, console, e
3448   ]
3449   screen-should-contain [
3450   ¦ .          .
3451   ¦ .cxx       .
3452   ¦ .dxx       .
3453   ¦ .exx       .
3454   ]
3455 ]
3456 
3457 scenario editor-can-scroll-up-past-empty-lines [
3458   local-scope
3459   assume-screen 10/width, 4/height
3460   # text with empty line in second screen
3461   s:text <- new [axy
3462 bxy
3463 cxy
3464 
3465 dxy
3466 exy
3467 fxy
3468 gxy
3469 ]
3470   e:&:editor <- new-editor s, 0/left, 4/right
3471   editor-render screen, e
3472   screen-should-contain [
3473   ¦ .          .
3474   ¦ .axy       .
3475   ¦ .bxy       .
3476   ¦ .cxy       .
3477   ]
3478   assume-console [
3479   ¦ press page-down
3480   ]
3481   run [
3482   ¦ editor-event-loop screen, console, e
3483   ]
3484   screen-should-contain [
3485   ¦ .          .
3486   ¦ .cxy       .
3487   ¦ .          .
3488   ¦ .dxy       .
3489   ]
3490   assume-console [
3491   ¦ press page-down
3492   ]
3493   run [
3494   ¦ editor-event-loop screen, console, e
3495   ]
3496   screen-should-contain [
3497   ¦ .          .
3498   ¦ .dxy       .
3499   ¦ .exy       .
3500   ¦ .fxy       .
3501   ]
3502   # scroll back up past empty line
3503   assume-console [
3504   ¦ press page-up
3505   ]
3506   run [
3507   ¦ editor-event-loop screen, console, e
3508   ]
3509   screen-should-contain [
3510   ¦ .          .
3511   ¦ .cxy       .
3512   ¦ .          .
3513   ¦ .dxy       .
3514   ]
3515 ]
3516 
3517 # ctrl-s - scroll up by one line
3518 # todo: scenarios
3519 
3520 after <handle-special-character> [
3521   {
3522   ¦ scroll-up?:bool <- equal c, 19/ctrl-s
3523   ¦ break-unless scroll-up?
3524   ¦ <move-cursor-begin>
3525   ¦ go-render?:bool, editor <- line-up editor, screen-height
3526   ¦ undo-coalesce-tag:num <- copy 5/line-up
3527   ¦ <move-cursor-end>
3528   ¦ return go-render?
3529   }
3530 ]
3531 
3532 def line-up editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
3533   local-scope
3534   load-ingredients
3535   left:num <- get *editor, left:offset
3536   right:num <- get *editor, right:offset
3537   max:num <- subtract right, left
3538   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3539   new-top:&:duplex-list:char <- before-start-of-next-line old-top, max
3540   movement?:bool <- not-equal old-top, new-top
3541   {
3542   ¦ break-unless movement?
3543   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3544   }
3545   return movement?
3546 ]
3547 
3548 # ctrl-x - scroll down by one line
3549 # todo: scenarios
3550 
3551 after <handle-special-character> [
3552   {
3553   ¦ scroll-down?:bool <- equal c, 24/ctrl-x
3554   ¦ break-unless scroll-down?
3555   ¦ <move-cursor-begin>
3556   ¦ go-render?:bool, editor <- line-down editor, screen-height
3557   ¦ undo-coalesce-tag:num <- copy 6/line-down
3558   ¦ <move-cursor-end>
3559   ¦ return go-render?
3560   }
3561 ]
3562 
3563 def line-down editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
3564   local-scope
3565   load-ingredients
3566   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3567   new-top:&:duplex-list:char <- before-previous-line old-top, editor
3568   movement?:bool <- not-equal old-top, new-top
3569   {
3570   ¦ break-unless movement?
3571   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3572   }
3573   return movement?
3574 ]
3575 
3576 # ctrl-t - move current line to top of screen
3577 # todo: scenarios
3578 
3579 after <handle-special-character> [
3580   {
3581   ¦ scroll-down?:bool <- equal c, 20/ctrl-t
3582   ¦ break-unless scroll-down?
3583   ¦ <move-cursor-begin>
3584   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3585   ¦ cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3586   ¦ cursor <- next cursor
3587   ¦ new-top:&:duplex-list:char <- before-previous-line cursor, editor
3588   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3589   ¦ *editor <- put *editor, cursor-row:offset, 1
3590   ¦ go-render?:bool <- not-equal new-top, old-top
3591   ¦ undo-coalesce-tag:num <- copy 0/never
3592   ¦ <move-cursor-end>
3593   ¦ return go-render?
3594   }
3595 ]
3596 
3597 # ctrl-/ - comment/uncomment current line
3598 # todo: scenarios
3599 
3600 after <handle-special-character> [
3601   {
3602   ¦ comment-toggle?:bool <- equal c, 31/ctrl-slash
3603   ¦ break-unless comment-toggle?
3604   ¦ cursor-column:num <- get *editor, cursor-column:offset
3605   ¦ data:&:duplex-list:char <- get *editor, data:offset
3606   ¦ <insert-character-begin>
3607   ¦ cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3608   ¦ {
3609   ¦ ¦ next:&:duplex-list:char <- next cursor
3610   ¦ ¦ break-unless next
3611   ¦ ¦ cursor <- copy next
3612   ¦ }
3613   ¦ before-line-start:&:duplex-list:char <- before-previous-line cursor, editor
3614   ¦ line-start:&:duplex-list:char <- next before-line-start
3615   ¦ commented-out?:bool <- match line-start, [#? ]  # comment prefix
3616   ¦ {
3617   ¦ ¦ break-unless commented-out?
3618   ¦ ¦ # uncomment
3619   ¦ ¦ data <- remove line-start, 3/length-comment-prefix, data
3620   ¦ ¦ cursor-column <- subtract cursor-column, 3/length-comment-prefix
3621   ¦ }
3622   ¦ {
3623   ¦ ¦ break-if commented-out?
3624   ¦ ¦ # comment
3625   ¦ ¦ insert before-line-start, [#? ]
3626   ¦ ¦ cursor-column <- add cursor-column, 3/length-comment-prefix
3627   ¦ }
3628   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
3629   ¦ <insert-character-end>
3630   ¦ return 1/do-render
3631   }
3632 ]
3633 
3634 scenario editor-comments-empty-line [
3635   local-scope
3636   assume-screen 10/width, 5/height
3637   e:&:editor <- new-editor [], 0/left, 5/right
3638   assume-console [
3639   ¦ press ctrl-slash
3640   ]
3641   run [
3642   ¦ editor-event-loop screen, console, e
3643   ¦ 4:num/raw <- get *e, cursor-row:offset
3644   ¦ 5:num/raw <- get *e, cursor-column:offset
3645   ]
3646   screen-should-contain [
3647   ¦ .          .
3648   ¦ .#?        .
3649   ¦ .╌╌╌╌╌     .
3650   ¦ .          .
3651   ]
3652   memory-should-contain [
3653   ¦ 4 <- 1
3654   ¦ 5 <- 3
3655   ]
3656 ]
3657 
3658 scenario editor-comments-at-start-of-contents [
3659   local-scope
3660   assume-screen 10/width, 5/height
3661   e:&:editor <- new-editor [ab], 0/left, 10/right
3662   assume-console [
3663   ¦ press ctrl-slash
3664   ]
3665   run [
3666   ¦ editor-event-loop screen, console, e
3667   ¦ 4:num/raw <- get *e, cursor-row:offset
3668   ¦ 5:num/raw <- get *e, cursor-column:offset
3669   ]
3670   screen-should-contain [
3671   ¦ .          .
3672   ¦ .#? ab     .
3673   ¦ .╌╌╌╌╌╌╌╌╌╌.
3674   ¦ .          .
3675   ]
3676   memory-should-contain [
3677   ¦ 4 <- 1
3678   ¦ 5 <- 3
3679   ]
3680 ]
3681 
3682 scenario editor-comments-at-end-of-contents [
3683   local-scope
3684   assume-screen 10/width, 5/height
3685   e:&:editor <- new-editor [ab], 0/left, 10/right
3686   assume-console [
3687   ¦ left-click 1, 7
3688   ¦ press ctrl-slash
3689   ]
3690   run [
3691   ¦ editor-event-loop screen, console, e
3692   ¦ 4:num/raw <- get *e, cursor-row:offset
3693   ¦ 5:num/raw <- get *e, cursor-column:offset
3694   ]
3695   screen-should-contain [
3696   ¦ .          .
3697   ¦ .#? ab     .
3698   ¦ .╌╌╌╌╌╌╌╌╌╌.
3699   ¦ .          .
3700   ]
3701   memory-should-contain [
3702   ¦ 4 <- 1
3703   ¦ 5 <- 5
3704   ]
3705 ]