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