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