1 ## special shortcuts for manipulating the editor
   2 # Some keys on the keyboard generate unicode characters, others generate
   3 # terminfo key codes. We need to modify different places in the two cases.
   4 
   5 # tab - insert two spaces
   6 
   7 scenario editor-inserts-two-spaces-on-tab [
   8   local-scope
   9   assume-screen 10/width, 5/height
  10   s:text <- new [ab
  11 cd]
  12   e:&:editor <- new-editor s, 0/left, 5/right
  13   assume-console [
  14   ¦ press tab
  15   ]
  16   run [
  17   ¦ editor-event-loop screen, console, e
  18   ]
  19   screen-should-contain [
  20   ¦ .          .
  21   ¦ .  ab      .
  22   ¦ .cd        .
  23   ]
  24 ]
  25 
  26 after <handle-special-character> [
  27   {
  28   ¦ tab?:bool <- equal c, 9/tab
  29   ¦ break-unless tab?
  30   ¦ <insert-character-begin>
  31   ¦ insert-at-cursor editor, 32/space, screen
  32   ¦ insert-at-cursor editor, 32/space, screen
  33   ¦ <insert-character-end>
  34   ¦ return 1/go-render
  35   }
  36 ]
  37 
  38 # backspace - delete character before cursor
  39 
  40 scenario editor-handles-backspace-key [
  41   local-scope
  42   assume-screen 10/width, 5/height
  43   e:&:editor <- new-editor [abc], 0/left, 10/right
  44   editor-render screen, e
  45   $clear-trace
  46   assume-console [
  47   ¦ left-click 1, 1
  48   ¦ press backspace
  49   ]
  50   run [
  51   ¦ editor-event-loop screen, console, e
  52   ¦ 4:num/raw <- get *e, cursor-row:offset
  53   ¦ 5:num/raw <- get *e, cursor-column:offset
  54   ]
  55   screen-should-contain [
  56   ¦ .          .
  57   ¦ .bc        .
  58   ¦ .╌╌╌╌╌╌╌╌╌╌.
  59   ¦ .          .
  60   ]
  61   memory-should-contain [
  62   ¦ 4 <- 1
  63   ¦ 5 <- 0
  64   ]
  65   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
  66 ]
  67 
  68 after <handle-special-character> [
  69   {
  70   ¦ delete-previous-character?:bool <- equal c, 8/backspace
  71   ¦ break-unless delete-previous-character?
  72   ¦ <backspace-character-begin>
  73   ¦ go-render?:bool, backspaced-cell:&:duplex-list:char <- delete-before-cursor editor, screen
  74   ¦ <backspace-character-end>
  75   ¦ return
  76   }
  77 ]
  78 
  79 # return values:
  80 #   go-render? - whether caller needs to update the screen
  81 #   backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc.
  82 def delete-before-cursor editor:&:editor, screen:&:screen -> go-render?:bool, backspaced-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
  83   local-scope
  84   load-ingredients
  85   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
  86   data:&:duplex-list:char <- get *editor, data:offset
  87   # if at start of text (before-cursor at § sentinel), return
  88   prev:&:duplex-list:char <- prev before-cursor
  89   return-unless prev, 0/no-more-render, 0/nothing-deleted
  90   trace 10, [app], [delete-before-cursor]
  91   original-row:num <- get *editor, cursor-row:offset
  92   scroll?:bool <- move-cursor-coordinates-left editor
  93   backspaced-cell:&:duplex-list:char <- copy before-cursor
  94   data <- remove before-cursor, data  # will also neatly trim next/prev pointers in backspaced-cell/before-cursor
  95   before-cursor <- copy prev
  96   *editor <- put *editor, before-cursor:offset, before-cursor
  97   return-if scroll?, 1/go-render
  98   screen-width:num <- screen-width screen
  99   cursor-row:num <- get *editor, cursor-row:offset
 100   cursor-column:num <- get *editor, cursor-column:offset
 101   # did we just backspace over a newline?
 102   same-row?:bool <- equal cursor-row, original-row
 103   return-unless same-row?, 1/go-render
 104   left:num <- get *editor, left:offset
 105   right:num <- get *editor, right:offset
 106   curr:&:duplex-list:char <- next before-cursor
 107   screen <- move-cursor screen, cursor-row, cursor-column
 108   curr-column:num <- copy cursor-column
 109   {
 110   ¦ # hit right margin? give up and let caller render
 111   ¦ at-right?:bool <- greater-or-equal curr-column, right
 112   ¦ return-if at-right?, 1/go-render
 113   ¦ break-unless curr
 114   ¦ # newline? done.
 115   ¦ currc:char <- get *curr, value:offset
 116   ¦ at-newline?:bool <- equal currc, 10/newline
 117   ¦ break-if at-newline?
 118   ¦ screen <- print screen, currc
 119   ¦ curr-column <- add curr-column, 1
 120   ¦ curr <- next curr
 121   ¦ loop
 122   }
 123   # we're guaranteed not to be at the right margin
 124   space:char <- copy 32/space
 125   screen <- print screen, space
 126   go-render? <- copy 0/false
 127 ]
 128 
 129 def move-cursor-coordinates-left editor:&:editor -> go-render?:bool, editor:&:editor [
 130   local-scope
 131   load-ingredients
 132   go-render?:bool <- copy 0/false
 133   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 134   cursor-row:num <- get *editor, cursor-row:offset
 135   cursor-column:num <- get *editor, cursor-column:offset
 136   left:num <- get *editor, left:offset
 137   # if not at left margin, move one character left
 138   {
 139   ¦ at-left-margin?:bool <- equal cursor-column, left
 140   ¦ break-if at-left-margin?
 141   ¦ trace 10, [app], [decrementing cursor column]
 142   ¦ cursor-column <- subtract cursor-column, 1
 143   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 144   ¦ return
 145   }
 146   # if at left margin, we must move to previous row:
 147   top-of-screen?:bool <- equal cursor-row, 1  # exclude menu bar
 148   {
 149   ¦ break-if top-of-screen?
 150   ¦ cursor-row <- subtract cursor-row, 1
 151   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 152   }
 153   {
 154   ¦ break-unless top-of-screen?
 155   ¦ <scroll-up>
 156   ¦ go-render? <- copy 1/true
 157   }
 158   {
 159   ¦ # case 1: if previous character was newline, figure out how long the previous line is
 160   ¦ previous-character:char <- get *before-cursor, value:offset
 161   ¦ previous-character-is-newline?:bool <- equal previous-character, 10/newline
 162   ¦ break-unless previous-character-is-newline?
 163   ¦ # compute length of previous line
 164   ¦ trace 10, [app], [switching to previous line]
 165   ¦ d:&:duplex-list:char <- get *editor, data:offset
 166   ¦ end-of-line:num <- previous-line-length before-cursor, d
 167   ¦ right:num <- get *editor, right:offset
 168   ¦ width:num <- subtract right, left
 169   ¦ wrap?:bool <- greater-than end-of-line, width
 170   ¦ {
 171   ¦ ¦ break-unless wrap?
 172   ¦ ¦ _, column-offset:num <- divide-with-remainder end-of-line, width
 173   ¦ ¦ cursor-column <- add left, column-offset
 174   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 175   ¦ }
 176   ¦ {
 177   ¦ ¦ break-if wrap?
 178   ¦ ¦ cursor-column <- add left, end-of-line
 179   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 180   ¦ }
 181   ¦ return
 182   }
 183   # case 2: if previous-character was not newline, we're just at a wrapped line
 184   trace 10, [app], [wrapping to previous line]
 185   right:num <- get *editor, right:offset
 186   cursor-column <- subtract right, 1  # leave room for wrap icon
 187   *editor <- put *editor, cursor-column:offset, cursor-column
 188 ]
 189 
 190 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts
 191 # the length of the previous line before the 'curr' pointer.
 192 def previous-line-length curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [
 193   local-scope
 194   load-ingredients
 195   result:num <- copy 0
 196   return-unless curr
 197   at-start?:bool <- equal curr, start
 198   return-if at-start?
 199   {
 200   ¦ curr <- prev curr
 201   ¦ break-unless curr
 202   ¦ at-start?:bool <- equal curr, start
 203   ¦ break-if at-start?
 204   ¦ c:char <- get *curr, value:offset
 205   ¦ at-newline?:bool <- equal c, 10/newline
 206   ¦ break-if at-newline?
 207   ¦ result <- add result, 1
 208   ¦ loop
 209   }
 210 ]
 211 
 212 scenario editor-clears-last-line-on-backspace [
 213   local-scope
 214   assume-screen 10/width, 5/height
 215   s:text <- new [ab
 216 cd]
 217   e:&:editor <- new-editor s, 0/left, 10/right
 218   assume-console [
 219   ¦ left-click 2, 0
 220   ¦ press backspace
 221   ]
 222   run [
 223   ¦ editor-event-loop screen, console, e
 224   ¦ 4:num/raw <- get *e, cursor-row:offset
 225   ¦ 5:num/raw <- get *e, cursor-column:offset
 226   ]
 227   screen-should-contain [
 228   ¦ .          .
 229   ¦ .abcd      .
 230   ¦ .╌╌╌╌╌╌╌╌╌╌.
 231   ¦ .          .
 232   ]
 233   memory-should-contain [
 234   ¦ 4 <- 1
 235   ¦ 5 <- 2
 236   ]
 237 ]
 238 
 239 scenario editor-joins-and-wraps-lines-on-backspace [
 240   local-scope
 241   assume-screen 10/width, 5/height
 242   # initialize editor with two long-ish but non-wrapping lines
 243   s:text <- new [abc def
 244 ghi jkl]
 245   e:&:editor <- new-editor s, 0/left, 10/right
 246   editor-render screen, e
 247   $clear-trace
 248   # position the cursor at the start of the second and hit backspace
 249   assume-console [
 250   ¦ left-click 2, 0
 251   ¦ press backspace
 252   ]
 253   run [
 254   ¦ editor-event-loop screen, console, e
 255   ]
 256   # resulting single line should wrap correctly
 257   screen-should-contain [
 258   ¦ .          .
 259   ¦ .abc defgh↩.
 260   ¦ .i jkl     .
 261   ¦ .╌╌╌╌╌╌╌╌╌╌.
 262   ¦ .          .
 263   ]
 264 ]
 265 
 266 scenario editor-wraps-long-lines-on-backspace [
 267   local-scope
 268   assume-screen 10/width, 5/height
 269   # initialize editor in part of the screen with a long line
 270   e:&:editor <- new-editor [abc def ghij], 0/left, 8/right
 271   editor-render screen, e
 272   # confirm that it wraps
 273   screen-should-contain [
 274   ¦ .          .
 275   ¦ .abc def↩  .
 276   ¦ . ghij     .
 277   ¦ .╌╌╌╌╌╌╌╌  .
 278   ]
 279   $clear-trace
 280   # position the cursor somewhere in the middle of the top screen line and hit backspace
 281   assume-console [
 282   ¦ left-click 1, 4
 283   ¦ press backspace
 284   ]
 285   run [
 286   ¦ editor-event-loop screen, console, e
 287   ]
 288   # resulting single line should wrap correctly and not overflow its bounds
 289   screen-should-contain [
 290   ¦ .          .
 291   ¦ .abcdef ↩  .
 292   ¦ .ghij      .
 293   ¦ .╌╌╌╌╌╌╌╌  .
 294   ¦ .          .
 295   ]
 296 ]
 297 
 298 # delete - delete character at cursor
 299 
 300 scenario editor-handles-delete-key [
 301   local-scope
 302   assume-screen 10/width, 5/height
 303   e:&:editor <- new-editor [abc], 0/left, 10/right
 304   editor-render screen, e
 305   $clear-trace
 306   assume-console [
 307   ¦ press delete
 308   ]
 309   run [
 310   ¦ editor-event-loop screen, console, e
 311   ]
 312   screen-should-contain [
 313   ¦ .          .
 314   ¦ .bc        .
 315   ¦ .╌╌╌╌╌╌╌╌╌╌.
 316   ¦ .          .
 317   ]
 318   check-trace-count-for-label 3, [print-character]  # length of original line to overwrite
 319   $clear-trace
 320   assume-console [
 321   ¦ press delete
 322   ]
 323   run [
 324   ¦ editor-event-loop screen, console, e
 325   ]
 326   screen-should-contain [
 327   ¦ .          .
 328   ¦ .c         .
 329   ¦ .╌╌╌╌╌╌╌╌╌╌.
 330   ¦ .          .
 331   ]
 332   check-trace-count-for-label 2, [print-character]  # new length to overwrite
 333 ]
 334 
 335 after <handle-special-key> [
 336   {
 337   ¦ delete-next-character?:bool <- equal k, 65522/delete
 338   ¦ break-unless delete-next-character?
 339   ¦ <delete-character-begin>
 340   ¦ go-render?:bool, deleted-cell:&:duplex-list:char <- delete-at-cursor editor, screen
 341   ¦ <delete-character-end>
 342   ¦ return
 343   }
 344 ]
 345 
 346 def delete-at-cursor editor:&:editor, screen:&:screen -> go-render?:bool, deleted-cell:&:duplex-list:char, editor:&:editor, screen:&:screen [
 347   local-scope
 348   load-ingredients
 349   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 350   data:&:duplex-list:char <- get *editor, data:offset
 351   deleted-cell:&:duplex-list:char <- next before-cursor
 352   return-unless deleted-cell, 0/don't-render
 353   currc:char <- get *deleted-cell, value:offset
 354   data <- remove deleted-cell, data
 355   deleted-newline?:bool <- equal currc, 10/newline
 356   return-if deleted-newline?, 1/go-render
 357   # wasn't a newline? render rest of line
 358   curr:&:duplex-list:char <- next before-cursor  # refresh after remove above
 359   cursor-row:num <- get *editor, cursor-row:offset
 360   cursor-column:num <- get *editor, cursor-column:offset
 361   screen <- move-cursor screen, cursor-row, cursor-column
 362   curr-column:num <- copy cursor-column
 363   screen-width:num <- screen-width screen
 364   {
 365   ¦ # hit right margin? give up and let caller render
 366   ¦ at-right?:bool <- greater-or-equal curr-column, screen-width
 367   ¦ return-if at-right?, 1/go-render
 368   ¦ break-unless curr
 369   ¦ # newline? done.
 370   ¦ currc:char <- get *curr, value:offset
 371   ¦ at-newline?:bool <- equal currc, 10/newline
 372   ¦ break-if at-newline?
 373   ¦ screen <- print screen, currc
 374   ¦ curr-column <- add curr-column, 1
 375   ¦ curr <- next curr
 376   ¦ loop
 377   }
 378   # we're guaranteed not to be at the right margin
 379   space:char <- copy 32/space
 380   screen <- print screen, space
 381   go-render? <- copy 0/false
 382 ]
 383 
 384 # right arrow
 385 
 386 scenario editor-moves-cursor-right-with-key [
 387   local-scope
 388   assume-screen 10/width, 5/height
 389   e:&:editor <- new-editor [abc], 0/left, 10/right
 390   editor-render screen, e
 391   $clear-trace
 392   assume-console [
 393   ¦ press right-arrow
 394   ¦ type [0]
 395   ]
 396   run [
 397   ¦ editor-event-loop screen, console, e
 398   ]
 399   screen-should-contain [
 400   ¦ .          .
 401   ¦ .a0bc      .
 402   ¦ .╌╌╌╌╌╌╌╌╌╌.
 403   ¦ .          .
 404   ]
 405   check-trace-count-for-label 3, [print-character]  # 0 and following characters
 406 ]
 407 
 408 after <handle-special-key> [
 409   {
 410   ¦ move-to-next-character?:bool <- equal k, 65514/right-arrow
 411   ¦ break-unless move-to-next-character?
 412   ¦ # if not at end of text
 413   ¦ next-cursor:&:duplex-list:char <- next before-cursor
 414   ¦ break-unless next-cursor
 415   ¦ # scan to next character
 416   ¦ <move-cursor-begin>
 417   ¦ before-cursor <- copy next-cursor
 418   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 419   ¦ go-render?:bool <- move-cursor-coordinates-right editor, screen-height
 420   ¦ screen <- move-cursor screen, cursor-row, cursor-column
 421   ¦ undo-coalesce-tag:num <- copy 2/right-arrow
 422   ¦ <move-cursor-end>
 423   ¦ return
 424   }
 425 ]
 426 
 427 def move-cursor-coordinates-right editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
 428   local-scope
 429   load-ingredients
 430   before-cursor:&:duplex-list:char <- get *editor before-cursor:offset
 431   cursor-row:num <- get *editor, cursor-row:offset
 432   cursor-column:num <- get *editor, cursor-column:offset
 433   left:num <- get *editor, left:offset
 434   right:num <- get *editor, right:offset
 435   # if crossed a newline, move cursor to start of next row
 436   {
 437   ¦ old-cursor-character:char <- get *before-cursor, value:offset
 438   ¦ was-at-newline?:bool <- equal old-cursor-character, 10/newline
 439   ¦ break-unless was-at-newline?
 440   ¦ cursor-row <- add cursor-row, 1
 441   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 442   ¦ cursor-column <- copy left
 443   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 444   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 445   ¦ return-unless below-screen?, 0/don't-render
 446   ¦ <scroll-down>
 447   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 448   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 449   ¦ return 1/go-render
 450   }
 451   # if the line wraps, move cursor to start of next row
 452   {
 453   ¦ # if we're at the column just before the wrap indicator
 454   ¦ wrap-column:num <- subtract right, 1
 455   ¦ at-wrap?:bool <- equal cursor-column, wrap-column
 456   ¦ break-unless at-wrap?
 457   ¦ # and if next character isn't newline
 458   ¦ next:&:duplex-list:char <- next before-cursor
 459   ¦ break-unless next
 460   ¦ next-character:char <- get *next, value:offset
 461   ¦ newline?:bool <- equal next-character, 10/newline
 462   ¦ break-if newline?
 463   ¦ cursor-row <- add cursor-row, 1
 464   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 465   ¦ cursor-column <- copy left
 466   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
 467   ¦ below-screen?:bool <- greater-or-equal cursor-row, screen-height  # must be equal
 468   ¦ return-unless below-screen?, 0/no-more-render
 469   ¦ <scroll-down>
 470   ¦ cursor-row <- subtract cursor-row, 1  # bring back into screen range
 471   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
 472   ¦ return 1/go-render
 473   }
 474   # otherwise move cursor one character right
 475   cursor-column <- add cursor-column, 1
 476   *editor <- put *editor, cursor-column:offset, cursor-column
 477   go-render? <- copy 0/false
 478 ]
 479 
 480 scenario editor-moves-cursor-to-next-line-with-right-arrow [
 481   local-scope
 482   assume-screen 10/width, 5/height
 483   s:text <- new [abc
 484 d]
 485   e:&:editor <- new-editor s, 0/left, 10/right
 486   editor-render screen, e
 487   $clear-trace
 488   # type right-arrow a few times to get to start of second line
 489   assume-console [
 490   ¦ press right-arrow
 491   ¦ press right-arrow
 492   ¦ press right-arrow
 493   ¦ press right-arrow  # next line
 494   ]
 495   run [
 496   ¦ editor-event-loop screen, console, e
 497   ]
 498   check-trace-count-for-label 0, [print-character]
 499   # type something and ensure it goes where it should
 500   assume-console [
 501   ¦ type [0]
 502   ]
 503   run [
 504   ¦ editor-event-loop screen, console, e
 505   ]
 506   screen-should-contain [
 507   ¦ .          .
 508   ¦ .abc       .
 509   ¦ .0d        .
 510   ¦ .╌╌╌╌╌╌╌╌╌╌.
 511   ¦ .          .
 512   ]
 513   check-trace-count-for-label 2, [print-character]  # new length of second line
 514 ]
 515 
 516 scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [
 517   local-scope
 518   assume-screen 10/width, 5/height
 519   s:text <- new [abc
 520 d]
 521   e:&:editor <- new-editor s, 1/left, 10/right
 522   editor-render screen, e
 523   assume-console [
 524   ¦ press right-arrow
 525   ¦ press right-arrow
 526   ¦ press right-arrow
 527   ¦ press right-arrow  # next line
 528   ¦ type [0]
 529   ]
 530   run [
 531   ¦ editor-event-loop screen, console, e
 532   ]
 533   screen-should-contain [
 534   ¦ .          .
 535   ¦ . abc      .
 536   ¦ . 0d       .
 537   ¦ . ╌╌╌╌╌╌╌╌╌.
 538   ¦ .          .
 539   ]
 540 ]
 541 
 542 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
 543   local-scope
 544   assume-screen 10/width, 5/height
 545   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 546   editor-render screen, e
 547   $clear-trace
 548   assume-console [
 549   ¦ left-click 1, 3
 550   ¦ press right-arrow
 551   ]
 552   run [
 553   ¦ editor-event-loop screen, console, e
 554   ¦ 3:num/raw <- get *e, cursor-row:offset
 555   ¦ 4:num/raw <- get *e, cursor-column:offset
 556   ]
 557   screen-should-contain [
 558   ¦ .          .
 559   ¦ .abcd↩     .
 560   ¦ .ef        .
 561   ¦ .╌╌╌╌╌     .
 562   ¦ .          .
 563   ]
 564   memory-should-contain [
 565   ¦ 3 <- 2
 566   ¦ 4 <- 0
 567   ]
 568   check-trace-count-for-label 0, [print-character]
 569 ]
 570 
 571 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
 572   local-scope
 573   assume-screen 10/width, 5/height
 574   # line just barely wrapping
 575   e:&:editor <- new-editor [abcde], 0/left, 5/right
 576   editor-render screen, e
 577   $clear-trace
 578   # position cursor at last character before wrap and hit right-arrow
 579   assume-console [
 580   ¦ left-click 1, 3
 581   ¦ press right-arrow
 582   ]
 583   run [
 584   ¦ editor-event-loop screen, console, e
 585   ¦ 3:num/raw <- get *e, cursor-row:offset
 586   ¦ 4:num/raw <- get *e, cursor-column:offset
 587   ]
 588   memory-should-contain [
 589   ¦ 3 <- 2
 590   ¦ 4 <- 0
 591   ]
 592   # now hit right arrow again
 593   assume-console [
 594   ¦ press right-arrow
 595   ]
 596   run [
 597   ¦ editor-event-loop screen, console, e
 598   ¦ 3:num/raw <- get *e, cursor-row:offset
 599   ¦ 4:num/raw <- get *e, cursor-column:offset
 600   ]
 601   memory-should-contain [
 602   ¦ 3 <- 2
 603   ¦ 4 <- 1
 604   ]
 605   check-trace-count-for-label 0, [print-character]
 606 ]
 607 
 608 scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
 609   local-scope
 610   assume-screen 10/width, 5/height
 611   e:&:editor <- new-editor [abcdef], 1/left, 6/right
 612   editor-render screen, e
 613   $clear-trace
 614   assume-console [
 615   ¦ left-click 1, 4
 616   ¦ press right-arrow
 617   ]
 618   run [
 619   ¦ editor-event-loop screen, console, e
 620   ¦ 3:num/raw <- get *e, cursor-row:offset
 621   ¦ 4:num/raw <- get *e, cursor-column:offset
 622   ]
 623   screen-should-contain [
 624   ¦ .          .
 625   ¦ . abcd↩    .
 626   ¦ . ef       .
 627   ¦ . ╌╌╌╌╌    .
 628   ¦ .          .
 629   ]
 630   memory-should-contain [
 631   ¦ 3 <- 2
 632   ¦ 4 <- 1
 633   ]
 634   check-trace-count-for-label 0, [print-character]
 635 ]
 636 
 637 scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
 638   local-scope
 639   assume-screen 10/width, 5/height
 640   s:text <- new [abc
 641 d]
 642   e:&:editor <- new-editor s, 0/left, 10/right
 643   editor-render screen, e
 644   $clear-trace
 645   # move to end of line, press right-arrow, type a character
 646   assume-console [
 647   ¦ left-click 1, 3
 648   ¦ press right-arrow
 649   ¦ type [0]
 650   ]
 651   run [
 652   ¦ editor-event-loop screen, console, e
 653   ]
 654   # new character should be in next line
 655   screen-should-contain [
 656   ¦ .          .
 657   ¦ .abc       .
 658   ¦ .0d        .
 659   ¦ .╌╌╌╌╌╌╌╌╌╌.
 660   ¦ .          .
 661   ]
 662   check-trace-count-for-label 2, [print-character]
 663 ]
 664 
 665 # todo: ctrl-right: next word-end
 666 
 667 # left arrow
 668 
 669 scenario editor-moves-cursor-left-with-key [
 670   local-scope
 671   assume-screen 10/width, 5/height
 672   e:&:editor <- new-editor [abc], 0/left, 10/right
 673   editor-render screen, e
 674   $clear-trace
 675   assume-console [
 676   ¦ left-click 1, 2
 677   ¦ press left-arrow
 678   ¦ type [0]
 679   ]
 680   run [
 681   ¦ editor-event-loop screen, console, e
 682   ]
 683   screen-should-contain [
 684   ¦ .          .
 685   ¦ .a0bc      .
 686   ¦ .╌╌╌╌╌╌╌╌╌╌.
 687   ¦ .          .
 688   ]
 689   check-trace-count-for-label 3, [print-character]
 690 ]
 691 
 692 after <handle-special-key> [
 693   {
 694   ¦ move-to-previous-character?:bool <- equal k, 65515/left-arrow
 695   ¦ break-unless move-to-previous-character?
 696   ¦ trace 10, [app], [left arrow]
 697   ¦ # if not at start of text (before-cursor at § sentinel)
 698   ¦ prev:&:duplex-list:char <- prev before-cursor
 699   ¦ return-unless prev, 0/don't-render
 700   ¦ <move-cursor-begin>
 701   ¦ go-render? <- move-cursor-coordinates-left editor
 702   ¦ before-cursor <- copy prev
 703   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
 704   ¦ undo-coalesce-tag:num <- copy 1/left-arrow
 705   ¦ <move-cursor-end>
 706   ¦ return
 707   }
 708 ]
 709 
 710 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
 711   local-scope
 712   assume-screen 10/width, 5/height
 713   # initialize editor with two lines
 714   s:text <- new [abc
 715 d]
 716   e:&:editor <- new-editor s, 0/left, 10/right
 717   editor-render screen, e
 718   $clear-trace
 719   # position cursor at start of second line (so there's no previous newline)
 720   assume-console [
 721   ¦ left-click 2, 0
 722   ¦ press left-arrow
 723   ]
 724   run [
 725   ¦ editor-event-loop screen, console, e
 726   ¦ 3:num/raw <- get *e, cursor-row:offset
 727   ¦ 4:num/raw <- get *e, cursor-column:offset
 728   ]
 729   memory-should-contain [
 730   ¦ 3 <- 1
 731   ¦ 4 <- 3
 732   ]
 733   check-trace-count-for-label 0, [print-character]
 734 ]
 735 
 736 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
 737   local-scope
 738   assume-screen 10/width, 5/height
 739   # initialize editor with three lines
 740   s:text <- new [abc
 741 def
 742 g]
 743   e:&:editor <- new-editor s:text, 0/left, 10/right
 744   editor-render screen, e
 745   $clear-trace
 746   # position cursor further down (so there's a newline before the character at
 747   # the cursor)
 748   assume-console [
 749   ¦ left-click 3, 0
 750   ¦ press left-arrow
 751   ¦ type [0]
 752   ]
 753   run [
 754   ¦ editor-event-loop screen, console, e
 755   ]
 756   screen-should-contain [
 757   ¦ .          .
 758   ¦ .abc       .
 759   ¦ .def0      .
 760   ¦ .g         .
 761   ¦ .╌╌╌╌╌╌╌╌╌╌.
 762   ]
 763   check-trace-count-for-label 1, [print-character]  # just the '0'
 764 ]
 765 
 766 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
 767   local-scope
 768   assume-screen 10/width, 5/height
 769   s:text <- new [abc
 770 def
 771 g]
 772   e:&:editor <- new-editor s, 0/left, 10/right
 773   editor-render screen, e
 774   $clear-trace
 775   # position cursor at start of text, press left-arrow, then type a character
 776   assume-console [
 777   ¦ left-click 1, 0
 778   ¦ press left-arrow
 779   ¦ type [0]
 780   ]
 781   run [
 782   ¦ editor-event-loop screen, console, e
 783   ]
 784   # left-arrow should have had no effect
 785   screen-should-contain [
 786   ¦ .          .
 787   ¦ .0abc      .
 788   ¦ .def       .
 789   ¦ .g         .
 790   ¦ .╌╌╌╌╌╌╌╌╌╌.
 791   ]
 792   check-trace-count-for-label 4, [print-character]  # length of first line
 793 ]
 794 
 795 scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
 796   local-scope
 797   assume-screen 10/width, 5/height
 798   # initialize editor with text containing an empty line
 799   s:text <- new [abc
 800 
 801 d]
 802   e:&:editor <- new-editor s, 0/left, 10/right
 803   editor-render screen, e:&:editor
 804   $clear-trace
 805   # position cursor right after empty line
 806   assume-console [
 807   ¦ left-click 3, 0
 808   ¦ press left-arrow
 809   ¦ type [0]
 810   ]
 811   run [
 812   ¦ editor-event-loop screen, console, e
 813   ]
 814   screen-should-contain [
 815   ¦ .          .
 816   ¦ .abc       .
 817   ¦ .0         .
 818   ¦ .d         .
 819   ¦ .╌╌╌╌╌╌╌╌╌╌.
 820   ]
 821   check-trace-count-for-label 1, [print-character]  # just the '0'
 822 ]
 823 
 824 scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
 825   local-scope
 826   assume-screen 10/width, 5/height
 827   # initialize editor with a wrapping line
 828   e:&:editor <- new-editor [abcdef], 0/left, 5/right
 829   editor-render screen, e
 830   $clear-trace
 831   screen-should-contain [
 832   ¦ .          .
 833   ¦ .abcd↩     .
 834   ¦ .ef        .
 835   ¦ .╌╌╌╌╌     .
 836   ¦ .          .
 837   ]
 838   # position cursor right after empty line
 839   assume-console [
 840   ¦ left-click 2, 0
 841   ¦ press left-arrow
 842   ]
 843   run [
 844   ¦ editor-event-loop screen, console, e
 845   ¦ 3:num/raw <- get *e, cursor-row:offset
 846   ¦ 4:num/raw <- get *e, cursor-column:offset
 847   ]
 848   memory-should-contain [
 849   ¦ 3 <- 1  # previous row
 850   ¦ 4 <- 3  # right margin except wrap icon
 851   ]
 852   check-trace-count-for-label 0, [print-character]
 853 ]
 854 
 855 scenario editor-moves-across-screen-lines-to-wrapping-line-with-left-arrow [
 856   local-scope
 857   assume-screen 10/width, 5/height
 858   # initialize editor with a wrapping line followed by a second line
 859   s:text <- new [abcdef
 860 g]
 861   e:&:editor <- new-editor s, 0/left, 5/right
 862   editor-render screen, e
 863   $clear-trace
 864   screen-should-contain [
 865   ¦ .          .
 866   ¦ .abcd↩     .
 867   ¦ .ef        .
 868   ¦ .g         .
 869   ¦ .╌╌╌╌╌     .
 870   ]
 871   # position cursor right after empty line
 872   assume-console [
 873   ¦ left-click 3, 0
 874   ¦ press left-arrow
 875   ]
 876   run [
 877   ¦ editor-event-loop screen, console, e
 878   ¦ 3:num/raw <- get *e, cursor-row:offset
 879   ¦ 4:num/raw <- get *e, cursor-column:offset
 880   ]
 881   memory-should-contain [
 882   ¦ 3 <- 2  # previous row
 883   ¦ 4 <- 2  # end of wrapped line
 884   ]
 885   check-trace-count-for-label 0, [print-character]
 886 ]
 887 
 888 scenario editor-moves-across-screen-lines-to-non-wrapping-line-with-left-arrow [
 889   local-scope
 890   assume-screen 10/width, 5/height
 891   # initialize editor with a line on the verge of wrapping, followed by a second line
 892   s:text <- new [abcd
 893 e]
 894   e:&:editor <- new-editor s, 0/left, 5/right
 895   editor-render screen, e
 896   $clear-trace
 897   screen-should-contain [
 898   ¦ .          .
 899   ¦ .abcd      .
 900   ¦ .e         .
 901   ¦ .╌╌╌╌╌     .
 902   ¦ .          .
 903   ]
 904   # position cursor right after empty line
 905   assume-console [
 906   ¦ left-click 2, 0
 907   ¦ press left-arrow
 908   ]
 909   run [
 910   ¦ editor-event-loop screen, console, e
 911   ¦ 3:num/raw <- get *e, cursor-row:offset
 912   ¦ 4:num/raw <- get *e, cursor-column:offset
 913   ]
 914   memory-should-contain [
 915   ¦ 3 <- 1  # previous row
 916   ¦ 4 <- 4  # end of wrapped line
 917   ]
 918   check-trace-count-for-label 0, [print-character]
 919 ]
 920 
 921 # todo: ctrl-left: previous word-start
 922 
 923 # up arrow
 924 
 925 scenario editor-moves-to-previous-line-with-up-arrow [
 926   local-scope
 927   assume-screen 10/width, 5/height
 928   s:text <- new [abc
 929 def]
 930   e:&:editor <- new-editor s, 0/left, 10/right
 931   editor-render screen, e
 932   $clear-trace
 933   assume-console [
 934   ¦ left-click 2, 1
 935   ¦ press up-arrow
 936   ]
 937   run [
 938   ¦ editor-event-loop screen, console, e
 939   ¦ 3:num/raw <- get *e, cursor-row:offset
 940   ¦ 4:num/raw <- get *e, cursor-column:offset
 941   ]
 942   memory-should-contain [
 943   ¦ 3 <- 1
 944   ¦ 4 <- 1
 945   ]
 946   check-trace-count-for-label 0, [print-character]
 947   assume-console [
 948   ¦ type [0]
 949   ]
 950   run [
 951   ¦ editor-event-loop screen, console, e
 952   ]
 953   screen-should-contain [
 954   ¦ .          .
 955   ¦ .a0bc      .
 956   ¦ .def       .
 957   ¦ .╌╌╌╌╌╌╌╌╌╌.
 958   ¦ .          .
 959   ]
 960 ]
 961 
 962 after <handle-special-key> [
 963   {
 964   ¦ move-to-previous-line?:bool <- equal k, 65517/up-arrow
 965   ¦ break-unless move-to-previous-line?
 966   ¦ <move-cursor-begin>
 967   ¦ go-render? <- move-to-previous-line editor
 968   ¦ undo-coalesce-tag:num <- copy 3/up-arrow
 969   ¦ <move-cursor-end>
 970   ¦ return
 971   }
 972 ]
 973 
 974 def move-to-previous-line editor:&:editor -> go-render?:bool, editor:&:editor [
 975   local-scope
 976   load-ingredients
 977   go-render?:bool <- copy 0/false
 978   cursor-row:num <- get *editor, cursor-row:offset
 979   cursor-column:num <- get *editor, cursor-column:offset
 980   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
 981   left:num <- get *editor, left:offset
 982   right:num <- get *editor, right:offset
 983   already-at-top?:bool <- lesser-or-equal cursor-row, 1/top
 984   {
 985   ¦ # if cursor not at top, move it
 986   ¦ break-if already-at-top?
 987   ¦ # if not at newline, move to start of line (previous newline)
 988   ¦ # then scan back another line
 989   ¦ # if either step fails, give up without modifying cursor or coordinates
 990   ¦ curr:&:duplex-list:char <- copy before-cursor
 991   ¦ {
 992   ¦ ¦ old:&:duplex-list:char <- copy curr
 993   ¦ ¦ c2:char <- get *curr, value:offset
 994   ¦ ¦ at-newline?:bool <- equal c2, 10/newline
 995   ¦ ¦ break-if at-newline?
 996   ¦ ¦ curr:&:duplex-list:char <- before-previous-line curr, editor
 997   ¦ ¦ no-motion?:bool <- equal curr, old
 998   ¦ ¦ return-if no-motion?
 999   ¦ }
1000   ¦ {
1001   ¦ ¦ old <- copy curr
1002   ¦ ¦ curr <- before-previous-line curr, editor
1003   ¦ ¦ no-motion?:bool <- equal curr, old
1004   ¦ ¦ return-if no-motion?
1005   ¦ }
1006   ¦ before-cursor <- copy curr
1007   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1008   ¦ cursor-row <- subtract cursor-row, 1
1009   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
1010   ¦ # scan ahead to right column or until end of line
1011   ¦ target-column:num <- copy cursor-column
1012   ¦ cursor-column <- copy left
1013   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1014   ¦ {
1015   ¦ ¦ done?:bool <- greater-or-equal cursor-column, target-column
1016   ¦ ¦ break-if done?
1017   ¦ ¦ curr:&:duplex-list:char <- next before-cursor
1018   ¦ ¦ break-unless curr
1019   ¦ ¦ currc:char <- get *curr, value:offset
1020   ¦ ¦ at-newline?:bool <- equal currc, 10/newline
1021   ¦ ¦ break-if at-newline?
1022   ¦ ¦ #
1023   ¦ ¦ before-cursor <- copy curr
1024   ¦ ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1025   ¦ ¦ cursor-column <- add cursor-column, 1
1026   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1027   ¦ ¦ loop
1028   ¦ }
1029   ¦ return
1030   }
1031   {
1032   ¦ # if cursor already at top, scroll up
1033   ¦ break-unless already-at-top?
1034   ¦ <scroll-up>
1035   ¦ return 1/go-render
1036   }
1037 ]
1038 
1039 scenario editor-adjusts-column-at-previous-line [
1040   local-scope
1041   assume-screen 10/width, 5/height
1042   s:text <- new [ab
1043 def]
1044   e:&:editor <- new-editor s, 0/left, 10/right
1045   editor-render screen, e
1046   $clear-trace
1047   assume-console [
1048   ¦ left-click 2, 3
1049   ¦ press up-arrow
1050   ]
1051   run [
1052   ¦ editor-event-loop screen, console, e
1053   ¦ 3:num/raw <- get *e, cursor-row:offset
1054   ¦ 4:num/raw <- get *e, cursor-column:offset
1055   ]
1056   memory-should-contain [
1057   ¦ 3 <- 1
1058   ¦ 4 <- 2
1059   ]
1060   check-trace-count-for-label 0, [print-character]
1061   assume-console [
1062   ¦ type [0]
1063   ]
1064   run [
1065   ¦ editor-event-loop screen, console, e
1066   ]
1067   screen-should-contain [
1068   ¦ .          .
1069   ¦ .ab0       .
1070   ¦ .def       .
1071   ¦ .╌╌╌╌╌╌╌╌╌╌.
1072   ¦ .          .
1073   ]
1074 ]
1075 
1076 scenario editor-adjusts-column-at-empty-line [
1077   local-scope
1078   assume-screen 10/width, 5/height
1079   s:text <- new [
1080 def]
1081   e:&:editor <- new-editor s, 0/left, 10/right
1082   editor-render screen, e
1083   $clear-trace
1084   assume-console [
1085   ¦ left-click 2, 3
1086   ¦ press up-arrow
1087   ]
1088   run [
1089   ¦ editor-event-loop screen, console, e
1090   ¦ 3:num/raw <- get *e, cursor-row:offset
1091   ¦ 4:num/raw <- get *e, cursor-column:offset
1092   ]
1093   memory-should-contain [
1094   ¦ 3 <- 1
1095   ¦ 4 <- 0
1096   ]
1097   check-trace-count-for-label 0, [print-character]
1098   assume-console [
1099   ¦ type [0]
1100   ]
1101   run [
1102   ¦ editor-event-loop screen, console, e
1103   ]
1104   screen-should-contain [
1105   ¦ .          .
1106   ¦ .0         .
1107   ¦ .def       .
1108   ¦ .╌╌╌╌╌╌╌╌╌╌.
1109   ¦ .          .
1110   ]
1111 ]
1112 
1113 scenario editor-moves-to-previous-line-from-left-margin [
1114   local-scope
1115   assume-screen 10/width, 5/height
1116   # start out with three lines
1117   s:text <- new [abc
1118 def
1119 ghi]
1120   e:&:editor <- new-editor s, 0/left, 10/right
1121   editor-render screen, e
1122   $clear-trace
1123   # click on the third line and hit up-arrow, so you end up just after a newline
1124   assume-console [
1125   ¦ left-click 3, 0
1126   ¦ press up-arrow
1127   ]
1128   run [
1129   ¦ editor-event-loop screen, console, e
1130   ¦ 3:num/raw <- get *e, cursor-row:offset
1131   ¦ 4:num/raw <- get *e, cursor-column:offset
1132   ]
1133   memory-should-contain [
1134   ¦ 3 <- 2
1135   ¦ 4 <- 0
1136   ]
1137   check-trace-count-for-label 0, [print-character]
1138   assume-console [
1139   ¦ type [0]
1140   ]
1141   run [
1142   ¦ editor-event-loop screen, console, e
1143   ]
1144   screen-should-contain [
1145   ¦ .          .
1146   ¦ .abc       .
1147   ¦ .0def      .
1148   ¦ .ghi       .
1149   ¦ .╌╌╌╌╌╌╌╌╌╌.
1150   ]
1151 ]
1152 
1153 # down arrow
1154 
1155 scenario editor-moves-to-next-line-with-down-arrow [
1156   local-scope
1157   assume-screen 10/width, 5/height
1158   s:text <- new [abc
1159 def]
1160   e:&:editor <- new-editor s, 0/left, 10/right
1161   editor-render screen, e
1162   $clear-trace
1163   # cursor starts out at (1, 0)
1164   assume-console [
1165   ¦ press down-arrow
1166   ]
1167   run [
1168   ¦ editor-event-loop screen, console, e
1169   ¦ 3:num/raw <- get *e, cursor-row:offset
1170   ¦ 4:num/raw <- get *e, cursor-column:offset
1171   ]
1172   # ..and ends at (2, 0)
1173   memory-should-contain [
1174   ¦ 3 <- 2
1175   ¦ 4 <- 0
1176   ]
1177   check-trace-count-for-label 0, [print-character]
1178   assume-console [
1179   ¦ type [0]
1180   ]
1181   run [
1182   ¦ editor-event-loop screen, console, e
1183   ]
1184   screen-should-contain [
1185   ¦ .          .
1186   ¦ .abc       .
1187   ¦ .0def      .
1188   ¦ .╌╌╌╌╌╌╌╌╌╌.
1189   ¦ .          .
1190   ]
1191 ]
1192 
1193 after <handle-special-key> [
1194   {
1195   ¦ move-to-next-line?:bool <- equal k, 65516/down-arrow
1196   ¦ break-unless move-to-next-line?
1197   ¦ <move-cursor-begin>
1198   ¦ go-render? <- move-to-next-line editor, screen-height
1199   ¦ undo-coalesce-tag:num <- copy 4/down-arrow
1200   ¦ <move-cursor-end>
1201   ¦ return
1202   }
1203 ]
1204 
1205 def move-to-next-line editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
1206   local-scope
1207   load-ingredients
1208   cursor-row:num <- get *editor, cursor-row:offset
1209   cursor-column:num <- get *editor, cursor-column:offset
1210   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1211   left:num <- get *editor, left:offset
1212   right:num <- get *editor, right:offset
1213   last-line:num <- subtract screen-height, 1
1214   already-at-bottom?:bool <- greater-or-equal cursor-row, last-line
1215   {
1216   ¦ # if cursor not at bottom, move it
1217   ¦ break-if already-at-bottom?
1218   ¦ # scan to start of next line, then to right column or until end of line
1219   ¦ max:num <- subtract right, left
1220   ¦ next-line:&:duplex-list:char <- before-start-of-next-line before-cursor, max
1221   ¦ {
1222   ¦ ¦ # already at end of buffer? try to scroll up (so we can see more
1223   ¦ ¦ # warnings or sandboxes below)
1224   ¦ ¦ no-motion?:bool <- equal next-line, before-cursor
1225   ¦ ¦ break-unless no-motion?
1226   ¦ ¦ scroll?:bool <- greater-than cursor-row, 1
1227   ¦ ¦ break-if scroll?, +try-to-scroll
1228   ¦ ¦ return 0/don't-render
1229   ¦ }
1230   ¦ cursor-row <- add cursor-row, 1
1231   ¦ *editor <- put *editor, cursor-row:offset, cursor-row
1232   ¦ before-cursor <- copy next-line
1233   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1234   ¦ target-column:num <- copy cursor-column
1235   ¦ cursor-column <- copy left
1236   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1237   ¦ {
1238   ¦ ¦ done?:bool <- greater-or-equal cursor-column, target-column
1239   ¦ ¦ break-if done?
1240   ¦ ¦ curr:&:duplex-list:char <- next before-cursor
1241   ¦ ¦ break-unless curr
1242   ¦ ¦ currc:char <- get *curr, value:offset
1243   ¦ ¦ at-newline?:bool <- equal currc, 10/newline
1244   ¦ ¦ break-if at-newline?
1245   ¦ ¦ #
1246   ¦ ¦ before-cursor <- copy curr
1247   ¦ ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1248   ¦ ¦ cursor-column <- add cursor-column, 1
1249   ¦ ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1250   ¦ ¦ loop
1251   ¦ }
1252   ¦ return 0/don't-render
1253   }
1254   +try-to-scroll
1255   <scroll-down>
1256   go-render? <- copy 1/true
1257 ]
1258 
1259 scenario editor-adjusts-column-at-next-line [
1260   local-scope
1261   assume-screen 10/width, 5/height
1262   s:text <- new [abc
1263 de]
1264   e:&:editor <- new-editor s, 0/left, 10/right
1265   editor-render screen, e
1266   $clear-trace
1267   assume-console [
1268   ¦ left-click 1, 3
1269   ¦ press down-arrow
1270   ]
1271   run [
1272   ¦ editor-event-loop screen, console, e
1273   ¦ 3:num/raw <- get *e, cursor-row:offset
1274   ¦ 4:num/raw <- get *e, cursor-column:offset
1275   ]
1276   memory-should-contain [
1277   ¦ 3 <- 2
1278   ¦ 4 <- 2
1279   ]
1280   check-trace-count-for-label 0, [print-character]
1281   assume-console [
1282   ¦ type [0]
1283   ]
1284   run [
1285   ¦ editor-event-loop screen, console, e
1286   ]
1287   screen-should-contain [
1288   ¦ .          .
1289   ¦ .abc       .
1290   ¦ .de0       .
1291   ¦ .╌╌╌╌╌╌╌╌╌╌.
1292   ¦ .          .
1293   ]
1294 ]
1295 
1296 # ctrl-a/home - move cursor to start of line
1297 
1298 scenario editor-moves-to-start-of-line-with-ctrl-a [
1299   local-scope
1300   assume-screen 10/width, 5/height
1301   s:text <- new [123
1302 456]
1303   e:&:editor <- new-editor s, 0/left, 10/right
1304   editor-render screen, e
1305   $clear-trace
1306   # start on second line, press ctrl-a
1307   assume-console [
1308   ¦ left-click 2, 3
1309   ¦ press ctrl-a
1310   ]
1311   run [
1312   ¦ editor-event-loop screen, console, e
1313   ¦ 4:num/raw <- get *e, cursor-row:offset
1314   ¦ 5:num/raw <- get *e, cursor-column:offset
1315   ]
1316   # cursor moves to start of line
1317   memory-should-contain [
1318   ¦ 4 <- 2
1319   ¦ 5 <- 0
1320   ]
1321   check-trace-count-for-label 0, [print-character]
1322 ]
1323 
1324 after <handle-special-character> [
1325   {
1326   ¦ move-to-start-of-line?:bool <- equal c, 1/ctrl-a
1327   ¦ break-unless move-to-start-of-line?
1328   ¦ <move-cursor-begin>
1329   ¦ move-to-start-of-line editor
1330   ¦ undo-coalesce-tag:num <- copy 0/never
1331   ¦ <move-cursor-end>
1332   ¦ return 0/don't-render
1333   }
1334 ]
1335 
1336 after <handle-special-key> [
1337   {
1338   ¦ move-to-start-of-line?:bool <- equal k, 65521/home
1339   ¦ break-unless move-to-start-of-line?
1340   ¦ <move-cursor-begin>
1341   ¦ move-to-start-of-line editor
1342   ¦ undo-coalesce-tag:num <- copy 0/never
1343   ¦ <move-cursor-end>
1344   ¦ return 0/don't-render
1345   }
1346 ]
1347 
1348 def move-to-start-of-line editor:&:editor -> editor:&:editor [
1349   local-scope
1350   load-ingredients
1351   # update cursor column
1352   left:num <- get *editor, left:offset
1353   cursor-column:num <- copy left
1354   *editor <- put *editor, cursor-column:offset, cursor-column
1355   # update before-cursor
1356   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1357   init:&:duplex-list:char <- get *editor, data:offset
1358   # while not at start of line, move
1359   {
1360   ¦ at-start-of-text?:bool <- equal before-cursor, init
1361   ¦ break-if at-start-of-text?
1362   ¦ prev:char <- get *before-cursor, value:offset
1363   ¦ at-start-of-line?:bool <- equal prev, 10/newline
1364   ¦ break-if at-start-of-line?
1365   ¦ before-cursor <- prev before-cursor
1366   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1367   ¦ assert before-cursor, [move-to-start-of-line tried to move before start of text]
1368   ¦ loop
1369   }
1370 ]
1371 
1372 scenario editor-moves-to-start-of-line-with-ctrl-a-2 [
1373   local-scope
1374   assume-screen 10/width, 5/height
1375   s:text <- new [123
1376 456]
1377   e:&:editor <- new-editor s, 0/left, 10/right
1378   editor-render screen, e
1379   $clear-trace
1380   # start on first line (no newline before), press ctrl-a
1381   assume-console [
1382   ¦ left-click 1, 3
1383   ¦ press ctrl-a
1384   ]
1385   run [
1386   ¦ editor-event-loop screen, console, e
1387   ¦ 4:num/raw <- get *e, cursor-row:offset
1388   ¦ 5:num/raw <- get *e, cursor-column:offset
1389   ]
1390   # cursor moves to start of line
1391   memory-should-contain [
1392   ¦ 4 <- 1
1393   ¦ 5 <- 0
1394   ]
1395   check-trace-count-for-label 0, [print-character]
1396 ]
1397 
1398 scenario editor-moves-to-start-of-line-with-home [
1399   local-scope
1400   assume-screen 10/width, 5/height
1401   s:text <- new [123
1402 456]
1403   e:&:editor <- new-editor s, 0/left, 10/right
1404   $clear-trace
1405   # start on second line, press 'home'
1406   assume-console [
1407   ¦ left-click 2, 3
1408   ¦ press home
1409   ]
1410   run [
1411   ¦ editor-event-loop screen, console, e
1412   ¦ 3:num/raw <- get *e, cursor-row:offset
1413   ¦ 4:num/raw <- get *e, cursor-column:offset
1414   ]
1415   # cursor moves to start of line
1416   memory-should-contain [
1417   ¦ 3 <- 2
1418   ¦ 4 <- 0
1419   ]
1420   check-trace-count-for-label 0, [print-character]
1421 ]
1422 
1423 scenario editor-moves-to-start-of-line-with-home-2 [
1424   local-scope
1425   assume-screen 10/width, 5/height
1426   s:text <- new [123
1427 456]
1428   e:&:editor <- new-editor s, 0/left, 10/right
1429   editor-render screen, e
1430   $clear-trace
1431   # start on first line (no newline before), press 'home'
1432   assume-console [
1433   ¦ left-click 1, 3
1434   ¦ press home
1435   ]
1436   run [
1437   ¦ editor-event-loop screen, console, e
1438   ¦ 3:num/raw <- get *e, cursor-row:offset
1439   ¦ 4:num/raw <- get *e, cursor-column:offset
1440   ]
1441   # cursor moves to start of line
1442   memory-should-contain [
1443   ¦ 3 <- 1
1444   ¦ 4 <- 0
1445   ]
1446   check-trace-count-for-label 0, [print-character]
1447 ]
1448 
1449 # ctrl-e/end - move cursor to end of line
1450 
1451 scenario editor-moves-to-end-of-line-with-ctrl-e [
1452   local-scope
1453   assume-screen 10/width, 5/height
1454   s:text <- new [123
1455 456]
1456   e:&:editor <- new-editor s, 0/left, 10/right
1457   editor-render screen, e
1458   $clear-trace
1459   # start on first line, press ctrl-e
1460   assume-console [
1461   ¦ left-click 1, 1
1462   ¦ press ctrl-e
1463   ]
1464   run [
1465   ¦ editor-event-loop screen, console, e
1466   ¦ 4:num/raw <- get *e, cursor-row:offset
1467   ¦ 5:num/raw <- get *e, cursor-column:offset
1468   ]
1469   # cursor moves to end of line
1470   memory-should-contain [
1471   ¦ 4 <- 1
1472   ¦ 5 <- 3
1473   ]
1474   check-trace-count-for-label 0, [print-character]
1475   # editor inserts future characters at cursor
1476   assume-console [
1477   ¦ type [z]
1478   ]
1479   run [
1480   ¦ editor-event-loop screen, console, e
1481   ¦ 4:num/raw <- get *e, cursor-row:offset
1482   ¦ 5:num/raw <- get *e, cursor-column:offset
1483   ]
1484   memory-should-contain [
1485   ¦ 4 <- 1
1486   ¦ 5 <- 4
1487   ]
1488   screen-should-contain [
1489   ¦ .          .
1490   ¦ .123z      .
1491   ¦ .456       .
1492   ¦ .╌╌╌╌╌╌╌╌╌╌.
1493   ¦ .          .
1494   ]
1495   check-trace-count-for-label 1, [print-character]
1496 ]
1497 
1498 after <handle-special-character> [
1499   {
1500   ¦ move-to-end-of-line?:bool <- equal c, 5/ctrl-e
1501   ¦ break-unless move-to-end-of-line?
1502   ¦ <move-cursor-begin>
1503   ¦ move-to-end-of-line editor
1504   ¦ undo-coalesce-tag:num <- copy 0/never
1505   ¦ <move-cursor-end>
1506   ¦ return 0/don't-render
1507   }
1508 ]
1509 
1510 after <handle-special-key> [
1511   {
1512   ¦ move-to-end-of-line?:bool <- equal k, 65520/end
1513   ¦ break-unless move-to-end-of-line?
1514   ¦ <move-cursor-begin>
1515   ¦ move-to-end-of-line editor
1516   ¦ undo-coalesce-tag:num <- copy 0/never
1517   ¦ <move-cursor-end>
1518   ¦ return 0/don't-render
1519   }
1520 ]
1521 
1522 def move-to-end-of-line editor:&:editor -> editor:&:editor [
1523   local-scope
1524   load-ingredients
1525   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1526   cursor-column:num <- get *editor, cursor-column:offset
1527   # while not at start of line, move
1528   {
1529   ¦ next:&:duplex-list:char <- next before-cursor
1530   ¦ break-unless next  # end of text
1531   ¦ nextc:char <- get *next, value:offset
1532   ¦ at-end-of-line?:bool <- equal nextc, 10/newline
1533   ¦ break-if at-end-of-line?
1534   ¦ before-cursor <- copy next
1535   ¦ *editor <- put *editor, before-cursor:offset, before-cursor
1536   ¦ cursor-column <- add cursor-column, 1
1537   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
1538   ¦ loop
1539   }
1540 ]
1541 
1542 scenario editor-moves-to-end-of-line-with-ctrl-e-2 [
1543   local-scope
1544   assume-screen 10/width, 5/height
1545   s:text <- new [123
1546 456]
1547   e:&:editor <- new-editor s, 0/left, 10/right
1548   editor-render screen, e
1549   $clear-trace
1550   # start on second line (no newline after), press ctrl-e
1551   assume-console [
1552   ¦ left-click 2, 1
1553   ¦ press ctrl-e
1554   ]
1555   run [
1556   ¦ editor-event-loop screen, console, e
1557   ¦ 4:num/raw <- get *e, cursor-row:offset
1558   ¦ 5:num/raw <- get *e, cursor-column:offset
1559   ]
1560   # cursor moves to end of line
1561   memory-should-contain [
1562   ¦ 4 <- 2
1563   ¦ 5 <- 3
1564   ]
1565   check-trace-count-for-label 0, [print-character]
1566 ]
1567 
1568 scenario editor-moves-to-end-of-line-with-end [
1569   local-scope
1570   assume-screen 10/width, 5/height
1571   s:text <- new [123
1572 456]
1573   e:&:editor <- new-editor s, 0/left, 10/right
1574   editor-render screen, e
1575   $clear-trace
1576   # start on first line, press 'end'
1577   assume-console [
1578   ¦ left-click 1, 1
1579   ¦ press end
1580   ]
1581   run [
1582   ¦ editor-event-loop screen, console, e
1583   ¦ 3:num/raw <- get *e, cursor-row:offset
1584   ¦ 4:num/raw <- get *e, cursor-column:offset
1585   ]
1586   # cursor moves to end of line
1587   memory-should-contain [
1588   ¦ 3 <- 1
1589   ¦ 4 <- 3
1590   ]
1591   check-trace-count-for-label 0, [print-character]
1592 ]
1593 
1594 scenario editor-moves-to-end-of-line-with-end-2 [
1595   local-scope
1596   assume-screen 10/width, 5/height
1597   s:text <- new [123
1598 456]
1599   e:&:editor <- new-editor s, 0/left, 10/right
1600   editor-render screen, e
1601   $clear-trace
1602   # start on second line (no newline after), press 'end'
1603   assume-console [
1604   ¦ left-click 2, 1
1605   ¦ press end
1606   ]
1607   run [
1608   ¦ editor-event-loop screen, console, e
1609   ¦ 3:num/raw <- get *e, cursor-row:offset
1610   ¦ 4:num/raw <- get *e, cursor-column:offset
1611   ]
1612   # cursor moves to end of line
1613   memory-should-contain [
1614   ¦ 3 <- 2
1615   ¦ 4 <- 3
1616   ]
1617   check-trace-count-for-label 0, [print-character]
1618 ]
1619 
1620 # ctrl-u - delete text from start of line until (but not at) cursor
1621 
1622 scenario editor-deletes-to-start-of-line-with-ctrl-u [
1623   local-scope
1624   assume-screen 10/width, 5/height
1625   s:text <- new [123
1626 456]
1627   e:&:editor <- new-editor s, 0/left, 10/right
1628   editor-render screen, e
1629   $clear-trace
1630   # start on second line, press ctrl-u
1631   assume-console [
1632   ¦ left-click 2, 2
1633   ¦ press ctrl-u
1634   ]
1635   run [
1636   ¦ editor-event-loop screen, console, e
1637   ]
1638   # cursor deletes to start of line
1639   screen-should-contain [
1640   ¦ .          .
1641   ¦ .123       .
1642   ¦ .6         .
1643   ¦ .╌╌╌╌╌╌╌╌╌╌.
1644   ¦ .          .
1645   ]
1646   check-trace-count-for-label 10, [print-character]
1647 ]
1648 
1649 after <handle-special-character> [
1650   {
1651   ¦ delete-to-start-of-line?:bool <- equal c, 21/ctrl-u
1652   ¦ break-unless delete-to-start-of-line?
1653   ¦ <delete-to-start-of-line-begin>
1654   ¦ deleted-cells:&:duplex-list:char <- delete-to-start-of-line editor
1655   ¦ <delete-to-start-of-line-end>
1656   ¦ go-render?:bool <- minimal-render-for-ctrl-u editor, screen
1657   ¦ return
1658   }
1659 ]
1660 
1661 def minimal-render-for-ctrl-u editor:&:editor, screen:&:screen -> go-render?:bool, screen:&:screen [
1662   local-scope
1663   load-ingredients
1664   curr-row:num <- get *editor, cursor-row:offset
1665   curr-column:num <- get *editor, cursor-column:offset
1666   left:num <- get *editor, left:offset
1667   right:num <- get *editor, right:offset
1668   end:num <- subtract left, right
1669   # accumulate the current line as text and render it
1670   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1671   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1672   i:num <- copy 0
1673   {
1674   ¦ i <- add i, 1
1675   ¦ # if we have a wrapped line, give up and render the whole screen
1676   ¦ wrap?:bool <- equal i, end
1677   ¦ return-if wrap?, 1/go-render
1678   ¦ curr <- next curr
1679   ¦ break-unless curr
1680   ¦ c:char <- get *curr, value:offset
1681   ¦ b:bool <- equal c, 10
1682   ¦ break-if b
1683   ¦ buf <- append buf, c
1684   ¦ loop
1685   }
1686   curr-line:text <- buffer-to-array buf
1687   render-code screen, curr-line, curr-column, right, curr-row
1688   return 0/dont-render
1689 ]
1690 
1691 def delete-to-start-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1692   local-scope
1693   load-ingredients
1694   # compute range to delete
1695   init:&:duplex-list:char <- get *editor, data:offset
1696   before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset
1697   start:&:duplex-list:char <- copy before-cursor
1698   end:&:duplex-list:char <- next before-cursor
1699   {
1700   ¦ at-start-of-text?:bool <- equal start, init
1701   ¦ break-if at-start-of-text?
1702   ¦ curr:char <- get *start, value:offset
1703   ¦ at-start-of-line?:bool <- equal curr, 10/newline
1704   ¦ break-if at-start-of-line?
1705   ¦ start <- prev start
1706   ¦ assert start, [delete-to-start-of-line tried to move before start of text]
1707   ¦ loop
1708   }
1709   # snip it out
1710   result:&:duplex-list:char <- next start
1711   remove-between start, end
1712   # adjust cursor
1713   before-cursor <- copy start
1714   *editor <- put *editor, before-cursor:offset, before-cursor
1715   left:num <- get *editor, left:offset
1716   *editor <- put *editor, cursor-column:offset, left
1717 ]
1718 
1719 def render-code screen:&:screen, s:text, left:num, right:num, row:num -> row:num, screen:&:screen [
1720   local-scope
1721   load-ingredients
1722   return-unless s
1723   color:num <- copy 7/white
1724   column:num <- copy left
1725   screen <- move-cursor screen, row, column
1726   screen-height:num <- screen-height screen
1727   i:num <- copy 0
1728   len:num <- length *s
1729   {
1730   ¦ +next-character
1731   ¦ done?:bool <- greater-or-equal i, len
1732   ¦ break-if done?
1733   ¦ done? <- greater-or-equal row, screen-height
1734   ¦ break-if done?
1735   ¦ c:char <- index *s, i
1736   ¦ <character-c-received>
1737   ¦ {
1738   ¦ ¦ # newline? move to left rather than 0
1739   ¦ ¦ newline?:bool <- equal c, 10/newline
1740   ¦ ¦ break-unless newline?
1741   ¦ ¦ # clear rest of line in this window
1742   ¦ ¦ {
1743   ¦ ¦ ¦ done?:bool <- greater-than column, right
1744   ¦ ¦ ¦ break-if done?
1745   ¦ ¦ ¦ space:char <- copy 32/space
1746   ¦ ¦ ¦ print screen, space
1747   ¦ ¦ ¦ column <- add column, 1
1748   ¦ ¦ ¦ loop
1749   ¦ ¦ }
1750   ¦ ¦ row <- add row, 1
1751   ¦ ¦ column <- copy left
1752   ¦ ¦ screen <- move-cursor screen, row, column
1753   ¦ ¦ i <- add i, 1
1754   ¦ ¦ loop +next-character
1755   ¦ }
1756   ¦ {
1757   ¦ ¦ # at right? wrap.
1758   ¦ ¦ at-right?:bool <- equal column, right
1759   ¦ ¦ break-unless at-right?
1760   ¦ ¦ # print wrap icon
1761   ¦ ¦ wrap-icon:char <- copy 8617/loop-back-to-left
1762   ¦ ¦ print screen, wrap-icon, 245/grey
1763   ¦ ¦ column <- copy left
1764   ¦ ¦ row <- add row, 1
1765   ¦ ¦ screen <- move-cursor screen, row, column
1766   ¦ ¦ # don't increment i
1767   ¦ ¦ loop +next-character
1768   ¦ }
1769   ¦ i <- add i, 1
1770   ¦ print screen, c, color
1771   ¦ column <- add column, 1
1772   ¦ loop
1773   }
1774   was-at-left?:bool <- equal column, left
1775   clear-line-until screen, right
1776   {
1777   ¦ break-if was-at-left?
1778   ¦ row <- add row, 1
1779   }
1780   move-cursor screen, row, left
1781 ]
1782 
1783 scenario editor-deletes-to-start-of-line-with-ctrl-u-2 [
1784   local-scope
1785   assume-screen 10/width, 5/height
1786   s:text <- new [123
1787 456]
1788   e:&:editor <- new-editor s, 0/left, 10/right
1789   editor-render screen, e
1790   $clear-trace
1791   # start on first line (no newline before), press ctrl-u
1792   assume-console [
1793   ¦ left-click 1, 2
1794   ¦ press ctrl-u
1795   ]
1796   run [
1797   ¦ editor-event-loop screen, console, e
1798   ]
1799   # cursor deletes to start of line
1800   screen-should-contain [
1801   ¦ .          .
1802   ¦ .3         .
1803   ¦ .456       .
1804   ¦ .╌╌╌╌╌╌╌╌╌╌.
1805   ¦ .          .
1806   ]
1807   check-trace-count-for-label 10, [print-character]
1808 ]
1809 
1810 scenario editor-deletes-to-start-of-line-with-ctrl-u-3 [
1811   local-scope
1812   assume-screen 10/width, 5/height
1813   s:text <- new [123
1814 456]
1815   e:&:editor <- new-editor s, 0/left, 10/right
1816   editor-render screen, e
1817   $clear-trace
1818   # start past end of line, press ctrl-u
1819   assume-console [
1820   ¦ left-click 1, 3
1821   ¦ press ctrl-u
1822   ]
1823   run [
1824   ¦ editor-event-loop screen, console, e
1825   ]
1826   # cursor deletes to start of line
1827   screen-should-contain [
1828   ¦ .          .
1829   ¦ .          .
1830   ¦ .456       .
1831   ¦ .╌╌╌╌╌╌╌╌╌╌.
1832   ¦ .          .
1833   ]
1834   check-trace-count-for-label 10, [print-character]
1835 ]
1836 
1837 scenario editor-deletes-to-start-of-final-line-with-ctrl-u [
1838   local-scope
1839   assume-screen 10/width, 5/height
1840   s:text <- new [123
1841 456]
1842   e:&:editor <- new-editor s, 0/left, 10/right
1843   editor-render screen, e
1844   $clear-trace
1845   # start past end of final line, press ctrl-u
1846   assume-console [
1847   ¦ left-click 2, 3
1848   ¦ press ctrl-u
1849   ]
1850   run [
1851   ¦ editor-event-loop screen, console, e
1852   ]
1853   # cursor deletes to start of line
1854   screen-should-contain [
1855   ¦ .          .
1856   ¦ .123       .
1857   ¦ .          .
1858   ¦ .╌╌╌╌╌╌╌╌╌╌.
1859   ¦ .          .
1860   ]
1861   check-trace-count-for-label 10, [print-character]
1862 ]
1863 
1864 # ctrl-k - delete text from cursor to end of line (but not the newline)
1865 
1866 scenario editor-deletes-to-end-of-line-with-ctrl-k [
1867   local-scope
1868   assume-screen 10/width, 5/height
1869   s:text <- new [123
1870 456]
1871   e:&:editor <- new-editor s, 0/left, 10/right
1872   editor-render screen, e
1873   $clear-trace
1874   # start on first line, press ctrl-k
1875   assume-console [
1876   ¦ left-click 1, 1
1877   ¦ press ctrl-k
1878   ]
1879   run [
1880   ¦ editor-event-loop screen, console, e
1881   ]
1882   # cursor deletes to end of line
1883   screen-should-contain [
1884   ¦ .          .
1885   ¦ .1         .
1886   ¦ .456       .
1887   ¦ .╌╌╌╌╌╌╌╌╌╌.
1888   ¦ .          .
1889   ]
1890   check-trace-count-for-label 9, [print-character]
1891 ]
1892 
1893 after <handle-special-character> [
1894   {
1895   ¦ delete-to-end-of-line?:bool <- equal c, 11/ctrl-k
1896   ¦ break-unless delete-to-end-of-line?
1897   ¦ <delete-to-end-of-line-begin>
1898   ¦ deleted-cells:&:duplex-list:char <- delete-to-end-of-line editor
1899   ¦ <delete-to-end-of-line-end>
1900   ¦ # checks if we can do a minimal render and if we can it will do a minimal render
1901   ¦ go-render?:bool <- minimal-render-for-ctrl-k editor, screen, deleted-cells
1902   ¦ return
1903   }
1904 ]
1905 
1906 def minimal-render-for-ctrl-k editor:&:editor, screen:&:screen, deleted-cells:&:duplex-list:char -> go-render?:bool, screen:&:screen [
1907   local-scope
1908   load-ingredients
1909   # if we deleted nothing, there's nothing to render
1910   return-unless deleted-cells, 0/dont-render
1911   # if we have a wrapped line, give up and render the whole screen
1912   curr-column:num <- get *editor, cursor-column:offset
1913   num-deleted-cells:num <- length, deleted-cells
1914   old-row-len:num <- add curr-column, num-deleted-cells
1915   left:num <- get *editor, left:offset
1916   right:num <- get *editor, right:offset
1917   end:num <- subtract right, left
1918   wrap?:bool <- greater-or-equal old-row-len, end
1919   return-if wrap?, 1/go-render
1920   # accumulate the current line as text and render it
1921   buf:&:buffer:char <- new-buffer 30  # accumulator for the text we need to render
1922   curr:&:duplex-list:char <- get *editor, before-cursor:offset
1923   i:num <- copy 0
1924   {
1925   ¦ i <- add i, 1
1926   ¦ # check if we are at the end of the line
1927   ¦ curr <- next curr
1928   ¦ break-unless curr
1929   ¦ c:char <- get *curr, value:offset
1930   ¦ # check if we have a newline
1931   ¦ b:bool <- equal c, 10
1932   ¦ break-if b
1933   ¦ buf <- append buf, c
1934   ¦ loop
1935   }
1936   curr-line:text <- buffer-to-array buf
1937   curr-row:num <- get *editor, cursor-row:offset
1938   render-code screen, curr-line, curr-column, right, curr-row
1939   return 0/dont-render
1940 ]
1941 
1942 def delete-to-end-of-line editor:&:editor -> result:&:duplex-list:char, editor:&:editor [
1943   local-scope
1944   load-ingredients
1945   # compute range to delete
1946   start:&:duplex-list:char <- get *editor, before-cursor:offset
1947   end:&:duplex-list:char <- next start
1948   {
1949   ¦ at-end-of-text?:bool <- equal end, 0/null
1950   ¦ break-if at-end-of-text?
1951   ¦ curr:char <- get *end, value:offset
1952   ¦ at-end-of-line?:bool <- equal curr, 10/newline
1953   ¦ break-if at-end-of-line?
1954   ¦ end <- next end
1955   ¦ loop
1956   }
1957   # snip it out
1958   result <- next start
1959   remove-between start, end
1960 ]
1961 
1962 scenario editor-deletes-to-end-of-line-with-ctrl-k-2 [
1963   local-scope
1964   assume-screen 10/width, 5/height
1965   s:text <- new [123
1966 456]
1967   e:&:editor <- new-editor s, 0/left, 10/right
1968   editor-render screen, e
1969   $clear-trace
1970   # start on second line (no newline after), press ctrl-k
1971   assume-console [
1972   ¦ left-click 2, 1
1973   ¦ press ctrl-k
1974   ]
1975   run [
1976   ¦ editor-event-loop screen, console, e
1977   ]
1978   # cursor deletes to end of line
1979   screen-should-contain [
1980   ¦ .          .
1981   ¦ .123       .
1982   ¦ .4         .
1983   ¦ .╌╌╌╌╌╌╌╌╌╌.
1984   ¦ .          .
1985   ]
1986   check-trace-count-for-label 9, [print-character]
1987 ]
1988 
1989 scenario editor-deletes-to-end-of-line-with-ctrl-k-3 [
1990   local-scope
1991   assume-screen 10/width, 5/height
1992   s:text <- new [123
1993 456]
1994   e:&:editor <- new-editor s, 0/left, 10/right
1995   editor-render screen, e
1996   $clear-trace
1997   # start at end of line
1998   assume-console [
1999   ¦ left-click 1, 2
2000   ¦ press ctrl-k
2001   ]
2002   run [
2003   ¦ editor-event-loop screen, console, e
2004   ]
2005   # cursor deletes just last character
2006   screen-should-contain [
2007   ¦ .          .
2008   ¦ .12        .
2009   ¦ .456       .
2010   ¦ .╌╌╌╌╌╌╌╌╌╌.
2011   ¦ .          .
2012   ]
2013   check-trace-count-for-label 8, [print-character]
2014 ]
2015 
2016 scenario editor-deletes-to-end-of-line-with-ctrl-k-4 [
2017   local-scope
2018   assume-screen 10/width, 5/height
2019   s:text <- new [123
2020 456]
2021   e:&:editor <- new-editor s, 0/left, 10/right
2022   editor-render screen, e
2023   $clear-trace
2024   # start past end of line
2025   assume-console [
2026   ¦ left-click 1, 3
2027   ¦ press ctrl-k
2028   ]
2029   run [
2030   ¦ editor-event-loop screen, console, e
2031   ]
2032   # cursor deletes nothing
2033   screen-should-contain [
2034   ¦ .          .
2035   ¦ .123       .
2036   ¦ .456       .
2037   ¦ .╌╌╌╌╌╌╌╌╌╌.
2038   ¦ .          .
2039   ]
2040   check-trace-count-for-label 7, [print-character]
2041 ]
2042 
2043 scenario editor-deletes-to-end-of-line-with-ctrl-k-5 [
2044   local-scope
2045   assume-screen 10/width, 5/height
2046   s:text <- new [123
2047 456]
2048   e:&:editor <- new-editor s, 0/left, 10/right
2049   editor-render screen, e
2050   $clear-trace
2051   # start at end of text
2052   assume-console [
2053   ¦ left-click 2, 2
2054   ¦ press ctrl-k
2055   ]
2056   run [
2057   ¦ editor-event-loop screen, console, e
2058   ]
2059   # cursor deletes just the final character
2060   screen-should-contain [
2061   ¦ .          .
2062   ¦ .123       .
2063   ¦ .45        .
2064   ¦ .╌╌╌╌╌╌╌╌╌╌.
2065   ¦ .          .
2066   ]
2067   check-trace-count-for-label 8, [print-character]
2068 ]
2069 
2070 scenario editor-deletes-to-end-of-line-with-ctrl-k-6 [
2071   local-scope
2072   assume-screen 10/width, 5/height
2073   s:text <- new [123
2074 456]
2075   e:&:editor <- new-editor s, 0/left, 10/right
2076   editor-render screen, e
2077   $clear-trace
2078   # start past end of text
2079   assume-console [
2080   ¦ left-click 2, 3
2081   ¦ press ctrl-k
2082   ]
2083   run [
2084   ¦ editor-event-loop screen, console, e
2085   ]
2086   # cursor deletes nothing
2087   screen-should-contain [
2088   ¦ .          .
2089   ¦ .123       .
2090   ¦ .456       .
2091   ¦ .╌╌╌╌╌╌╌╌╌╌.
2092   ¦ .          .
2093   ]
2094   # no prints necessary
2095   check-trace-count-for-label 0, [print-character]
2096 ]
2097 
2098 scenario editor-deletes-to-end-of-wrapped-line-with-ctrl-k [
2099   local-scope
2100   assume-screen 10/width, 5/height
2101   # create an editor with the first line wrapping to a second screen row
2102   s:text <- new [1234
2103 567]
2104   e:&:editor <- new-editor s, 0/left, 4/right
2105   editor-render screen, e
2106   $clear-trace
2107   # delete all of the first wrapped line
2108   assume-console [
2109   ¦ press ctrl-k
2110   ]
2111   run [
2112   ¦ editor-event-loop screen, console, e
2113   ]
2114   # screen shows an empty unwrapped first line
2115   screen-should-contain [
2116   ¦ .          .
2117   ¦ .          .
2118   ¦ .567       .
2119   ¦ .╌╌╌╌      .
2120   ¦ .          .
2121   ]
2122   # entire screen is refreshed
2123   check-trace-count-for-label 16, [print-character]
2124 ]
2125 
2126 # cursor-down can scroll if necessary
2127 
2128 scenario editor-can-scroll-down-using-arrow-keys [
2129   local-scope
2130   # screen has 1 line for menu + 3 lines
2131   assume-screen 10/width, 4/height
2132   # initialize editor with >3 lines
2133   s:text <- new [a
2134 b
2135 c
2136 d]
2137   e:&:editor <- new-editor s, 0/left, 10/right
2138   editor-render screen, e
2139   screen-should-contain [
2140   ¦ .          .
2141   ¦ .a         .
2142   ¦ .b         .
2143   ¦ .c         .
2144   ]
2145   # position cursor at last line, then try to move further down
2146   assume-console [
2147   ¦ left-click 3, 0
2148   ¦ press down-arrow
2149   ]
2150   run [
2151   ¦ editor-event-loop screen, console, e
2152   ]
2153   # screen slides by one line
2154   screen-should-contain [
2155   ¦ .          .
2156   ¦ .b         .
2157   ¦ .c         .
2158   ¦ .d         .
2159   ]
2160 ]
2161 
2162 after <scroll-down> [
2163   trace 10, [app], [scroll down]
2164   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2165   left:num <- get *editor, left:offset
2166   right:num <- get *editor, right:offset
2167   max:num <- subtract right, left
2168   old-top:&:duplex-list:char <- copy top-of-screen
2169   top-of-screen <- before-start-of-next-line top-of-screen, max
2170   *editor <- put *editor, top-of-screen:offset, top-of-screen
2171   no-movement?:bool <- equal old-top, top-of-screen
2172   return-if no-movement?, 0/don't-render
2173 ]
2174 
2175 # takes a pointer into the doubly-linked list, scans ahead at most 'max'
2176 # positions until the next newline
2177 # returns original if no next newline
2178 # beware: never return null pointer.
2179 def before-start-of-next-line original:&:duplex-list:char, max:num -> curr:&:duplex-list:char [
2180   local-scope
2181   load-ingredients
2182   count:num <- copy 0
2183   curr:&:duplex-list:char <- copy original
2184   # skip the initial newline if it exists
2185   {
2186   ¦ c:char <- get *curr, value:offset
2187   ¦ at-newline?:bool <- equal c, 10/newline
2188   ¦ break-unless at-newline?
2189   ¦ curr <- next curr
2190   ¦ count <- add count, 1
2191   }
2192   {
2193   ¦ return-unless curr, original
2194   ¦ done?:bool <- greater-or-equal count, max
2195   ¦ break-if done?
2196   ¦ c:char <- get *curr, value:offset
2197   ¦ at-newline?:bool <- equal c, 10/newline
2198   ¦ break-if at-newline?
2199   ¦ curr <- next curr
2200   ¦ count <- add count, 1
2201   ¦ loop
2202   }
2203   return-unless curr, original
2204   return curr
2205 ]
2206 
2207 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys [
2208   local-scope
2209   # screen has 1 line for menu + 3 lines
2210   assume-screen 10/width, 4/height
2211   # initialize editor with a long, wrapped line and more than a screen of
2212   # other lines
2213   s:text <- new [abcdef
2214 g
2215 h
2216 i]
2217   e:&:editor <- new-editor s, 0/left, 5/right
2218   editor-render screen, e
2219   screen-should-contain [
2220   ¦ .          .
2221   ¦ .abcd↩     .
2222   ¦ .ef        .
2223   ¦ .g         .
2224   ]
2225   # position cursor at last line, then try to move further down
2226   assume-console [
2227   ¦ left-click 3, 0
2228   ¦ press down-arrow
2229   ]
2230   run [
2231   ¦ editor-event-loop screen, console, e
2232   ]
2233   # screen shows partial wrapped line
2234   screen-should-contain [
2235   ¦ .          .
2236   ¦ .ef        .
2237   ¦ .g         .
2238   ¦ .h         .
2239   ]
2240 ]
2241 
2242 scenario editor-scrolls-down-past-wrapped-line-using-arrow-keys-2 [
2243   local-scope
2244   # screen has 1 line for menu + 3 lines
2245   assume-screen 10/width, 4/height
2246   # editor starts with a long line wrapping twice
2247   s:text <- new [abcdefghij
2248 k
2249 l
2250 m]
2251   e:&:editor <- new-editor s, 0/left, 5/right
2252   # position cursor at last line, then try to move further down
2253   assume-console [
2254   ¦ left-click 3, 0
2255   ¦ press down-arrow
2256   ]
2257   run [
2258   ¦ editor-event-loop screen, console, e
2259   ]
2260   # screen shows partial wrapped line containing a wrap icon
2261   screen-should-contain [
2262   ¦ .          .
2263   ¦ .efgh↩     .
2264   ¦ .ij        .
2265   ¦ .k         .
2266   ]
2267   # scroll down again
2268   assume-console [
2269   ¦ press down-arrow
2270   ]
2271   run [
2272   ¦ editor-event-loop screen, console, e
2273   ]
2274   # screen shows partial wrapped line
2275   screen-should-contain [
2276   ¦ .          .
2277   ¦ .ij        .
2278   ¦ .k         .
2279   ¦ .l         .
2280   ]
2281 ]
2282 
2283 scenario editor-scrolls-down-when-line-wraps [
2284   local-scope
2285   # screen has 1 line for menu + 3 lines
2286   assume-screen 5/width, 4/height
2287   # editor contains a long line in the third line
2288   s:text <- new [a
2289 b
2290 cdef]
2291   e:&:editor <- new-editor s, 0/left, 5/right
2292   # position cursor at end, type a character
2293   assume-console [
2294   ¦ left-click 3, 4
2295   ¦ type [g]
2296   ]
2297   run [
2298   ¦ editor-event-loop screen, console, e
2299   ¦ 3:num/raw <- get *e, cursor-row:offset
2300   ¦ 4:num/raw <- get *e, cursor-column:offset
2301   ]
2302   # screen scrolls
2303   screen-should-contain [
2304   ¦ .     .
2305   ¦ .b    .
2306   ¦ .cdef↩.
2307   ¦ .g    .
2308   ]
2309   memory-should-contain [
2310   ¦ 3 <- 3
2311   ¦ 4 <- 1
2312   ]
2313 ]
2314 
2315 scenario editor-scrolls-down-on-newline [
2316   local-scope
2317   assume-screen 5/width, 4/height
2318   # position cursor after last line and type newline
2319   s:text <- new [a
2320 b
2321 c]
2322   e:&:editor <- new-editor s, 0/left, 5/right
2323   assume-console [
2324   ¦ left-click 3, 4
2325   ¦ type [
2326 ]
2327   ]
2328   run [
2329   ¦ editor-event-loop screen, console, e
2330   ¦ 3:num/raw <- get *e, cursor-row:offset
2331   ¦ 4:num/raw <- get *e, cursor-column:offset
2332   ]
2333   # screen scrolls
2334   screen-should-contain [
2335   ¦ .     .
2336   ¦ .b    .
2337   ¦ .c    .
2338   ¦ .     .
2339   ]
2340   memory-should-contain [
2341   ¦ 3 <- 3
2342   ¦ 4 <- 0
2343   ]
2344 ]
2345 
2346 scenario editor-scrolls-down-on-right-arrow [
2347   local-scope
2348   # screen has 1 line for menu + 3 lines
2349   assume-screen 5/width, 4/height
2350   # editor contains a wrapped line
2351   s:text <- new [a
2352 b
2353 cdefgh]
2354   e:&:editor <- new-editor s, 0/left, 5/right
2355   # position cursor at end of screen and try to move right
2356   assume-console [
2357   ¦ left-click 3, 3
2358   ¦ press right-arrow
2359   ]
2360   run [
2361   ¦ editor-event-loop screen, console, e
2362   ¦ 3:num/raw <- get *e, cursor-row:offset
2363   ¦ 4:num/raw <- get *e, cursor-column:offset
2364   ]
2365   # screen scrolls
2366   screen-should-contain [
2367   ¦ .     .
2368   ¦ .b    .
2369   ¦ .cdef↩.
2370   ¦ .gh   .
2371   ]
2372   memory-should-contain [
2373   ¦ 3 <- 3
2374   ¦ 4 <- 0
2375   ]
2376 ]
2377 
2378 scenario editor-scrolls-down-on-right-arrow-2 [
2379   local-scope
2380   # screen has 1 line for menu + 3 lines
2381   assume-screen 5/width, 4/height
2382   # editor contains more lines than can fit on screen
2383   s:text <- new [a
2384 b
2385 c
2386 d]
2387   e:&:editor <- new-editor s, 0/left, 5/right
2388   # position cursor at end of screen and try to move right
2389   assume-console [
2390   ¦ left-click 3, 3
2391   ¦ press right-arrow
2392   ]
2393   run [
2394   ¦ editor-event-loop screen, console, e
2395   ¦ 3:num/raw <- get *e, cursor-row:offset
2396   ¦ 4:num/raw <- get *e, cursor-column:offset
2397   ]
2398   # screen scrolls
2399   screen-should-contain [
2400   ¦ .     .
2401   ¦ .b    .
2402   ¦ .c    .
2403   ¦ .d    .
2404   ]
2405   memory-should-contain [
2406   ¦ 3 <- 3
2407   ¦ 4 <- 0
2408   ]
2409 ]
2410 
2411 scenario editor-scrolls-at-end-on-down-arrow [
2412   local-scope
2413   assume-screen 10/width, 5/height
2414   s:text <- new [abc
2415 de]
2416   e:&:editor <- new-editor s, 0/left, 10/right
2417   editor-render screen, e
2418   $clear-trace
2419   # try to move down past end of text
2420   assume-console [
2421   ¦ left-click 2, 0
2422   ¦ press down-arrow
2423   ]
2424   run [
2425   ¦ editor-event-loop screen, console, e
2426   ¦ 3:num/raw <- get *e, cursor-row:offset
2427   ¦ 4:num/raw <- get *e, cursor-column:offset
2428   ]
2429   # screen should scroll, moving cursor to end of text
2430   memory-should-contain [
2431   ¦ 3 <- 1
2432   ¦ 4 <- 2
2433   ]
2434   assume-console [
2435   ¦ type [0]
2436   ]
2437   run [
2438   ¦ editor-event-loop screen, console, e
2439   ]
2440   screen-should-contain [
2441   ¦ .          .
2442   ¦ .de0       .
2443   ¦ .╌╌╌╌╌╌╌╌╌╌.
2444   ¦ .          .
2445   ]
2446   # try to move down again
2447   $clear-trace
2448   assume-console [
2449   ¦ left-click 2, 0
2450   ¦ press down-arrow
2451   ]
2452   run [
2453   ¦ editor-event-loop screen, console, e
2454   ¦ 3:num/raw <- get *e, cursor-row:offset
2455   ¦ 4:num/raw <- get *e, cursor-column:offset
2456   ]
2457   # screen stops scrolling because cursor is already at top
2458   memory-should-contain [
2459   ¦ 3 <- 1
2460   ¦ 4 <- 3
2461   ]
2462   check-trace-count-for-label 0, [print-character]
2463   assume-console [
2464   ¦ type [1]
2465   ]
2466   run [
2467   ¦ editor-event-loop screen, console, e
2468   ]
2469   screen-should-contain [
2470   ¦ .          .
2471   ¦ .de01      .
2472   ¦ .╌╌╌╌╌╌╌╌╌╌.
2473   ¦ .          .
2474   ]
2475 ]
2476 
2477 scenario editor-combines-page-and-line-scroll [
2478   local-scope
2479   # screen has 1 line for menu + 3 lines
2480   assume-screen 10/width, 4/height
2481   # initialize editor with a few pages of lines
2482   s:text <- new [a
2483 b
2484 c
2485 d
2486 e
2487 f
2488 g]
2489   e:&:editor <- new-editor s, 0/left, 5/right
2490   editor-render screen, e
2491   # scroll down one page and one line
2492   assume-console [
2493   ¦ press page-down
2494   ¦ left-click 3, 0
2495   ¦ press down-arrow
2496   ]
2497   run [
2498   ¦ editor-event-loop screen, console, e
2499   ]
2500   # screen scrolls down 3 lines
2501   screen-should-contain [
2502   ¦ .          .
2503   ¦ .d         .
2504   ¦ .e         .
2505   ¦ .f         .
2506   ]
2507 ]
2508 
2509 # cursor-up can scroll if necessary
2510 
2511 scenario editor-can-scroll-up-using-arrow-keys [
2512   local-scope
2513   # screen has 1 line for menu + 3 lines
2514   assume-screen 10/width, 4/height
2515   # initialize editor with >3 lines
2516   s:text <- new [a
2517 b
2518 c
2519 d]
2520   e:&:editor <- new-editor s, 0/left, 10/right
2521   editor-render screen, e
2522   screen-should-contain [
2523   ¦ .          .
2524   ¦ .a         .
2525   ¦ .b         .
2526   ¦ .c         .
2527   ]
2528   # position cursor at top of second page, then try to move up
2529   assume-console [
2530   ¦ press page-down
2531   ¦ press up-arrow
2532   ]
2533   run [
2534   ¦ editor-event-loop screen, console, e
2535   ]
2536   # screen slides by one line
2537   screen-should-contain [
2538   ¦ .          .
2539   ¦ .b         .
2540   ¦ .c         .
2541   ¦ .d         .
2542   ]
2543 ]
2544 
2545 after <scroll-up> [
2546   trace 10, [app], [scroll up]
2547   top-of-screen:&:duplex-list:char <- get *editor, top-of-screen:offset
2548   old-top:&:duplex-list:char <- copy top-of-screen
2549   top-of-screen <- before-previous-line top-of-screen, editor
2550   *editor <- put *editor, top-of-screen:offset, top-of-screen
2551   no-movement?:bool <- equal old-top, top-of-screen
2552   return-if no-movement?, 0/don't-render
2553 ]
2554 
2555 # takes a pointer into the doubly-linked list, scans back to before start of
2556 # previous *wrapped* line
2557 # returns original if no next newline
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 ]
3492 
3493 # ctrl-s - scroll up by one line
3494 # todo: scenarios
3495 
3496 after <handle-special-character> [
3497   {
3498   ¦ scroll-up?:bool <- equal c, 19/ctrl-s
3499   ¦ break-unless scroll-up?
3500   ¦ <move-cursor-begin>
3501   ¦ go-render?:bool, editor <- line-up editor, screen-height
3502   ¦ undo-coalesce-tag:num <- copy 5/line-up
3503   ¦ <move-cursor-end>
3504   ¦ return go-render?
3505   }
3506 ]
3507 
3508 def line-up editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
3509   local-scope
3510   load-ingredients
3511   left:num <- get *editor, left:offset
3512   right:num <- get *editor, right:offset
3513   max:num <- subtract right, left
3514   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3515   new-top:&:duplex-list:char <- before-start-of-next-line old-top, max
3516   movement?:bool <- not-equal old-top, new-top
3517   {
3518   ¦ break-unless movement?
3519   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3520   }
3521   return movement?
3522 ]
3523 
3524 # ctrl-x - scroll down by one line
3525 # todo: scenarios
3526 
3527 after <handle-special-character> [
3528   {
3529   ¦ scroll-down?:bool <- equal c, 24/ctrl-x
3530   ¦ break-unless scroll-down?
3531   ¦ <move-cursor-begin>
3532   ¦ go-render?:bool, editor <- line-down editor, screen-height
3533   ¦ undo-coalesce-tag:num <- copy 6/line-down
3534   ¦ <move-cursor-end>
3535   ¦ return go-render?
3536   }
3537 ]
3538 
3539 def line-down editor:&:editor, screen-height:num -> go-render?:bool, editor:&:editor [
3540   local-scope
3541   load-ingredients
3542   old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3543   new-top:&:duplex-list:char <- before-previous-line old-top, editor
3544   movement?:bool <- not-equal old-top, new-top
3545   {
3546   ¦ break-unless movement?
3547   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3548   }
3549   return movement?
3550 ]
3551 
3552 # ctrl-t - move current line to top of screen
3553 # todo: scenarios
3554 
3555 after <handle-special-character> [
3556   {
3557   ¦ scroll-down?:bool <- equal c, 20/ctrl-t
3558   ¦ break-unless scroll-down?
3559   ¦ <move-cursor-begin>
3560   ¦ old-top:&:duplex-list:char <- get *editor, top-of-screen:offset
3561   ¦ cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3562   ¦ cursor <- next cursor
3563   ¦ new-top:&:duplex-list:char <- before-previous-line cursor, editor
3564   ¦ *editor <- put *editor, top-of-screen:offset, new-top
3565   ¦ *editor <- put *editor, cursor-row:offset, 1
3566   ¦ go-render?:bool <- not-equal new-top, old-top
3567   ¦ undo-coalesce-tag:num <- copy 0/never
3568   ¦ <move-cursor-end>
3569   ¦ return go-render?
3570   }
3571 ]
3572 
3573 # ctrl-/ - comment/uncomment current line
3574 # todo: scenarios
3575 
3576 after <handle-special-character> [
3577   {
3578   ¦ comment-toggle?:bool <- equal c, 31/ctrl-slash
3579   ¦ break-unless comment-toggle?
3580   ¦ cursor-column:num <- get *editor, cursor-column:offset
3581   ¦ data:&:duplex-list:char <- get *editor, data:offset
3582   ¦ <insert-character-begin>
3583   ¦ cursor:&:duplex-list:char <- get *editor, before-cursor:offset
3584   ¦ {
3585   ¦ ¦ next:&:duplex-list:char <- next cursor
3586   ¦ ¦ break-unless next
3587   ¦ ¦ cursor <- copy next
3588   ¦ }
3589   ¦ before-line-start:&:duplex-list:char <- before-previous-line cursor, editor
3590   ¦ line-start:&:duplex-list:char <- next before-line-start
3591   ¦ commented-out?:bool <- match line-start, [#? ]  # comment prefix
3592   ¦ {
3593   ¦ ¦ break-unless commented-out?
3594   ¦ ¦ # uncomment
3595   ¦ ¦ data <- remove line-start, 3/length-comment-prefix, data
3596   ¦ ¦ cursor-column <- subtract cursor-column, 3/length-comment-prefix
3597   ¦ }
3598   ¦ {
3599   ¦ ¦ break-if commented-out?
3600   ¦ ¦ # comment
3601   ¦ ¦ insert before-line-start, [#? ]
3602   ¦ ¦ cursor-column <- add cursor-column, 3/length-comment-prefix
3603   ¦ }
3604   ¦ *editor <- put *editor, cursor-column:offset, cursor-column
3605   ¦ <insert-character-end>
3606   ¦ return 1/do-render
3607   }
3608 ]
3609 
3610 scenario editor-comments-empty-line [
3611   local-scope
3612   assume-screen 10/width, 5/height
3613   e:&:editor <- new-editor [], 0/left, 5/right
3614   assume-console [
3615   ¦ press ctrl-slash
3616   ]
3617   run [
3618   ¦ editor-event-loop screen, console, e
3619   ¦ 4:num/raw <- get *e, cursor-row:offset
3620   ¦ 5:num/raw <- get *e, cursor-column:offset
3621   ]
3622   screen-should-contain [
3623   ¦ .          .
3624   ¦ .#?        .
3625   ¦ .╌╌╌╌╌     .
3626   ¦ .          .
3627   ]
3628   memory-should-contain [
3629   ¦ 4 <- 1
3630   ¦ 5 <- 3
3631   ]
3632 ]
3633 
3634 scenario editor-comments-at-start-of-contents [
3635   local-scope
3636   assume-screen 10/width, 5/height
3637   e:&:editor <- new-editor [ab], 0/left, 10/right
3638   assume-console [
3639   ¦ press ctrl-slash
3640   ]
3641   run [
3642   ¦ editor-event-loop screen, console, e
3643   ¦ 4:num/raw <- get *e, cursor-row:offset
3644   ¦ 5:num/raw <- get *e, cursor-column:offset
3645   ]
3646   screen-should-contain [
3647   ¦ .          .
3648   ¦ .#? ab     .
3649   ¦ .╌╌╌╌╌╌╌╌╌╌.
3650   ¦ .          .
3651   ]
3652   memory-should-contain [
3653   ¦ 4 <- 1
3654   ¦ 5 <- 3
3655   ]
3656 ]
3657 
3658 scenario editor-comments-at-end-of-contents [
3659   local-scope
3660   assume-screen 10/width, 5/height
3661   e:&:editor <- new-editor [ab], 0/left, 10/right
3662   assume-console [
3663   ¦ left-click 1, 7
3664   ¦ press ctrl-slash
3665   ]
3666   run [
3667   ¦ editor-event-loop screen, console, e
3668   ¦ 4:num/raw <- get *e, cursor-row:offset
3669   ¦ 5:num/raw <- get *e, cursor-column:offset
3670   ]
3671   screen-should-contain [
3672   ¦ .          .
3673   ¦ .#? ab     .
3674   ¦ .╌╌╌╌╌╌╌╌╌╌.
3675   ¦ .          .
3676   ]
3677   memory-should-contain [
3678   ¦ 4 <- 1
3679   ¦ 5 <- 5
3680   ]
3681 ]