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