1 ## handling events from the keyboard, mouse, touch screen, ...
   2 
   3 # temporary main: interactive editor
   4 # hit ctrl-c to exit
   5 def! main text:text [
   6   local-scope
   7   load-inputs
   8   open-console
   9   clear-screen 0/screen  # non-scrolling app
  10   editor:&:editor <- new-editor text, 5/left, 45/right
  11   editor-render 0/screen, editor
  12   editor-event-loop 0/screen, 0/console, editor
  13   close-console
  14 ]
  15 
  16 def editor-event-loop screen:&:screen, console:&:console, editor:&:editor -> screen:&:screen, console:&:console, editor:&:editor [
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: package ranger.defaults</title>
</head><body bgcolor="#f0f0f8">

<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="ranger.html"><font color="#ffffff">ranger</font></a>.defaults</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/hut/ranger/ranger/defaults/__init__.py">/home/hut/ranger/ranger/defaults/__init__.py</a></font></td></tr></table>
    <p><tt>Default&nbsp;options&nbsp;and&nbsp;configration&nbsp;files</tt></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#aa55cc">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
    
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="ranger.defaults.apps.html">apps</a><br>
</td><td width="25%" valign=top><a href="ranger.defaults.commands.html">commands</a><br>
</td><td width="25%" valign=top><a href="ranger.defaults.keys.html">keys</a><br>
</td><td width="25%" valign=top><a href="ranger.defaults.options.html">options</a><br>
</td></tr></table></td></tr></table>
</body></html>
d="L145" class="LineNr"> 145 loop 146 } 147 # is cursor to the right of the last line? move to end 148 { 149 at-cursor-row?:bool <- equal row, cursor-row 150 cursor-outside-line?:bool <- lesser-or-equal column, cursor-column 151 before-cursor-on-same-line?:bool <- and at-cursor-row?, cursor-outside-line? 152 above-cursor-row?:bool <- lesser-than row, cursor-row 153 before-cursor?:bool <- or before-cursor-on-same-line?, above-cursor-row? 154 break-unless before-cursor? 155 cursor-row <- copy row 156 *editor <- put *editor, cursor-row:offset, cursor-row 157 cursor-column <- copy column 158 *editor <- put *editor, cursor-column:offset, cursor-column 159 before-cursor <- copy prev 160 *editor <- put *editor, before-cursor:offset, before-cursor 161 } 162 ] 163 164 # Process an event 'e' and try to minimally update the screen. 165 # Set 'go-render?' to true to indicate the caller must perform a non-minimal update. 166 def handle-keyboard-event screen:&:screen, editor:&:editor, e:event -> go-render?:bool, screen:&:screen, editor:&:editor [ 167 local-scope 168 load-inputs 169 return-unless editor, 0/don't-render 170 screen-width:num <- screen-width screen 171 screen-height:num <- screen-height screen 172 left:num <- get *editor, left:offset 173 right:num <- get *editor, right:offset 174 before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset 175 cursor-row:num <- get *editor, cursor-row:offset 176 cursor-column:num <- get *editor, cursor-column:offset 177 save-row:num <- copy cursor-row 178 save-column:num <- copy cursor-column 179 # character 180 { 181 c:char, is-unicode?:bool <- maybe-convert e, text:variant 182 break-unless is-unicode? 183 trace 10, [app], [handle-keyboard-event: special character] 184 # exceptions for special characters go here 185 <handle-special-character> 186 # ignore any other special characters 187 regular-character?:bool <- greater-or-equal c, 32/space 188 return-unless regular-character?, 0/don't-render 189 # otherwise type it in 190 <begin-insert-character> 191 go-render? <- insert-at-cursor editor, c, screen 192 <end-insert-character> 193 return 194 } 195 # special key to modify the text or move the cursor 196 k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant 197 assert is-keycode?, [event was of unknown type; neither keyboard nor mouse] 198 # handlers for each special key will go here 199 <handle-special-key> 200 return 1/go-render 201 ] 202 203 def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool, editor:&:editor, screen:&:screen [ 204 local-scope 205 load-inputs 206 before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset 207 insert c, before-cursor 208 before-cursor <- next before-cursor 209 *editor <- put *editor, before-cursor:offset, before-cursor 210 cursor-row:num <- get *editor, cursor-row:offset 211 cursor-column:num <- get *editor, cursor-column:offset 212 left:num <- get *editor, left:offset 213 right:num <- get *editor, right:offset 214 save-row:num <- copy cursor-row 215 save-column:num <- copy cursor-column 216 screen-width:num <- screen-width screen 217 screen-height:num <- screen-height screen 218 # occasionally we'll need to mess with the cursor 219 <insert-character-special-case> 220 # but mostly we'll just move the cursor right 221 cursor-column <- add cursor-column, 1 222 *editor <- put *editor, cursor-column:offset, cursor-column 223 next:&:duplex-list:char <- next before-cursor 224 { 225 # at end of all text? no need to scroll? just print the character and leave 226 at-end?:bool <- equal next, 0/null 227 break-unless at-end? 228 bottom:num <- subtract screen-height, 1 229 at-bottom?:bool <- equal save-row, bottom 230 at-right?:bool <- equal save-column, right 231 overflow?:bool <- and at-bottom?, at-right? 232 break-if overflow? 233 move-cursor screen, save-row, save-column 234 print screen, c 235 return 0/don't-render 236 } 237 { 238 # not at right margin? print the character and rest of line 239 break-unless next 240 at-right?:bool <- greater-or-equal cursor-column, screen-width 241 break-if at-right? 242 curr:&:duplex-list:char <- copy before-cursor 243 move-cursor screen, save-row, save-column 244 curr-column:num <- copy save-column 245 { 246 # hit right margin? give up and let caller render 247 at-right?:bool <- greater-than curr-column, right 248 return-if at-right?, 1/go-render 249 break-unless curr 250 # newline? done. 251 currc:char <- get *curr, value:offset 252 at-newline?:bool <- equal currc, 10/newline 253 break-if at-newline? 254 print screen, currc 255 curr-column <- add curr-column, 1 256 curr <- next curr 257 loop 258 } 259 return 0/don't-render 260 } 261 return 1/go-render 262 ] 263 264 # helper for tests 265 def editor-render screen:&:screen, editor:&:editor -> screen:&:screen, editor:&:editor [ 266 local-scope 267 load-inputs 268 old-top-idx:num <- save-top-idx screen 269 left:num <- get *editor, left:offset 270 right:num <- get *editor, right:offset 271 row:num, column:num <- render screen, editor 272 draw-horizontal screen, row, left, right, 9480/horizontal-dotted 273 row <- add row, 1 274 clear-screen-from screen, row, left, left, right 275 assert-no-scroll screen, old-top-idx 276 ] 277 278 scenario editor-handles-empty-event-queue [ 279 local-scope 280 assume-screen 10/width, 5/height 281 e:&:editor <- new-editor [abc], 0/left, 10/right 282 editor-render screen, e 283 assume-console [] 284 run [ 285 editor-event-loop screen, console, e 286 ] 287 screen-should-contain [ 288 . . 289 .abc . 290 .╌╌╌╌╌╌╌╌╌╌. 291 . . 292 ] 293 ] 294 295 scenario editor-handles-mouse-clicks [ 296 local-scope 297 assume-screen 10/width, 5/height 298 e:&:editor <- new-editor [abc], 0/left, 10/right 299 editor-render screen, e 300 $clear-trace 301 assume-console [ 302 left-click 1, 1 # on the 'b' 303 ] 304 run [ 305 editor-event-loop screen, console, e 306 3:num/raw <- get *e, cursor-row:offset 307 4:num/raw <- get *e, cursor-column:offset 308 ] 309 screen-should-contain [ 310 . . 311 .abc . 312 .╌╌╌╌╌╌╌╌╌╌. 313 . . 314 ] 315 memory-should-contain [ 316 3 <- 1 # cursor is at row 0.. 317 4 <- 1 # ..and column 1 318 ] 319 check-trace-count-for-label 0, [print-character] 320 ] 321 322 scenario editor-handles-mouse-clicks-outside-text [ 323 local-scope 324 assume-screen 10/width, 5/height 325 e:&:editor <- new-editor [abc], 0/left, 10/right 326 $clear-trace 327 assume-console [ 328 left-click 1, 7 # last line, to the right of text 329 ] 330 run [ 331 editor-event-loop screen, console, e 332 3:num/raw <- get *e, cursor-row:offset 333 4:num/raw <- get *e, cursor-column:offset 334 ] 335 memory-should-contain [ 336 3 <- 1 # cursor row 337 4 <- 3 # cursor column 338 ] 339 check-trace-count-for-label 0, [print-character] 340 ] 341 342 scenario editor-handles-mouse-clicks-outside-text-2 [ 343 local-scope 344 assume-screen 10/width, 5/height 345 s:text <- new [abc 346 def] 347 e:&:editor <- new-editor s, 0/left, 10/right 348 $clear-trace 349 assume-console [ 350 left-click 1, 7 # interior line, to the right of text 351 ] 352 run [ 353 editor-event-loop screen, console, e 354 3:num/raw <- get *e, cursor-row:offset 355 4:num/raw <- get *e, cursor-column:offset 356 ] 357 memory-should-contain [ 358 3 <- 1 # cursor row 359 4 <- 3 # cursor column 360 ] 361 check-trace-count-for-label 0, [print-character] 362 ] 363 364 scenario editor-handles-mouse-clicks-outside-text-3 [ 365 local-scope 366 assume-screen 10/width, 5/height 367 s:text <- new [abc 368 def] 369 e:&:editor <- new-editor s, 0/left, 10/right 370 $clear-trace 371 assume-console [ 372 left-click 3, 7 # below text 373 ] 374 run [ 375 editor-event-loop screen, console, e 376 3:num/raw <- get *e, cursor-row:offset 377 4:num/raw <- get *e, cursor-column:offset 378 ] 379 memory-should-contain [ 380 3 <- 2 # cursor row 381 4 <- 3 # cursor column 382 ] 383 check-trace-count-for-label 0, [print-character] 384 ] 385 386 scenario editor-handles-mouse-clicks-outside-column [ 387 local-scope 388 assume-screen 10/width, 5/height 389 # editor occupies only left half of screen 390 e:&:editor <- new-editor [abc], 0/left, 5/right 391 editor-render screen, e 392 $clear-trace 393 assume-console [ 394 # click on right half of screen 395 left-click 3, 8 396 ] 397 run [ 398 editor-event-loop screen, console, e 399 3:num/raw <- get *e, cursor-row:offset 400 4:num/raw <- get *e, cursor-column:offset 401 ] 402 screen-should-contain [ 403 . . 404 .abc . 405 .╌╌╌╌╌ . 406 . . 407 ] 408 memory-should-contain [ 409 3 <- 1 # no change to cursor row 410 4 <- 0 # ..or column 411 ] 412 check-trace-count-for-label 0, [print-character] 413 ] 414 415 scenario editor-handles-mouse-clicks-in-menu-area [ 416 local-scope 417 assume-screen 10/width, 5/height 418 e:&:editor <- new-editor [abc], 0/left, 5/right 419 editor-render screen, e 420 $clear-trace 421 assume-console [ 422 # click on first, 'menu' row 423 left-click 0, 3 424 ] 425 run [ 426 editor-event-loop screen, console, e 427 3:num/raw <- get *e, cursor-row:offset 428 4:num/raw <- get *e, cursor-column:offset 429 ] 430 # no change to cursor 431 memory-should-contain [ 432 3 <- 1 433 4 <- 0 434 ] 435 ] 436 437 scenario editor-inserts-characters-into-empty-editor [ 438 local-scope 439 assume-screen 10/width, 5/height 440 e:&:editor <- new-editor [], 0/left, 5/right 441 editor-render screen, e 442 $clear-trace 443 assume-console [ 444 type [abc] 445 ] 446 run [ 447 editor-event-loop screen, console, e 448 ] 449 screen-should-contain [ 450 . . 451 .abc . 452 .╌╌╌╌╌ . 453 . . 454 ] 455 check-trace-count-for-label 3, [print-character] 456 ] 457 458 scenario editor-inserts-characters-at-cursor [ 459 local-scope 460 assume-screen 10/width, 5/height 461 e:&:editor <- new-editor [abc], 0/left, 10/right 462 editor-render screen, e 463 $clear-trace 464 # type two letters at different places 465 assume-console [ 466 type [0] 467 left-click 1, 2 468 type [d] 469 ] 470 run [ 471 editor-event-loop screen, console, e 472 ] 473 screen-should-contain [ 474 . . 475 .0adbc . 476 .╌╌╌╌╌╌╌╌╌╌. 477 . . 478 ] 479 check-trace-count-for-label 7, [print-character] # 4 for first letter, 3 for second 480 ] 481 482 scenario editor-inserts-characters-at-cursor-2 [ 483 local-scope 484 assume-screen 10/width, 5/height 485 e:&:editor <- new-editor [abc], 0/left, 10/right 486 editor-render screen, e 487 $clear-trace 488 assume-console [ 489 left-click 1, 5 # right of last line 490 type [d] 491 ] 492 run [ 493 editor-event-loop screen, console, e 494 ] 495 screen-should-contain [ 496 . . 497 .abcd . 498 .╌╌╌╌╌╌╌╌╌╌. 499 . . 500 ] 501 check-trace-count-for-label 1, [print-character] 502 ] 503 504 scenario editor-inserts-characters-at-cursor-5 [ 505 local-scope 506 assume-screen 10/width, 5/height 507 s:text <- new [abc 508 d] 509 e:&:editor <- new-editor s, 0/left, 10/right 510 editor-render screen, e 511 $clear-trace 512 assume-console [ 513 left-click 1, 5 # right of non-last line 514 type [e] 515 ] 516 run [ 517 editor-event-loop screen, console, e 518 ] 519 screen-should-contain [ 520 . . 521 .abce . 522 .d . 523 .╌╌╌╌╌╌╌╌╌╌. 524 . . 525 ] 526 check-trace-count-for-label 1, [print-character] 527 ] 528 529 scenario editor-inserts-characters-at-cursor-3 [ 530 local-scope 531 assume-screen 10/width, 5/height 532 e:&:editor <- new-editor [abc], 0/left, 10/right 533 editor-render screen, e 534 $clear-trace 535 assume-console [ 536 left-click 3, 5 # below all text 537 type [d] 538 ] 539 run [ 540 editor-event-loop screen, console, e 541 ] 542 screen-should-contain [ 543 . . 544 .abcd . 545 .╌╌╌╌╌╌╌╌╌╌. 546 . . 547 ] 548 check-trace-count-for-label 1, [print-character] 549 ] 550 551 scenario editor-inserts-characters-at-cursor-4 [ 552 local-scope 553 assume-screen 10/width, 5/height 554 s:text <- new [abc 555 d] 556 e:&:editor <- new-editor s, 0/left, 10/right 557 editor-render screen, e 558 $clear-trace 559 assume-console [ 560 left-click 3, 5 # below all text 561 type [e] 562 ] 563 run [ 564 editor-event-loop screen, console, e 565 ] 566 screen-should-contain [ 567 . . 568 .abc . 569 .de . 570 .╌╌╌╌╌╌╌╌╌╌. 571 . . 572 ] 573 check-trace-count-for-label 1, [print-character] 574 ] 575 576 scenario editor-inserts-characters-at-cursor-6 [ 577 local-scope 578 assume-screen 10/width, 5/height 579 s:text <- new [abc 580 d] 581 e:&:editor <- new-editor s, 0/left, 10/right 582 editor-render screen, e 583 $clear-trace 584 assume-console [ 585 left-click 3, 5 # below all text 586 type [ef] 587 ] 588 run [ 589 editor-event-loop screen, console, e 590 ] 591 screen-should-contain [ 592 . . 593 .abc . 594 .def . 595 .╌╌╌╌╌╌╌╌╌╌. 596 . . 597 ] 598 check-trace-count-for-label 2, [print-character] 599 ] 600 601 scenario editor-moves-cursor-after-inserting-characters [ 602 local-scope 603 assume-screen 10/width, 5/height 604 e:&:editor <- new-editor [ab], 0/left, 5/right 605 editor-render screen, e 606 assume-console [ 607 type [01] 608 ] 609 run [ 610 editor-event-loop screen, console, e 611 ] 612 screen-should-contain [ 613 . . 614 .01ab . 615 .╌╌╌╌╌ . 616 . . 617 ] 618 ] 619 620 # if the cursor reaches the right margin, wrap the line 621 622 scenario editor-wraps-line-on-insert [ 623 local-scope 624 assume-screen 5/width, 5/height 625 e:&:editor <- new-editor [abc], 0/left, 5/right 626 editor-render screen, e 627 # type a letter 628 assume-console [ 629 type [e] 630 ] 631 run [ 632 editor-event-loop screen, console, e 633 ] 634 # no wrap yet 635 screen-should-contain [ 636 . . 637 .eabc . 638 .╌╌╌╌╌. 639 . . 640 . . 641 ] 642 # type a second letter 643 assume-console [ 644 type [f] 645 ] 646 run [ 647 editor-event-loop screen, console, e 648 ] 649 # now wrap 650 screen-should-contain [ 651 . . 652 .efab↩. 653 .c . 654 .╌╌╌╌╌. 655 . . 656 ] 657 ] 658 659 scenario editor-wraps-line-on-insert-2 [ 660 local-scope 661 # create an editor with some text 662 assume-screen 10/width, 5/height 663 s:text <- new [abcdefg 664 defg] 665 e:&:editor <- new-editor s, 0/left, 5/right 666 editor-render screen, e 667 # type more text at the start 668 assume-console [ 669 left-click 3, 0 670 type [abc] 671 ] 672 run [ 673 editor-event-loop screen, console, e 674 3:num/raw <- get *e, cursor-row:offset 675 4:num/raw <- get *e, cursor-column:offset 676 ] 677 # cursor is not wrapped 678 memory-should-contain [ 679 3 <- 3 680 4 <- 3 681 ] 682 # but line is wrapped 683 screen-should-contain [ 684 . . 685 .abcd↩ . 686 .efg . 687 .abcd↩ . 688 .efg . 689 ] 690 ] 691 692 after <insert-character-special-case> [ 693 # if the line wraps at the cursor, move cursor to start of next row 694 { 695 # if either: 696 # a) we're at the end of the line and at the column of the wrap indicator, or 697 # b) we're not at end of line and just before the column of the wrap indicator 698 wrap-column:num <- copy right 699 before-wrap-column:num <- subtract wrap-column, 1 700 at-wrap?:bool <- greater-or-equal cursor-column, wrap-column 701 just-before-wrap?:bool <- greater-or-equal cursor-column, before-wrap-column 702 next:&:duplex-list:char <- next before-cursor 703 # at end of line? next == 0 || next.value == 10/newline 704 at-end-of-line?:bool <- equal next, 0 705 { 706 break-if at-end-of-line? 707 next-character:char <- get *next, value:offset 708 at-end-of-line? <- equal next-character, 10/newline 709 } 710 # break unless ((eol? and at-wrap?) or (~eol? and just-before-wrap?)) 711 move-cursor-to-next-line?:bool <- copy 0/false 712 { 713 break-if at-end-of-line? 714 move-cursor-to-next-line? <- copy just-before-wrap? 715 # if we're moving the cursor because it's in the middle of a wrapping 716 # line, adjust it to left-most column 717 potential-new-cursor-column:num <- copy left 718 } 719 { 720 break-unless at-end-of-line? 721 move-cursor-to-next-line? <- copy at-wrap? 722 # if we're moving the cursor because it's at the end of a wrapping line, 723 # adjust it to one past the left-most column to make room for the 724 # newly-inserted wrap-indicator 725 potential-new-cursor-column:num <- add left, 1/make-room-for-wrap-indicator 726 } 727 break-unless move-cursor-to-next-line? 728 cursor-column <- copy potential-new-cursor-column 729 *editor <- put *editor, cursor-column:offset, cursor-column 730 cursor-row <- add cursor-row, 1 731 *editor <- put *editor, cursor-row:offset, cursor-row 732 # if we're out of the screen, scroll down 733 { 734 below-screen?:bool <- greater-or-equal cursor-row, screen-height 735 break-unless below-screen? 736 <scroll-down> 737 } 738 return 1/go-render 739 } 740 ] 741 742 scenario editor-wraps-cursor-after-inserting-characters-in-middle-of-line [ 743 local-scope 744 assume-screen 10/width, 5/height 745 e:&:editor <- new-editor [abcde], 0/left, 5/right 746 assume-console [ 747 left-click 1, 3 # right before the wrap icon 748 type [f] 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 screen-should-contain [ 756 . . 757 .abcf↩ . 758 .de . 759 .╌╌╌╌╌ . 760 . . 761 ] 762 memory-should-contain [ 763 3 <- 2 # cursor row 764 4 <- 0 # cursor column 765 ] 766 ] 767 768 scenario editor-wraps-cursor-after-inserting-characters-at-end-of-line [ 769 local-scope 770 assume-screen 10/width, 5/height 771 # create an editor containing two lines 772 s:text <- new [abc 773 xyz] 774 e:&:editor <- new-editor s, 0/left, 5/right 775 editor-render screen, e 776 screen-should-contain [ 777 . . 778 .abc . 779 .xyz . 780 .╌╌╌╌╌ . 781 . . 782 ] 783 assume-console [ 784 left-click 1, 4 # at end of first line 785 type [de] # trigger wrap 786 ] 787 run [ 788 editor-event-loop screen, console, e 789 ] 790 screen-should-contain [ 791 . . 792 .abcd↩ . 793 .e . 794 .xyz . 795 .╌╌╌╌╌ . 796 ] 797 ] 798 799 scenario editor-wraps-cursor-to-left-margin [ 800 local-scope 801 assume-screen 10/width, 5/height 802 e:&:editor <- new-editor [abcde], 2/left, 7/right 803 assume-console [ 804 left-click 1, 5 # line is full; no wrap icon yet 805 type [01] 806 ] 807 run [ 808 editor-event-loop screen, console, e 809 3:num/raw <- get *e, cursor-row:offset 810 4:num/raw <- get *e, cursor-column:offset 811 ] 812 screen-should-contain [ 813 . . 814 . abc0↩ . 815 . 1de . 816 . ╌╌╌╌╌ . 817 . . 818 ] 819 memory-should-contain [ 820 3 <- 2 # cursor row 821 4 <- 3 # cursor column 822 ] 823 ] 824 825 # if newline, move cursor to start of next line, and maybe align indent with previous line 826 827 container editor [ 828 indent?:bool 829 ] 830 831 after <editor-initialization> [ 832 *result <- put *result, indent?:offset, 1/true 833 ] 834 835 scenario editor-moves-cursor-down-after-inserting-newline [ 836 local-scope 837 assume-screen 10/width, 5/height 838 e:&:editor <- new-editor [abc], 0/left, 10/right 839 assume-console [ 840 type [0 841 1] 842 ] 843 run [ 844 editor-event-loop screen, console, e 845 ] 846 screen-should-contain [ 847 . . 848 .0 . 849 .1abc . 850 .╌╌╌╌╌╌╌╌╌╌. 851 . . 852 ] 853 ] 854 855 after <handle-special-character> [ 856 { 857 newline?:bool <- equal c, 10/newline 858 break-unless newline? 859 <begin-insert-enter> 860 insert-new-line-and-indent editor, screen 861 <end-insert-enter> 862 return 1/go-render 863 } 864 ] 865 866 def insert-new-line-and-indent editor:&:editor, screen:&:screen -> editor:&:editor, screen:&:screen [ 867 local-scope 868 load-inputs 869 cursor-row:num <- get *editor, cursor-row:offset 870 cursor-column:num <- get *editor, cursor-column:offset 871 before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset 872 left:num <- get *editor, left:offset 873 right:num <- get *editor, right:offset 874 screen-height:num <- screen-height screen 875 # update cursor coordinates 876 at-start-of-wrapped-line?:bool <- at-start-of-wrapped-line? editor 877 { 878 break-if at-start-of-wrapped-line? 879 cursor-row <- add cursor-row, 1 880 *editor <- put *editor, cursor-row:offset, cursor-row 881 } 882 cursor-column <- copy left 883 *editor <- put *editor, cursor-column:offset, cursor-column 884 # maybe scroll 885 { 886 below-screen?:bool <- greater-or-equal cursor-row, screen-height # must be equal, never greater 887 break-unless below-screen? 888 <scroll-down> 889 cursor-row <- subtract cursor-row, 1 # bring back into screen range 890 *editor <- put *editor, cursor-row:offset, cursor-row 891 } 892 # insert newline 893 insert 10/newline, before-cursor 894 before-cursor <- next before-cursor 895 *editor <- put *editor, before-cursor:offset, before-cursor 896 # indent if necessary 897 indent?:bool <- get *editor, indent?:offset 898 return-unless indent? 899 d:&:duplex-list:char <- get *editor, data:offset 900 end-of-previous-line:&:duplex-list:char <- prev before-cursor 901 indent:num <- line-indent end-of-previous-line, d 902 i:num <- copy 0 903 { 904 indent-done?:bool <- greater-or-equal i, indent 905 break-if indent-done? 906 insert-at-cursor editor, 32/space, screen 907 i <- add i, 1 908 loop 909 } 910 ] 911 912 def at-start-of-wrapped-line? editor:&:editor -> result:bool [ 913 local-scope 914 load-inputs 915 left:num <- get *editor, left:offset 916 cursor-column:num <- get *editor, cursor-column:offset 917 cursor-at-left?:bool <- equal cursor-column, left 918 return-unless cursor-at-left?, 0/false 919 before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset 920 before-before-cursor:&:duplex-list:char <- prev before-cursor 921 return-unless before-before-cursor, 0/false # cursor is at start of editor 922 char-before-cursor:char <- get *before-cursor, value:offset 923 cursor-after-newline?:bool <- equal char-before-cursor, 10/newline 924 return-if cursor-after-newline?, 0/false 925 # if cursor is at left margin and not at start, but previous character is not a newline, 926 # then we're at start of a wrapped line 927 return 1/true 928 ] 929 930 # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts 931 # the number of spaces at the start of the line containing 'curr'. 932 def line-indent curr:&:duplex-list:char, start:&:duplex-list:char -> result:num [ 933 local-scope 934 load-inputs 935 result:num <- copy 0 936 return-unless curr 937 at-start?:bool <- equal curr, start 938 return-if at-start? 939 { 940 curr <- prev curr 941 break-unless curr 942 at-start?:bool <- equal curr, start 943 break-if at-start? 944 c:char <- get *curr, value:offset 945 at-newline?:bool <- equal c, 10/newline 946 break-if at-newline? 947 # if c is a space, increment result 948 is-space?:bool <- equal c, 32/space 949 { 950 break-unless is-space? 951 result <- add result, 1 952 } 953 # if c is not a space, reset result 954 { 955 break-if is-space? 956 result <- copy 0 957 } 958 loop 959 } 960 ] 961 962 scenario editor-moves-cursor-down-after-inserting-newline-2 [ 963 local-scope 964 assume-screen 10/width, 5/height 965 e:&:editor <- new-editor [abc], 1/left, 10/right 966 assume-console [ 967 type [0 968 1] 969 ] 970 run [ 971 editor-event-loop screen, console, e 972 ] 973 screen-should-contain [ 974 . . 975 . 0 . 976 . 1abc . 977 . ╌╌╌╌╌╌╌╌╌. 978 . . 979 ] 980 ] 981 982 scenario editor-clears-previous-line-completely-after-inserting-newline [ 983 local-scope 984 assume-screen 10/width, 5/height 985 e:&:editor <- new-editor [abcde], 0/left, 5/right 986 editor-render screen, e 987 screen-should-contain [ 988 . . 989 .abcd↩ . 990 .e . 991 .╌╌╌╌╌ . 992 . . 993 ] 994 assume-console [ 995 press enter 996 ] 997 run [ 998 editor-event-loop screen, console, e 999 ] 1000 # line should be fully cleared 1001 screen-should-contain [ 1002 . . 1003 . . 1004 .abcd↩ . 1005 .e . 1006 .╌╌╌╌╌ . 1007 ] 1008 ] 1009 1010 scenario editor-splits-wrapped-line-after-inserting-newline [ 1011 local-scope 1012 assume-screen 10/width, 5/height 1013 e:&:editor <- new-editor [abcdef], 0/left, 5/right 1014 editor-render screen, e 1015 screen-should-contain [ 1016 . . 1017 .abcd↩ . 1018 .ef . 1019 .╌╌╌╌╌ . 1020 . . 1021 ] 1022 assume-console [ 1023 left-click 2, 0 1024 press enter 1025 ] 1026 run [ 1027 editor-event-loop screen, console, e 1028 10:num/raw <- get *e, cursor-row:offset 1029 11:num/raw <- get *e, cursor-column:offset 1030 ] 1031 screen-should-contain [ 1032 . . 1033 .abcd . 1034 .ef . 1035 .╌╌╌╌╌ . 1036 ] 1037 memory-should-contain [ 1038 10 <- 2 # cursor-row 1039 11 <- 0 # cursor-column 1040 ] 1041 ] 1042 1043 scenario editor-inserts-indent-after-newline [ 1044 local-scope 1045 assume-screen 10/width, 10/height 1046 s:text <- new [ab 1047 cd 1048 ef] 1049 e:&:editor <- new-editor s, 0/left, 10/right 1050 # position cursor after 'cd' and hit 'newline' 1051 assume-console [ 1052 left-click 2, 8 1053 type [ 1054 ] 1055 ] 1056 run [ 1057 editor-event-loop screen, console, e 1058 3:num/raw <- get *e, cursor-row:offset 1059 4:num/raw <- get *e, cursor-column:offset 1060 ] 1061 # cursor should be below start of previous line 1062 memory-should-contain [ 1063 3 <- 3 # cursor row 1064 4 <- 2 # cursor column (indented) 1065 ] 1066 ] 1067 1068 scenario editor-skips-indent-around-paste [ 1069 local-scope 1070 assume-screen 10/width, 10/height 1071 s:text <- new [ab 1072 cd 1073 ef] 1074 e:&:editor <- new-editor s, 0/left, 10/right 1075 # position cursor after 'cd' and hit 'newline' surrounded by paste markers 1076 assume-console [ 1077 left-click 2, 8 1078 press 65507 # start paste 1079 press enter 1080 press 65506 # end paste 1081 ] 1082 run [ 1083 editor-event-loop screen, console, e 1084 3:num/raw <- get *e, cursor-row:offset 1085 4:num/raw <- get *e, cursor-column:offset 1086 ] 1087 # cursor should be below start of previous line 1088 memory-should-contain [ 1089 3 <- 3 # cursor row 1090 4 <- 0 # cursor column (not indented) 1091 ] 1092 ] 1093 1094 after <handle-special-key> [ 1095 { 1096 paste-start?:bool <- equal k, 65507/paste-start 1097 break-unless paste-start? 1098 *editor <- put *editor, indent?:offset, 0/false 1099 return 1/go-render 1100 } 1101 ] 1102 1103 after <handle-special-key> [ 1104 { 1105 paste-end?:bool <- equal k, 65506/paste-end 1106 break-unless paste-end? 1107 *editor <- put *editor, indent?:offset, 1/true 1108 return 1/go-render 1109 } 1110 ] 1111 1112 ## helpers 1113 1114 def draw-horizontal screen:&:screen, row:num, x:num, right:num -> screen:&:screen [ 1115 local-scope 1116 load-inputs 1117 height:num <- screen-height screen 1118 past-bottom?:bool <- greater-or-equal row, height 1119 return-if past-bottom? 1120 style:char, style-found?:bool <- next-input 1121 { 1122 break-if style-found? 1123 style <- copy 9472/horizontal 1124 } 1125 color:num, color-found?:bool <- next-input 1126 { 1127 # default color to white 1128 break-if color-found? 1129 color <- copy 245/grey 1130 } 1131 bg-color:num, bg-color-found?:bool <- next-input 1132 { 1133 break-if bg-color-found? 1134 bg-color <- copy 0/black 1135 } 1136 screen <- move-cursor screen, row, x 1137 { 1138 continue?:bool <- lesser-or-equal x, right # right is inclusive, to match editor semantics 1139 break-unless continue? 1140 print screen, style, color, bg-color 1141 x <- add x, 1 1142 loop 1143 } 1144 ]