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   editor-render screen, e
1631   $clear-trace
1632   # start on second line, press ctrl-u
1633   assume-console [
1634   ¦ left-click 2, 2
1635   ¦ press ctrl-u
1636   ]
1637   run [
1638   ¦ editor-event-loop screen, console, e
1639   ]
1640   # cursor deletes to start of line
1641   screen-should-contain [
1642   ¦ .          .
1643   ¦ .123       .
1644   ¦ .6         .
1645   ¦ .╌╌╌╌╌╌╌╌╌╌.
1646   ¦ .          .
1647   ]
1648   check-trace-count-for-label 10, [print-character]
1649 ]
1650 
1651 after <handle-special-character> [
1652   {
1653   ¦ delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
1654   ¦ break-unless delete-to-start-of-line?
1655   ¦ <delete-to-start-of-line-begin>
1656   ¦ deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
1657   ¦ <delete-to-start-of-line-end>
1658   ¦ go-render?:bool <- minimal-render-for-ctrl-u editor, screen
1659   ¦ return
1660   }
1661 ]
1662 
1663 def minimal-render-for-ctrl-u editor:&:editor, screen:&:screen -> go-render?:bool, screen:&:screen [
1664   local-scope
1665   load-ingredients
1666   curr-row:num <- get *editor, cursor-row:offset
1667   curr-column:num <- get *editor, cursor-column:offset
1668   left:num <- get *editor, left:offset
1669   right:num <- get *editor, right:offset
1670   end:num <- subtract left, right
1671   # accumulate the current line as text and render it
1672   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1673   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1674   i:num <- copy 0
1675   {
1676   ¦ i <- add i, 1
1677   ¦ # if we have a wrapped line, give up and render the whole screen
1678   ¦ wrap?:bool <- equal i, end
1679   ¦ return-if wrap?, 1/do-render
1680   ¦ curr <- next curr
1681   ¦ break-unless curr
1682   ¦ c:char <- get *curr, value:offset
1683   ¦ b:bool <- equal c, 10
1684   ¦ break-if b
1685   ¦ buf <- append buf, c
1686   ¦ loop
1687   }
1688   curr-line:text <- buffer-to-array buf
1689   render-code screen, curr-line, curr-column, right, curr-row
1690   return 0/dont-render
1691 ]
1692 
1693 def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1694   local-scope
1695   load-ingredients
1696   # compute range to delete
1697   init:&:duplex-list:char <- get *editor, data:offset
1698   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1699   start:&:duplex-list:char <- copy before-cursor
1700   end:&:duplex-list:char <- next before-cursor
1701   {
1702   ¦ at-start-of-text?:bool <- equal start, init
1703   ¦ break-if at-start-of-text?
1704   ¦ curr:char <- get *start, value:offset
1705   ¦ at-start-of-line?:bool <- equal curr, 10/newline
1706   ¦ break-if at-start-of-line?
1707   ¦ start <- prev start
1708   ¦ assert start, [delete-to-start-of-line tried to move before start of text]
1709   ¦ loop
1710   }
1711   # snip it out
1712   result:&:duplex-list:char <- next start
1713   remove-between start, end
1714   # adjust cursor
1715   before-cursor <- copy start
1716   *editor <- put *editor, before-cursor:offset, before-cursor
1717   left:num <- get *editor, left:offset
1718   *editor <- put *editor, cursor-column:offset, left
1719 ]
1720 
1721 def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [
1722   local-scope
1723   load-ingredients
1724   return-unless s
1725   color:num <- copy 7/white
1726   column:num <- copy left
1727   screen <- move-cursor screen, row, column
1728   screen-height:num <- screen-height screen
1729   i:num <- copy 0
1730   len:num <- length *s
1731   {
1732   ¦ +next-character
1733   ¦ done?:bool <- greater-or-equal i, len
1734   ¦ break-if done?
1735   ¦ done? <- greater-or-equal row, screen-height
1736   ¦ break-if done?
1737   ¦ c:char <- index *s, i
1738   ¦ <character-c-received>
1739   ¦ {
1740   ¦ ¦ # newline? move to left rather than 0
1741   ¦ ¦ newline?:bool <- equal c, 10/newline
1742   ¦ ¦ break-unless newline?
1743   ¦ ¦ # clear rest of line in this window
1744   ¦ ¦ {
1745   ¦ ¦ ¦ done?:bool <- greater-than column, right
1746   ¦ ¦ ¦ break-if done?
1747   ¦ ¦ ¦ space:char <- copy 32/space
1748   ¦ ¦ ¦ print screen, space
1749   ¦ ¦ ¦ column <- add column, 1
1750   ¦ ¦ ¦ loop
1751   ¦ ¦ }
1752   ¦ ¦ row <- add row, 1
1753   ¦ ¦ column <- copy left
1754   ¦ ¦ screen <- move-cursor screen, row, column
1755   ¦ ¦ i <- add i, 1
1756   ¦ ¦ loop +next-character
1757   ¦ }
1758   ¦ {
1759   ¦ ¦ # at right? wrap.
1760   ¦ ¦ at-right?:bool <- equal column, right
1761   ¦ ¦ break-unless at-right?
1762   ¦ ¦ # print wrap icon
1763   ¦ ¦ wrap-icon:char <- copy 8617/loop-back-to-left
1764   ¦ ¦ print screen, wrap-icon, 245/grey
1765   ¦ ¦ column <- copy left
1766   ¦ ¦ row <- add row, 1
1767   ¦ ¦ screen <- move-cursor screen, row, column
1768   ¦ ¦ # don't increment i
1769   ¦ ¦ loop +next-character
1770   ¦ }
1771   ¦ i <- add i, 1
1772   ¦ print screen, c, color
1773   ¦ column <- add column, 1
1774   ¦ loop
1775   }
1776   was-at-left?:bool <- equal column, left
1777   clear-line-until screen, right
1778   {
1779   ¦ break-if was-at-left?
1780   ¦ row <- add row, 1
1781   }
1782   move-cursor screen, row, left
1783 ]
1784 
1785 scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [
1786   local-scope
1787   assume-screen 10/width, 5/height
1788   s:text <- new [123
1789 456]
1790   e:&:editor <- new-editor s, 0/left, 10/right
1791   editor-render screen, e
1792   $clear-trace
1793   # start on first line (no newline before), press ctrl-u
1794   assume-console [
1795   ¦ left-click 1, 2
1796   ¦ press ctrl-u
1797   ]
1798   run [
1799   ¦ editor-event-loop screen, console, e
1800   ]
1801   # cursor deletes to start of line
1802   screen-should-contain [
1803   ¦ .          .
1804   ¦ .3         .
1805   ¦ .456       .
1806   ¦ .╌╌╌╌╌╌╌╌╌╌.
1807   ¦ .          .
1808   ]
1809   check-trace-count-for-label 10, [print-character]
1810 ]
1811 
1812 scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [
1813   local-scope
1814   assume-screen 10/width, 5/height
1815   s:text <- new [123
1816 456]
1817   e:&:editor <- new-editor s, 0/left, 10/right
1818   editor-render screen, e
1819   $clear-trace
1820   # start past end of line, press ctrl-u
1821   assume-console [
1822   ¦ left-click 1, 3
1823   ¦ press ctrl-u
1824   ]
1825   run [
1826   ¦ editor-event-loop screen, console, e
1827   ]
1828   # cursor deletes to start of line
1829   screen-should-contain [
1830   ¦ .          .
1831   ¦ .          .
1832   ¦ .456       .
1833   ¦ .╌╌╌╌╌╌╌╌╌╌.
1834   ¦ .          .
1835   ]
1836   check-trace-count-for-label 10, [print-character]
1837 ]
1838 
1839 scenario editor-deletes-to-start-of-final-line-with-ctrl-u [
1840   local-scope
1841   assume-screen 10/width, 5/height
1842   s:text <- new [123
1843 456]
1844   e:&:editor <- new-editor s, 0/left, 10/right
1845   editor-render screen, e
1846   $clear-trace
1847   # start past end of final line, press ctrl-u
1848   assume-console [
1849   ¦ left-click 2, 3
1850   ¦ press ctrl-u
1851   ]
1852   run [
1853   ¦ editor-event-loop screen, console, e
1854   ]
1855   # cursor deletes to start of line
1856   screen-should-contain [
1857   ¦ .          .
1858   ¦ .123       .
1859   ¦ .          .
1860   ¦ .╌╌╌╌╌╌╌╌╌╌.
1861   ¦ .          .
1862   ]
1863   check-trace-count-for-label 10, [print-character]
1864 ]
1865 
1866 # ctrl-k - delete text from cursor to end of line (but not the newline)
1867 
1868 scenario editor-deletes-to-end-of-line-with-ctrl-k [
1869   local-scope
1870   assume-screen 10/width, 5/height
1871   s:text <- new [123
1872 456]
1873   e:&:editor <- new-editor s, 0/left, 10/right
1874   editor-render screen, e
1875   $clear-trace
1876   # start on first line, press ctrl-k
1877   assume-console [
1878   ¦ left-click 1, 1
1879   ¦ press ctrl-k
1880   ]
1881   run [
1882   ¦ editor-event-loop screen, console, e
1883   ]
1884   # cursor deletes to end of line
1885   screen-should-contain [
1886   ¦ .          .
1887   ¦ .1         .
1888   ¦ .456       .
1889   ¦ .╌╌╌╌╌╌╌╌╌╌.
1890   ¦ .          .
1891   ]
1892   check-trace-count-for-label 9, [print-character]
1893 ]
1894 
1895 after <handle-special-character> [
1896   {
1897   ¦ delete-to-end-of-line?:bool <- equal c, 11/ctrl-k
1898   ¦ break-unless delete-to-end-of-line?
1899   ¦ <delete-to-end-of-line-begin>
1900   ¦ deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor
1901   ¦ <delete-to-end-of-line-end>
1902   ¦ # checks if we can do a minimal render and if we can it will do a minimal render
1903   ¦ go-render?:bool <- minimal-render-for-ctrl-k editor, screen, deleted-cells
1904   ¦ return
1905   }
1906 ]
1907 
1908 def minimal-render-for-ctrl-k editor:&:editor, screen:&:screen, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
1909   local-scope
1910   load-ingredients
1911   # if we deleted nothing, there's nothing to render
1912   return-unless deleted-cells, 0/dont-render
1913   # if we have a wrapped line, give up and render the whole screen
1914   curr-column:num <- get *editor, cursor-column:offset
1915   num-deleted-cells:num <- length, deleted-cells
1916   old-row-len:num <- add curr-column, num-deleted-cells
1917   left:num <- get *editor, left:offset
1918   right:num <- get *editor, right:offset
1919   end:num <- subtract right, left
1920   wrap?:bool <- greater-or-equal old-row-len, end
1921   return-if wrap?, 1/do-render
1922   # accumulate the current line as text and render it
1923   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1924   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1925   i:num <- copy 0
1926   {
1927   ¦ i <- add i, 1
1928   ¦ # check if we are at the end of the line
1929   ¦ curr <- next curr
1930   ¦ break-unless curr
1931   ¦ c:char <- get *curr, value:offset
1932   ¦ # check if we have a newline
1933   ¦ b:bool <- equal c, 10
1934   ¦ break-if b
1935   ¦ buf <- append buf, c
1936   ¦ loop
1937   }
1938   curr-line:text <- buffer-to-array buf
1939   curr-row:num <- get *editor, cursor-row:offset
1940   render-code screen, curr-line, curr-column, right, curr-row
1941   return 0/dont-render
1942 ]
1943 
1944 def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1945   local-scope
1946   load-ingredients
1947   # compute range to delete
1948   start:&:duplex-list:char <- get *editor, before-cursor:offset
1949   end:&:duplex-list:char <- next start
1950   {
1951   ¦ at-end-of-text?:bool <- equal end, 0/null
1952   ¦ break-if at-end-of-text?
1953   ¦ curr:char <- get *end, value:offset
1954   ¦ at-end-of-line?:bool <- equal curr, 10/newline
1955   ¦ break-if at-end-of-line?
1956   ¦ end <- next end
1957   ¦ loop
1958   }
1959   # snip it out
1960   result <- next start
1961   remove-between start, end
1962 ]
1963 
1964 scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [
1965   local-scope
1966   assume-screen 10/width, 5/height
1967   s:text <- new [123
1968 456]
1969   e:&:editor <- new-editor s, 0/left, 10/right
1970   editor-render screen, e
1971   $clear-trace
1972   # start on second line (no newline after), press ctrl-k
1973   assume-console [
1974   ¦ left-click 2, 1
1975   ¦ press ctrl-k
1976   ]
1977   run [
1978   ¦ editor-event-loop screen, console, e
1979   ]
1980   # cursor deletes to end of line
1981   screen-should-contain [
1982   ¦ .          .
1983   ¦ .123       .
1984   ¦ .4         .
1985   ¦ .╌╌╌╌╌╌╌╌╌╌.
1986   ¦ .          .
1987   ]
1988   check-trace-count-for-label 9, [print-character]
1989 ]
1990 
1991 scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [
1992   local-scope
1993   assume-screen 10/width, 5/height
1994   s:text <- new [123
1995 456]
1996   e:&:editor <- new-editor s, 0/left, 10/right
1997   editor-render screen, e
1998   $clear-trace
1999   # start at end of line
2000   assume-console [
2001   ¦ left-click 1, 2
2002   ¦ press ctrl-k
2003   ]
2004   run [
2005   ¦ editor-event-loop screen, console, e
2006   ]
2007   # cursor deletes just last character
2008   screen-should-contain [
2009   ¦ .          .
2010   ¦ .12        .
2011   ¦ .456       .
2012   ¦ .╌╌╌╌╌╌╌╌╌╌.
2013   ¦ .          .
2014   ]
2015   check-trace-count-for-label 8, [print-character]
2016 ]
2017 
2018 scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [
2019   local-scope
2020   assume-screen 10/width, 5/height
2021   s:text <- new [123
2022 456]
2023   e:&:editor <- new-editor s, 0/left, 10/right
2024   editor-render screen, e
2025   $clear-trace
2026   # start past end of line
2027   assume-console [
2028   ¦ left-click 1, 3
2029   ¦ press ctrl-k
2030   ]
2031   run [
2032   ¦ editor-event-loop screen, console, e
2033   ]
2034   # cursor deletes nothing
2035   screen-should-contain [
2036   ¦ .          .
2037   ¦ .123       .
2038   ¦ .456       .
2039   ¦ .╌╌╌╌╌╌╌╌╌╌.
2040   ¦ .          .
2041   ]
2042   check-trace-count-for-label 7, [print-character]
2043 ]
2044 
2045 scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [
2046   local-scope
2047   assume-screen 10/width, 5/height
2048   s:text <- new [123
2049 456]
2050   e:&:editor <- new-editor s, 0/left, 10/right
2051   editor-render screen, e
2052   $clear-trace
2053   # start at end of text
2054   assume-console [
2055   ¦ left-click 2, 2
2056   ¦ press ctrl-k
2057   ]
2058   run [
2059   ¦ editor-event-loop screen, console, e
2060   ]
2061   # cursor deletes just the final character
2062   screen-should-contain [
2063   ¦ .          .
2064   ¦ .123       .
2065   ¦ .45        .
2066   ¦ .╌╌╌╌╌╌╌╌╌╌.
2067   ¦ .          .
2068   ]
2069   check-trace-count-for-label 8, [print-character]
2070 ]
2071 
2072 scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
2073   local-scope
2074   assume-screen 10/width, 5/height
2075   s:text <- new [123
2076 456]
2077   e:&:editor <- new-editor s, 0/left, 10/right
2078   editor-render screen, e
2079   $clear-trace
2080   # start past end of text
2081   assume-console [
2082   ¦ left-click 2, 3
2083   ¦ press ctrl-k
2084   ]
2085   run [
2086   ¦ editor-event-loop screen, console, e
2087   ]
2088   # cursor deletes nothing
2089   screen-should-contain [
2090   ¦ .          .
2091   ¦ .123       .
2092   ¦ .456       .
2093   ¦ .╌╌╌╌╌╌╌╌╌╌.
2094   ¦ .          .
2095   ]
2096   # no prints necessary
2097   check-trace-count-for-label 0, [print-character]
2098 ]
2099 
2100 scenario editor-deletes-to-end-of-wrapped-line-with-ctrl-k [
2101   local-scope
2102   assume-screen 10/width, 5/height
2103   # create an editor with the first line wrapping to a second screen row
2104   s:text <- new [1234
2105 567]
2106   e:&:editor <- new-editor s, 0/left, 4/right
2107   editor-render screen, e
2108   $clear-trace
2109   # delete all of the first wrapped line
2110   assume-console [
2111   ¦ press ctrl-k
2112   ]
2113   run [
2114   ¦ editor-event-loop screen, console, e
2115   ]
2116   # screen shows an empty unwrapped first line
2117   screen-should-contain [
2118   ¦ .          .
2119   ¦ .          .
2120   ¦ .567       .
2121   ¦ .╌╌╌╌      .
2122   ¦ .          .
2123   ]
2124   # entire screen is refreshed
2125   check-trace-count-for-label 16, [print-character]
2126 ]
2127 
2128 # cursor-down can scroll if necessary
2129 
2130 scenario editor-can-scroll-down-using-arrow-keys [
2131   local-scope
2132   # screen has 1 line for menu + 3 lines
2133   assume-screen 10/width, 4/height
2134   # initialize editor with >3 lines
2135   s:text <- new [a
2136 b
2137 c
2138 d]
2139   e:&:editor <- new-editor s, 0/left, 10/right
2140   editor-render screen, e
2141   screen-should-contain [
2142   ¦ .          .
2143   ¦ .a         .
2144   ¦ .b         .
2145   ¦ .c         .
2146   ]
2147   # position cursor at last line, then try to move further down
2148   assume-console [
2149   ¦ left-click 3, 0
2150   ¦ press down-arrow
2151   ]
2152   run [
2153   ¦ editor-event-loop screen, console, e
2154   ]
2155   # screen slides by one line
2156   screen-should-contain [
2157   ¦ .          .
2158   ¦ .b         .
2159   ¦ .c         .
2160   ¦ .d         .
2161   ]
2162 ]
2163 
2164 after <scroll-down> [
2165   trace 10, [app], [scroll down]
2166   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2167   left:num <- get *editor, left:offset
2168   right:num <- get *editor, right:offset
2169   max:num <- subtract right, left
2170   old-top:&:duplex-list:char <- copy top-of-screen
2171   top-of-screen <- before-start-of-next-line top-of-screen, max
2172   *editor <- put *editor, top-of-screen:offset, top-of-screen
2173   no-movement?:bool <- equal old-top, top-of-screen
2174   return-if no-movement?, 0/don't-render
2175 ]
2176 
2177 # takes a pointer into the doubly-linked list, scans ahead at most 'max'
2178 # positions until the next newline
2179 # beware: never return null pointer.
2180 def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [
2181   local-scope
2182   load-ingredients
2183   count:num <- copy 0
2184   curr:&:duplex-list:char <- copy original
2185   # skip the initial newline if it exists
2186   {
2187   ¦ c:char <- get *curr, value:offset
2188   ¦ at-newline?:bool <- equal c, 10/newline
2189   ¦ break-unless at-newline?
2190   ¦ curr <- next curr
2191   ¦ count <- add count, 1
2192   }
2193   {
2194   ¦ return-unless curr, original
2195   ¦ done?:bool <- greater-or-equal count, max
2196   ¦ break-if done?
2197   ¦ c:char <- get *curr, value:offset
2198   ¦ at-newline?:bool <- equal c, 10/newline
2199   ¦ break-if at-newline?
2200   ¦ curr <- next curr
2201   ¦ count <- add count, 1
2202   ¦ loop
2203   }
2204   return-unless curr, original
2205   return curr
2206 ]
2207 
2208 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
2209   local-scope
2210   # screen has 1 line for menu + 3 lines
2211   assume-screen 10/width, 4/height
2212   # initialize editor with a long, wrapped line and more than a screen of
2213   # other lines
2214   s:text <- new [abcdef
2215 g
2216 h
2217 i]
2218   e:&:editor <- new-editor s, 0/left, 5/right
2219   editor-render screen, e
2220   screen-should-contain [
2221   ¦ .          .
2222   ¦ .abcd↩     .
2223   ¦ .ef        .
2224   ¦ .g         .
2225   ]
2226   # position cursor at last line, then try to move further down
2227   assume-console [
2228   ¦ left-click 3, 0
2229   ¦ press down-arrow
2230   ]
2231   run [
2232   ¦ editor-event-loop screen, console, e
2233   ]
2234   # screen shows partial wrapped line
2235   screen-should-contain [
2236   ¦ .          .
2237   ¦ .ef        .
2238   ¦ .g         .
2239   ¦ .h         .
2240   ]
2241 ]
2242 
2243 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
2244   local-scope
2245   # screen has 1 line for menu + 3 lines
2246   assume-screen 10/width, 4/height
2247   # editor starts with a long line wrapping twice
2248   s:text <- new [abcdefghij
2249 k
2250 l
2251 m]
2252   e:&:editor <- new-editor s, 0/left, 5/right
2253   # position cursor at last line, then try to move further down
2254   assume-console [
2255   ¦ left-click 3, 0
2256   ¦ press down-arrow
2257   ]
2258   run [
2259   ¦ editor-event-loop screen, console, e
2260   ]
2261   # screen shows partial wrapped line containing a wrap icon
2262   screen-should-contain [
2263   ¦ .          .
2264   ¦ .efgh↩     .
2265   ¦ .ij        .
2266   ¦ .k         .
2267   ]
2268   # scroll down again
2269   assume-console [
2270   ¦ press down-arrow
2271   ]
2272   run [
2273   ¦ editor-event-loop screen, console, e
2274   ]
2275   # screen shows partial wrapped line
2276   screen-should-contain [
2277   ¦ .          .
2278   ¦ .ij        .
2279   ¦ .k         .
2280   ¦ .l         .
2281   ]
2282 ]
2283 
2284 scenario editor-scrolls-down-when-line-wraps [
2285   local-scope
2286   # screen has 1 line for menu + 3 lines
2287   assume-screen 5/width, 4/height
2288   # editor contains a long line in the third line
2289   s:text <- new [a
2290 b
2291 cdef]
2292   e:&:editor <- new-editor s, 0/left, 5/right
2293   # position cursor at end, type a character
2294   assume-console [
2295   ¦ left-click 3, 4
2296   ¦ type [g]
2297   ]
2298   run [
2299   ¦ editor-event-loop screen, console, e
2300   ¦ 3:num/raw <- get *e, cursor-row:offset
2301   ¦ 4:num/raw <- get *e, cursor-column:offset
2302   ]
2303   # screen scrolls
2304   screen-should-contain [
2305   ¦ .     .
2306   ¦ .b    .
2307   ¦ .cdef↩.
2308   ¦ .g    .
2309   ]
2310   memory-should-contain [
2311   ¦ 3 <- 3
2312   ¦ 4 <- 1
2313   ]
2314 ]
2315 
2316 scenario editor-scrolls-down-on-newline [
2317   local-scope
2318   assume-screen 5/width, 4/height
2319   # position cursor after last line and type newline
2320   s:text <- new [a
2321 b
2322 c]
2323   e:&:editor <- new-editor s, 0/left, 5/right
2324   assume-console [
2325   ¦ left-click 3, 4
2326   ¦ type [
2327 ]
2328   ]
2329   run [
2330   ¦ editor-event-loop screen, console, e
2331   ¦ 3:num/raw <- get *e, cursor-row:offset
2332   ¦ 4:num/raw <- get *e, cursor-column:offset
2333   ]
2334   # screen scrolls
2335   screen-should-contain [
2336   ¦ .     .
2337   ¦ .b    .
2338   ¦ .c    .
2339   ¦ .     .
2340   ]
2341   memory-should-contain [
2342   ¦ 3 <- 3
2343   ¦ 4 <- 0
2344   ]
2345 ]
2346 
2347 scenario editor-scrolls-down-on-right-arrow [
2348   local-scope
2349   # screen has 1 line for menu + 3 lines
2350   assume-screen 5/width, 4/height
2351   # editor contains a wrapped line
2352   s:text <- new [a
2353 b
2354 cdefgh]
2355   e:&:editor <- new-editor s, 0/left, 5/right
2356   # position cursor at end of screen and try to move right
2357   assume-console [
2358   ¦ left-click 3, 3
2359   ¦ press right-arrow
2360   ]
2361   run [
2362   ¦ editor-event-loop screen, console, e
2363   ¦ 3:num/raw <- get *e, cursor-row:offset
2364   ¦ 4:num/raw <- get *e, cursor-column:offset
2365   ]
2366   # screen scrolls
2367   screen-should-contain [
2368   ¦ .     .
2369   ¦ .b    .
2370   ¦ .cdef↩.
2371   ¦ .gh   .
2372   ]
2373   memory-should-contain [
2374   ¦ 3 <- 3
2375   ¦ 4 <- 0
2376   ]
2377 ]
2378 
2379 scenario editor-scrolls-down-on-right-arrow-2 [
2380   local-scope
2381   # screen has 1 line for menu + 3 lines
2382   assume-screen 5/width, 4/height
2383   # editor contains more lines than can fit on screen
2384   s:text <- new [a
2385 b
2386 c
2387 d]
2388   e:&:editor <- new-editor s, 0/left, 5/right
2389   # position cursor at end of screen and try to move right
2390   assume-console [
2391   ¦ left-click 3, 3
2392   ¦ press right-arrow
2393   ]
2394   run [
2395   ¦ editor-event-loop screen, console, e
2396   ¦ 3:num/raw <- get *e, cursor-row:offset
2397   ¦ 4:num/raw <- get *e, cursor-column:offset
2398   ]
2399   # screen scrolls
2400   screen-should-contain [
2401   ¦ .     .
2402   ¦ .b    .
2403   ¦ .c    .
2404   ¦ .d    .
2405   ]
2406   memory-should-contain [
2407   ¦ 3 <- 3
2408   ¦ 4 <- 0
2409   ]
2410 ]
2411 
2412 scenario editor-scrolls-at-end-on-down-arrow [
2413   local-scope
2414   assume-screen 10/width, 5/height
2415   s:text <- new [abc
2416 de]
2417   e:&:editor <- new-editor s, 0/left, 10/right
2418   editor-render screen, e
2419   $clear-trace
2420   # try to move down past end of text
2421   assume-console [
2422   ¦ left-click 2, 0
2423   ¦ press down-arrow
2424   ]
2425   run [
2426   ¦ editor-event-loop screen, console, e
2427   ¦ 3:num/raw <- get *e, cursor-row:offset
2428   ¦ 4:num/raw <- get *e, cursor-column:offset
2429   ]
2430   # screen should scroll, moving cursor to end of text
2431   memory-should-contain [
2432   ¦ 3 <- 1
2433   ¦ 4 <- 2
2434   ]
2435   assume-console [
2436   ¦ type [0]
2437   ]
2438   run [
2439   ¦ editor-event-loop screen, console, e
2440   ]
2441   screen-should-contain [
2442   ¦ .          .
2443   ¦ .de0       .
2444   ¦ .╌╌╌╌╌╌╌╌╌╌.
2445   ¦ .          .
2446   ]
2447   # try to move down again
2448   $clear-trace
2449   assume-console [
2450   ¦ left-click 2, 0
2451   ¦ press down-arrow
2452   ]
2453   run [
2454   ¦ editor-event-loop screen, console, e
2455   ¦ 3:num/raw <- get *e, cursor-row:offset
2456   ¦ 4:num/raw <- get *e, cursor-column:offset
2457   ]
2458   # screen stops scrolling because cursor is already at top
2459   memory-should-contain [
2460   ¦ 3 <- 1
2461   ¦ 4 <- 3
2462   ]
2463   check-trace-count-for-label 0, [print-character]
2464   assume-console [
2465   ¦ type [1]
2466   ]
2467   run [
2468   ¦ editor-event-loop screen, console, e
2469   ]
2470   screen-should-contain [
2471   ¦ .          .
2472   ¦ .de01      .
2473   ¦ .╌╌╌╌╌╌╌╌╌╌.
2474   ¦ .          .
2475   ]
2476 ]
2477 
2478 scenario editor-combines-page-and-line-scroll [
2479   local-scope
2480   # screen has 1 line for menu + 3 lines
2481   assume-screen 10/width, 4/height
2482   # initialize editor with a few pages of lines
2483   s:text <- new [a
2484 b
2485 c
2486 d
2487 e
2488 f
2489 g]
2490   e:&:editor <- new-editor s, 0/left, 5/right
2491   editor-render screen, e
2492   # scroll down one page and one line
2493   assume-console [
2494   ¦ press page-down
2495   ¦ left-click 3, 0
2496   ¦ press down-arrow
2497   ]
2498   run [
2499   ¦ editor-event-loop screen, console, e
2500   ]
2501   # screen scrolls down 3 lines
2502   screen-should-contain [
2503   ¦ .          .
2504   ¦ .d         .
2505   ¦ .e         .
2506   ¦ .f         .
2507   ]
2508 ]
2509 
2510 # cursor-up can scroll if necessary
2511 
2512 scenario editor-can-scroll-up-using-arrow-keys [
2513   local-scope
2514   # screen has 1 line for menu + 3 lines
2515   assume-screen 10/width, 4/height
2516   # initialize editor with >3 lines
2517   s:text <- new [a
2518 b
2519 c
2520 d]
2521   e:&:editor <- new-editor s, 0/left, 10/right
2522   editor-render screen, e
2523   screen-should-contain [
2524   ¦ .          .
2525   ¦ .a         .
2526   ¦ .b         .
2527   ¦ .c         .
2528   ]
2529   # position cursor at top of second page, then try to move up
2530   assume-console [
2531   ¦ press page-down
2532   ¦ press up-arrow
2533   ]
2534   run [
2535   ¦ editor-event-loop screen, console, e
2536   ]
2537   # screen slides by one line
2538   screen-should-contain [
2539   ¦ .          .
2540   ¦ .b         .
2541   ¦ .c         .
2542   ¦ .d         .
2543   ]
2544 ]
2545 
2546 after <scroll-up> [
2547   trace 10, [app], [scroll up]
2548   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2549   old-top:&:duplex-list:char <- copy top-of-screen
2550   top-of-screen <- before-previous-line top-of-screen, editor
2551   *editor <- put *editor, top-of-screen:offset, top-of-screen
2552   no-movement?:bool <- equal old-top, top-of-screen
2553   return-if no-movement?, 0/don't-render
2554 ]
2555 
2556 # takes a pointer into the doubly-linked list, scans back to before start of
2557 # previous *wrapped* line
2558 # beware: never return null pointer
2559 def before-previous-line in:&:duplex-list:char, editor:&:editor -> out:&:duplex-list:char [
2560   local-scope
2561   load-ingredients
2562   curr:&:duplex-list:char <- copy in
2563   c:char <- get *curr, value:offset
2564   # compute max, number of characters to skip
2565   #   1 + len%(width-1)
2566   #   except rotate second term to vary from 1 to width-1 rather than 0 to width-2
2567   left:num <- get *editor, left:offset
2568   right:num <- get *editor, right:offset
2569   max-line-length:num <- subtract right, left, -1/exclusive-right, 1/wrap-icon
2570   sentinel:&:duplex-list:char <- get *editor, data:offset
2571   len:num <- previous-line-length curr, sentinel
2572   {
2573   ¦ break-if len
2574   ¦ # empty line; just skip this newline
2575   ¦ prev:&:duplex-list:char <- prev curr
2576   ¦ return-unless prev, curr
2577   ¦ return prev
2578   }
2579   _, max:num <- divide-with-remainder len, max-line-length
2580   # remainder 0 => scan one width-worth
2581   {
2582   ¦ break-if max
2583   ¦ max <- copy max-line-length
2584   }
2585   max <- add max, 1
2586   count:num <- copy 0
2587   # skip 'max' characters
2588   {
2589   ¦ done?:bool <- greater-or-equal count, max
2590   ¦ break-if done?
2591   ¦ prev:&:duplex-list:char <- prev curr
2592   ¦ break-unless prev
2593   ¦ curr <- copy prev
2594   ¦ count <- add count, 1
2595   ¦ loop
2596   }
2597   return curr
2598 ]
2599 
2600 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys [
2601   local-scope
2602   # screen has 1 line for menu + 3 lines
2603   assume-screen 10/width, 4/height
2604   # initialize editor with a long, wrapped line and more than a screen of
2605   # other lines
2606   s:text <- new [abcdef
2607 g
2608 h
2609 i]
2610   e:&:editor <- new-editor s, 0/left, 5/right
2611   editor-render screen, e
2612   screen-should-contain [
2613   ¦ .          .
2614   ¦ .abcd↩     .
2615   ¦ .ef        .
2616   ¦ .g         .
2617   ]
2618   # position cursor at top of second page, just below wrapped line
2619   assume-console [
2620   ¦ press page-down
2621   ]
2622   run [
2623   ¦ editor-event-loop screen, console, e
2624   ]
2625   screen-should-contain [
2626   ¦ .          .
2627   ¦ .g         .
2628   ¦ .h         .
2629   ¦ .i         .
2630   ]
2631   # now move up one line
2632   assume-console [
2633   ¦ press up-arrow
2634   ]
2635   run [
2636   ¦ editor-event-loop screen, console, e
2637   ]
2638   # screen shows partial wrapped line
2639   screen-should-contain [
2640   ¦ .          .
2641   ¦ .ef        .
2642   ¦ .g         .
2643   ¦ .h         .
2644   ]
2645 ]
2646 
2647 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-2 [
2648   local-scope
2649   # screen has 1 line for menu + 4 lines
2650   assume-screen 10/width, 5/height
2651   # editor starts with a long line wrapping twice, occupying 3 of the 4 lines
2652   s:text <- new [abcdefghij
2653 k
2654 l
2655 m]
2656   e:&:editor <- new-editor s, 0/left, 5/right
2657   editor-render screen, e
2658   # position cursor at top of second page
2659   assume-console [
2660   ¦ press page-down
2661   ]
2662   run [
2663   ¦ editor-event-loop screen, console, e
2664   ]
2665   screen-should-contain [
2666   ¦ .          .
2667   ¦ .k         .
2668   ¦ .l         .
2669   ¦ .m         .
2670   ¦ .╌╌╌╌╌     .
2671   ]
2672   # move up one line
2673   assume-console [
2674   ¦ press up-arrow
2675   ]
2676   run [
2677   ¦ editor-event-loop screen, console, e
2678   ]
2679   # screen shows partial wrapped line
2680   screen-should-contain [
2681   ¦ .          .
2682   ¦ .ij        .
2683   ¦ .k         .
2684   ¦ .l         .
2685   ¦ .m         .
2686   ]
2687   # move up a second line
2688   assume-console [
2689   ¦ press up-arrow
2690   ]
2691   run [
2692   ¦ editor-event-loop screen, console, e
2693   ]
2694   # screen shows partial wrapped line
2695   screen-should-contain [
2696   ¦ .          .
2697   ¦ .efgh↩     .
2698   ¦ .ij        .
2699   ¦ .k         .
2700   ¦ .l         .
2701   ]
2702   # move up a third line
2703   assume-console [
2704   ¦ press up-arrow
2705   ]
2706   run [
2707   ¦ editor-event-loop screen, console, e
2708   ]
2709   # screen shows partial wrapped line
2710   screen-should-contain [
2711   ¦ .          .
2712   ¦ .abcd↩     .
2713   ¦ .efgh↩     .
2714   ¦ .ij        .
2715   ¦ .k         .
2716   ]
2717 ]
2718 
2719 # same as editor-scrolls-up-past-wrapped-line-using-arrow-keys but length
2720 # slightly off, just to prevent over-training
2721 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-3 [
2722   local-scope
2723   # screen has 1 line for menu + 3 lines
2724   assume-screen 10/width, 4/height
2725   # initialize editor with a long, wrapped line and more than a screen of
2726   # other lines
2727   s:text <- new [abcdef
2728 g
2729 h
2730 i]
2731   e:&:editor <- new-editor s, 0/left, 6/right
2732   editor-render screen, e
2733   screen-should-contain [
2734   ¦ .          .
2735   ¦ .abcde↩    .
2736   ¦ .f         .
2737   ¦ .g         .
2738   ]
2739   # position cursor at top of second page, just below wrapped line
2740   assume-console [
2741   ¦ press page-down
2742   ]
2743   run [
2744   ¦ editor-event-loop screen, console, e
2745   ]
2746   screen-should-contain [
2747   ¦ .          .
2748   ¦ .g         .
2749   ¦ .h         .
2750   ¦ .i         .
2751   ]
2752   # now move up one line
2753   assume-console [
2754   ¦ press up-arrow
2755   ]
2756   run [
2757   ¦ editor-event-loop screen, console, e
2758   ]
2759   # screen shows partial wrapped line
2760   screen-should-contain [
2761   ¦ .          .
2762   ¦ .f         .
2763   ¦ .g         .
2764   ¦ .h         .
2765   ]
2766 ]
2767 
2768 # check empty lines
2769 scenario editor-scrolls-up-past-wrapped-line-using-arrow-keys-4 [
2770   local-scope
2771   assume-screen 10/width, 4/height
2772   # initialize editor with some lines around an empty line
2773   s:text <- new [a
2774 b
2775 
2776 c
2777 d
2778 e]
2779   e:&:editor <- new-editor s, 0/left, 6/right
2780   editor-render screen, e
2781   assume-console [
2782   ¦ press page-down
2783   ]
2784   run [
2785   ¦ editor-event-loop screen, console, e
2786   ]
2787   screen-should-contain [
2788   ¦ .          .
2789   ¦ .          .
2790   ¦ .c         .
2791   ¦ .d         .
2792   ]
2793   assume-console [
2794   ¦ press page-down
2795   ]
2796   run [
2797   ¦ editor-event-loop screen, console, e
2798   ]
2799   screen-should-contain [
2800   ¦ .          .
2801   ¦ .d         .
2802   ¦ .e         .
2803   ¦ .╌╌╌╌╌╌    .
2804   ]
2805   assume-console [
2806   ¦ press page-up
2807   ]
2808   run [
2809   ¦ editor-event-loop screen, console, e
2810   ]
2811   screen-should-contain [
2812   ¦ .          .
2813   ¦ .          .
2814   ¦ .c         .
2815   ¦ .d         .
2816   ]
2817 ]
2818 
2819 scenario editor-scrolls-up-on-left-arrow [
2820   local-scope
2821   # screen has 1 line for menu + 3 lines
2822   assume-screen 5/width, 4/height
2823   # editor contains >3 lines
2824   s:text <- new [a
2825 b
2826 c
2827 d
2828 e]
2829   e:&:editor <- new-editor s, 0/left, 5/right
2830   editor-render screen, e
2831   # position cursor at top of second page
2832   assume-console [
2833   ¦ press page-down
2834   ]
2835   run [
2836   ¦ editor-event-loop screen, console, e
2837   ]
2838   screen-should-contain [
2839   ¦ .     .
2840   ¦ .c    .
2841   ¦ .d    .
2842   ¦ .e    .
2843   ]
2844   # now try to move left
2845   assume-console [
2846   ¦ press left-arrow
2847   ]
2848   run [
2849   ¦ editor-event-loop screen, console, e
2850   ¦ 3:num/raw <- get *e, cursor-row:offset
2851   ¦ 4:num/raw <- get *e, cursor-column:offset
2852   ]
2853   # screen scrolls
2854   screen-should-contain [
2855   ¦ .     .
2856   ¦ .b    .
2857   ¦ .c    .
2858   ¦ .d    .
2859   ]
2860   memory-should-contain [
2861   ¦ 3 <- 1
2862   ¦ 4 <- 1
2863   ]
2864 ]
2865 
2866 scenario editor-can-scroll-up-to-start-of-file [
2867   local-scope
2868   # screen has 1 line for menu + 3 lines
2869   assume-screen 10/width, 4/height
2870   # initialize editor with >3 lines
2871   s:text <- new [a
2872 b
2873 c
2874 d]
2875   e:&:editor <- new-editor s, 0/left, 10/right
2876   editor-render screen, e
2877   screen-should-contain [
2878   ¦ .          .
2879   ¦ .a         .
2880   ¦ .b         .
2881   ¦ .c         .
2882   ]
2883   # position cursor at top of second page, then try to move up to start of
2884   # text
2885   assume-console [
2886   ¦ press page-down
2887   ¦ press up-arrow
2888   ¦ press up-arrow
2889   ]
2890   run [
2891   ¦ editor-event-loop screen, console, e
2892   ]
2893   # screen slides by one line
2894   screen-should-contain [
2895   ¦ .          .
2896   ¦ .a         .
2897   ¦ .b         .
2898   ¦ .c         .
2899   ]
2900   # try to move up again
2901   assume-console [
2902   ¦ press up-arrow
2903   ]
2904   run [
2905   ¦ editor-event-loop screen, console, e
2906   ]
2907   # screen remains unchanged
2908   screen-should-contain [
2909   ¦ .          .
2910   ¦ .a         .
2911   ¦ .b         .
2912   ¦ .c         .
2913   ]
2914 ]
2915 
2916 # ctrl-f/page-down - render next page if it exists
2917 
2918 scenario editor-can-scroll [
2919   local-scope
2920   assume-screen 10/width, 4/height
2921   s:text <- new [a
2922 b
2923 c
2924 d]
2925   e:&:editor <- new-editor s, 0/left, 10/right
2926   editor-render screen, e
2927   screen-should-contain [
2928   ¦ .          .
2929   ¦ .a         .
2930   ¦ .b         .
2931   ¦ .c         .
2932   ]
2933   # scroll down
2934   assume-console [
2935   ¦ press page-down
2936   ]
2937   run [
2938   ¦ editor-event-loop screen, console, e
2939   ]
2940   # screen shows next page
2941   screen-should-contain [
2942   ¦ .          .
2943   ¦ .c         .
2944   ¦ .d         .
2945   ¦ .╌╌╌╌╌╌╌╌╌╌.
2946   ]
2947 ]
2948 
2949 after <handle-special-character> [
2950   {
2951   ¦ page-down?:bool <- equal c, 6/ctrl-f
2952   ¦ break-unless page-down?
2953   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
2954   ¦ <move-cursor-begin>
2955   ¦ page-down editor
2956   ¦ undo-coalesce-tag:num <- copy 0/never
2957   ¦ <move-cursor-end>
2958   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2959   ¦ movement?:bool <- not-equal top-of-screen, old-top
2960   ¦ return movement?/go-render
2961   }
2962 ]
2963 
2964 after <handle-special-key> [
2965   {
2966   ¦ page-down?:bool <- equal k, 65518/page-down
2967   ¦ break-unless page-down?
2968   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
2969   ¦ <move-cursor-begin>
2970   ¦ page-down editor
2971   ¦ undo-coalesce-tag:num <- copy 0/never
2972   ¦ <move-cursor-end>
2973   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2974   ¦ movement?:bool <- not-equal top-of-screen, old-top
2975   ¦ return movement?/go-render
2976   }
2977 ]
2978 
2979 # page-down skips entire wrapped lines, so it can't scroll past lines
2980 # taking up the entire screen
2981 def page-down editor:&:editor -> editor:&:editor [
2982   local-scope
2983   load-ingredients
2984   # if editor contents don't overflow screen, do nothing
2985   bottom-of-screen:&:duplex-list:char <- get *editor, bottom-of-screen:offset
2986   return-unless bottom-of-screen
2987   # if not, position cursor at final character
2988   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
2989   before-cursor:&:duplex-list:char <- prev bottom-of-screen
2990   *editor <- put *editor, before-cursor:offset, before-cursor
2991   # keep one line in common with previous page
2992   {
2993   ¦ last:char <- get *before-cursor, value:offset
2994   ¦ newline?:bool <- equal last, 10/newline
2995   ¦ break-unless newline?:bool
2996   ¦ before-cursor <- prev before-cursor
2997   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
2998   }
2999   # move cursor and top-of-screen to start of that line
3000   move-to-start-of-line editor
3001   before-cursor <- get *editor, before-cursor:offset
3002   *editor <- put *editor, top-of-screen:offset, before-cursor
3003 ]
3004 
3005 scenario editor-does-not-scroll-past-end [
3006   local-scope
3007   assume-screen 10/width, 4/height
3008   s:text <- new [a
3009 b]
3010   e:&:editor <- new-editor s, 0/left, 10/right
3011   editor-render screen, e
3012   screen-should-contain [
3013   ¦ .          .
3014   ¦ .a         .
3015   ¦ .b         .
3016   ¦ .╌╌╌╌╌╌╌╌╌╌.
3017   ]
3018   # scroll down
3019   assume-console [
3020   ¦ press page-down
3021   ]
3022   run [
3023   ¦ editor-event-loop screen, console, e
3024   ]
3025   # screen remains unmodified
3026   screen-should-contain [
3027   ¦ .          .
3028   ¦ .a         .
3029   ¦ .b         .
3030   ¦ .╌╌╌╌╌╌╌╌╌╌.
3031   ]
3032 ]
3033 
3034 scenario editor-starts-next-page-at-start-of-wrapped-line [
3035   local-scope
3036   # screen has 1 line for menu + 3 lines for text
3037   assume-screen 10/width, 4/height
3038   # editor contains a long last line
3039   s:text <- new [a
3040 b
3041 cdefgh]
3042   # editor screen triggers wrap of last line
3043   e:&:editor <- new-editor s, 0/left, 4/right
3044   editor-render screen, e
3045   # some part of last line is not displayed
3046   screen-should-contain [
3047   ¦ .          .
3048   ¦ .a         .
3049   ¦ .b         .
3050   ¦ .cde↩      .
3051   ]
3052   # scroll down
3053   assume-console [
3054   ¦ press page-down
3055   ]
3056   run [
3057   ¦ editor-event-loop screen, console, e
3058   ]
3059   # screen shows entire wrapped line
3060   screen-should-contain [
3061   ¦ .          .
3062   ¦ .cde↩      .
3063   ¦ .fgh       .
3064   ¦ .╌╌╌╌      .
3065   ]
3066 ]
3067 
3068 scenario editor-starts-next-page-at-start-of-wrapped-line-2 [
3069   local-scope
3070   # screen has 1 line for menu + 3 lines for text
3071   assume-screen 10/width, 4/height
3072   # editor contains a very long line that occupies last two lines of screen
3073   # and still has something left over
3074   s:text <- new [a
3075 bcdefgh]
3076   e:&:editor <- new-editor s, 0/left, 4/right
3077   editor-render screen, e
3078   # some part of last line is not displayed
3079   screen-should-contain [
3080   ¦ .          .
3081   ¦ .a         .
3082   ¦ .bcd↩      .
3083   ¦ .efg↩      .
3084   ]
3085   # scroll down
3086   assume-console [
3087   ¦ press page-down
3088   ]
3089   run [
3090   ¦ editor-event-loop screen, console, e
3091   ]
3092   # screen shows entire wrapped line
3093   screen-should-contain [
3094   ¦ .          .
3095   ¦ .bcd↩      .
3096   ¦ .efg↩      .
3097   ¦ .h         .
3098   ]
3099 ]
3100 
3101 # ctrl-b/page-up - render previous page if it exists
3102 
3103 scenario editor-can-scroll-up [
3104   local-scope
3105   assume-screen 10/width, 4/height
3106   s:text <- new [a
3107 b
3108 c
3109 d]
3110   e:&:editor <- new-editor s, 0/left, 10/right
3111   editor-render screen, e
3112   screen-should-contain [
3113   ¦ .          .
3114   ¦ .a         .
3115   ¦ .b         .
3116   ¦ .c         .
3117   ]
3118   # scroll down
3119   assume-console [
3120   ¦ press page-down
3121   ]
3122   run [
3123   ¦ editor-event-loop screen, console, e
3124   ]
3125   # screen shows next page
3126   screen-should-contain [
3127   ¦ .          .
3128   ¦ .c         .
3129   ¦ .d         .
3130   ¦ .╌╌╌╌╌╌╌╌╌╌.
3131   ]
3132   # scroll back up
3133   assume-console [
3134   ¦ press page-up
3135   ]
3136   run [
3137   ¦ editor-event-loop screen, console, e
3138   ]
3139   # screen shows original page again
3140   screen-should-contain [
3141   ¦ .          .
3142   ¦ .a         .
3143   ¦ .b         .
3144   ¦ .c         .
3145   ]
3146 ]
3147 
3148 after <handle-special-character> [
3149   {
3150   ¦ page-up?:bool <- equal c, 2/ctrl-b
3151   ¦ break-unless page-up?
3152   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3153   ¦ <move-cursor-begin>
3154   ¦ editor <- page-up editor, screen-height
3155   ¦ undo-coalesce-tag:num <- copy 0/never
3156   ¦ <move-cursor-end>
3157   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3158   ¦ movement?:bool <- not-equal top-of-screen, old-top
3159   ¦ return movement?/go-render
3160   }
3161 ]
3162 
3163 after <handle-special-key> [
3164   {
3165   ¦ page-up?:bool <- equal k, 65519/page-up
3166   ¦ break-unless page-up?
3167   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3168   ¦ <move-cursor-begin>
3169   ¦ editor <- page-up editor, screen-height
3170   ¦ undo-coalesce-tag:num <- copy 0/never
3171   ¦ <move-cursor-end>
3172   ¦ top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3173   ¦ movement?:bool <- not-equal top-of-screen, old-top
3174   ¦ # don't bother re-rendering if nothing changed. todo: test this
3175   ¦ return movement?/go-render
3176   }
3177 ]
3178 
3179 def page-up editor:&:editor, screen-height:num -> editor:&:editor [
3180   local-scope
3181   load-ingredients
3182   max:num <- subtract screen-height, 1/menu-bar, 1/overlapping-line
3183   count:num <- copy 0
3184   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
3185   {
3186   ¦ done?:bool <- greater-or-equal count, max
3187   ¦ break-if done?
3188   ¦ prev:&:duplex-list:char <- before-previous-line top-of-screen, editor
3189   ¦ break-unless prev
3190   ¦ top-of-screen <- copy prev
3191   ¦ *editor <- put *editor, top-of-screen:offset, top-of-screen
3192   ¦ count <- add count, 1
3193   ¦ loop
3194   }
3195 ]
3196 
3197 scenario editor-can-scroll-up-multiple-pages [
3198   local-scope
3199   # screen has 1 line for menu + 3 lines
3200   assume-screen 10/width, 4/height
3201   # initialize editor with 8 lines
3202   s:text <- new [a
3203 b
3204 c
3205 d
3206 e
3207 f
3208 g
3209 h]
3210   e:&:editor <- new-editor s, 0/left, 10/right
3211   editor-render screen, e
3212   screen-should-contain [
3213   ¦ .          .
3214   ¦ .a         .
3215   ¦ .b         .
3216   ¦ .c         .
3217   ]
3218   # scroll down two pages
3219   assume-console [
3220   ¦ press page-down
3221   ¦ press page-down
3222   ]
3223   run [
3224   ¦ editor-event-loop screen, console, e
3225   ]
3226   # screen shows third page
3227   screen-should-contain [
3228   ¦ .          .
3229   ¦ .e         .
3230   ¦ .f         .
3231   ¦ .g         .
3232   ]
3233   # scroll up
3234   assume-console [
3235   ¦ press page-up
3236   ]
3237   run [
3238   ¦ editor-event-loop screen, console, e
3239   ]
3240   # screen shows second page
3241   screen-should-contain [
3242   ¦ .          .
3243   ¦ .c         .
3244   ¦ .d         .
3245   ¦ .e         .
3246   ]
3247   # scroll up again
3248   assume-console [
3249   ¦ press page-up
3250   ]
3251   run [
3252   ¦ editor-event-loop screen, console, e
3253   ]
3254   # screen shows original page again
3255   screen-should-contain [
3256   ¦ .          .
3257   ¦ .a         .
3258   ¦ .b         .
3259   ¦ .c         .
3260   ]
3261 ]
3262 
3263 scenario editor-can-scroll-up-wrapped-lines [
3264   local-scope
3265   # screen has 1 line for menu + 5 lines for text
3266   assume-screen 10/width, 6/height
3267   # editor contains a long line in the first page
3268   s:text <- new [a
3269 b
3270 cdefgh
3271 i
3272 j
3273 k
3274 l
3275 m
3276 n
3277 o]
3278   # editor screen triggers wrap of last line
3279   e:&:editor <- new-editor s, 0/left, 4/right
3280   editor-render screen, e
3281   # some part of last line is not displayed
3282   screen-should-contain [
3283   ¦ .          .
3284   ¦ .a         .
3285   ¦ .b         .
3286   ¦ .cde↩      .
3287   ¦ .fgh       .
3288   ¦ .i         .
3289   ]
3290   # scroll down a page and a line
3291   assume-console [
3292   ¦ press page-down
3293   ¦ left-click 5, 0
3294   ¦ press down-arrow
3295   ]
3296   run [
3297   ¦ editor-event-loop screen, console, e
3298   ]
3299   # screen shows entire wrapped line
3300   screen-should-contain [
3301   ¦ .          .
3302   ¦ .j         .
3303   ¦ .k         .
3304   ¦ .l         .
3305   ¦ .m         .
3306   ¦ .n         .
3307   ]
3308   # now scroll up one page
3309   assume-console [
3310   ¦ press page-up
3311   ]
3312   run [
3313   ¦ editor-event-loop screen, console, e
3314   ]
3315   # screen resets
3316   screen-should-contain [
3317   ¦ .          .
3318   ¦ .b         .
3319   ¦ .cde↩      .
3320   ¦ .fgh       .
3321   ¦ .i         .
3322   ¦ .j         .
3323   ]
3324 ]
3325 
3326 scenario editor-can-scroll-up-wrapped-lines-2 [
3327   local-scope
3328   # screen has 1 line for menu + 3 lines for text
3329   assume-screen 10/width, 4/height
3330   # editor contains a very long line that occupies last two lines of screen
3331   # and still has something left over
3332   s:text <- new [a
3333 bcdefgh]
3334   e:&:editor <- new-editor s, 0/left, 4/right
3335   editor-render screen, e
3336   # some part of last line is not displayed
3337   screen-should-contain [
3338   ¦ .          .
3339   ¦ .a         .
3340   ¦ .bcd↩      .
3341   ¦ .efg↩      .
3342   ]
3343   # scroll down
3344   assume-console [
3345   ¦ press page-down
3346   ]
3347   run [
3348   ¦ editor-event-loop screen, console, e
3349   ]
3350   # screen shows entire wrapped line
3351   screen-should-contain [
3352   ¦ .          .
3353   ¦ .bcd↩      .
3354   ¦ .efg↩      .
3355   ¦ .h         .
3356   ]
3357   # scroll back up
3358   assume-console [
3359   ¦ press page-up
3360   ]
3361   run [
3362   ¦ editor-event-loop screen, console, e
3363   ]
3364   # screen resets
3365   screen-should-contain [
3366   ¦ .          .
3367   ¦ .a         .
3368   ¦ .bcd↩      .
3369   ¦ .efg↩      .
3370   ]
3371 ]
3372 
3373 scenario editor-can-scroll-up-past-nonempty-lines [
3374   local-scope
3375   assume-screen 10/width, 4/height
3376   # text with empty line in second screen
3377   s:text <- new [axx
3378 bxx
3379 cxx
3380 dxx
3381 exx
3382 fxx
3383 gxx
3384 hxx
3385 ]
3386   e:&:editor <- new-editor s, 0/left, 4/right
3387   editor-render screen, e
3388   screen-should-contain [
3389   ¦ .          .
3390   ¦ .axx       .
3391   ¦ .bxx       .
3392   ¦ .cxx       .
3393   ]
3394   assume-console [
3395   ¦ press page-down
3396   ]
3397   run [
3398   ¦ editor-event-loop screen, console, e
3399   ]
3400   screen-should-contain [
3401   ¦ .          .
3402   ¦ .cxx       .
3403   ¦ .dxx       .
3404   ¦ .exx       .
3405   ]
3406   assume-console [
3407   ¦ press page-down
3408   ]
3409   run [
3410   ¦ editor-event-loop screen, console, e
3411   ]
3412   screen-should-contain [
3413   ¦ .          .
3414   ¦ .exx       .
3415   ¦ .fxx       .
3416   ¦ .gxx       .
3417   ]
3418   # scroll back up past empty line
3419   assume-console [
3420   ¦ press page-up
3421   ]
3422   run [
3423   ¦ editor-event-loop screen, console, e
3424   ]
3425   screen-should-contain [
3426   ¦ .          .
3427   ¦ .cxx       .
3428   ¦ .dxx       .
3429   ¦ .exx       .
3430   ]
3431 ]
3432 
3433 scenario editor-can-scroll-up-past-empty-lines [
3434   local-scope
3435   assume-screen 10/width, 4/height
3436   # text with empty line in second screen
3437   s:text <- new [axy
3438 bxy
3439 cxy
3440 
3441 dxy
3442 exy
3443 fxy
3444 gxy
3445 ]
3446   e:&:editor <- new-editor s, 0/left, 4/right
3447   editor-render screen, e
3448   screen-should-contain [
3449   ¦ .          .
3450   ¦ .axy       .
3451   ¦ .bxy       .
3452   ¦ .cxy       .
3453   ]
3454   assume-console [
3455   ¦ press page-down
3456   ]
3457   run [
3458   ¦ editor-event-loop screen, console, e
3459   ]
3460   screen-should-contain [
3461   ¦ .          .
3462   ¦ .cxy       .
3463   ¦ .          .
3464   ¦ .dxy       .
3465   ]
3466   assume-console [
3467   ¦ press page-down
3468   ]
3469   run [
3470   ¦ editor-event-loop screen, console, e
3471   ]
3472   screen-should-contain [
3473   ¦ .          .
3474   ¦ .dxy       .
3475   ¦ .exy       .
3476   ¦ .fxy       .
3477   ]
3478   # scroll back up past empty line
3479   assume-console [
3480   ¦ press page-up
3481   ]
3482   run [
3483   ¦ editor-event-loop screen, console, e
3484   ]
3485   screen-should-contain [
3486   ¦ .          .
3487   ¦ .cxy       .
3488   ¦ .          .
3489   ¦ .dxy       .
3490   ]
3491 ]