## minimize prints to screen in the earlier typing layer def editor-event-loop screen:&:screen, console:&:console, editor:&:editor -> screen:&:screen, console:&:console, editor:&:editor [ local-scope load-ingredients { # looping over each (keyboard or touch) event as it occurs +next-event cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset screen <- move-cursor screen, cursor-row, cursor-column e:event, found?:bool, quit?:bool, console <- read-event console loop-unless found? break-if quit? # only in tests trace 10, [app], [next-event] # 'touch' event t:touch-event, is-touch?:bool <- maybe-convert e, touch:variant { break-unless is-touch? move-cursor-in-editor screen, editor, t loop +next-event } # keyboard events { break-if is-touch? go-render?:bool <- handle-keyboard-event screen, editor, e { break-unless go-render? screen <- editor-render screen, editor } } loop } ] # Process an event 'e' and try to minimally update the screen. # Set 'go-render?' to true to indicate the caller must perform a non-minimal update. def handle-keyboard-event screen:&:screen, editor:&:editor, e:event -> go-render?:bool, screen:&:screen, editor:&:editor [ local-scope load-ingredients return-unless editor, 0/don't-render screen-width:num <- screen-width screen screen-height:num <- screen-height screen left:num <- get *editor, left:offset right:num <- get *editor, right:offset before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset save-row:num <- copy cursor-row save-column:num <- copy cursor-column # character { c:char, is-unicode?:bool <- maybe-convert e, text:variant break-unless is-unicode? trace 10, [app], [handle-keyboard-event: special character] # exceptions for special characters go here # ignore any other special characters regular-character?:bool <- greater-or-equal c, 32/space return-unless regular-character?, 0/don't-render # otherwise type it in go-render? <- insert-at-cursor editor, c, screen return } # special key to modify the text or move the cursor k:num, is-keycode?:bool <- maybe-convert e:event, keycode:variant assert is-keycode?, [event was of unknown type; neither keyboard nor mouse] # handlers for each special key will go here return 1/go-render ] def insert-at-cursor editor:&:editor, c:char, screen:&:screen -> go-render?:bool, editor:&:editor, screen:&:screen [ local-scope load-ingredients before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset insert c, before-cursor before-cursor <- next before-cursor *editor <- put *editor, before-cursor:offset, before-cursor cursor-row:num <- get *editor, cursor-row:offset cursor-column:num <- get *editor, cursor-column:offset left:num <- get *editor, left:offset right:num <- get *editor, right:offset save-row:num <- copy cursor-row save-column:num <- copy cursor-column screen-width:num <- screen-width screen screen-height:num <- screen-height screen # occasionally we'll need to mess with the cursor # but mostly we'll just move the cursor right cursor-column <- add cursor-column, 1 *editor <- put *editor, cursor-column:offset, cursor-column next:&:duplex-list:char <- next before-cursor { # at end of all text? no need to scroll? just print the character and leave at-end?:bool <- equal next, 0/null break-unless at-end? bottom:num <- subtract screen-height, 1 at-bottom?:bool <- equal save-row, bottom at-right?:bool <- equal save-column, right overflow?:bool <- and at-bottom?, at-right? break-if overflow? move-cursor screen, save-row, save-column print screen, c return 0/don't-render } { # not at right margin? print the character and rest of line break-unless next at-right?:bool <- greater-or-equal cursor-column, screen-width break-if at-right? curr:&:duplex-list:char <- copy before-cursor move-cursor screen, save-row, save-column curr-column:num <- copy save-column { # hit right margin? give up and let caller render at-right?:bool <- greater-than curr-column, right return-if at-right?, 1/go-render break-unless curr # newline? done. currc:char <- get *curr, value:offset at-newline?:bool <- equal currc, 10/newline break-if at-newline? print screen, currc curr-column <- add curr-column, 1 curr <- next curr loop } return 0/don't-render } return 1/go-render ] scenario editor-handles-mouse-clicks [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 1, 1 # on the 'b' ] run [ editor-event-loop screen, console, e 3:num/raw <- get *e, cursor-row:offset 4:num/raw <- get *e, cursor-column:offset ] screen-should-contain [ . . .abc . .┈┈┈┈┈┈┈┈┈┈. . . ] memory-should-contain [ 3 <- 1 # cursor is at row 0.. 4 <- 1 # ..and column 1 ] check-trace-count-for-label 0, [print-character] ] scenario editor-handles-mouse-clicks-outside-text [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right $clear-trace assume-console [ left-click 1, 7 # last line, to the right of text ] run [ editor-event-loop screen, console, e 3:num/raw <- get *e, cursor-row:offset 4:num/raw <- get *e, cursor-column:offset ] memory-should-contain [ 3 <- 1 # cursor row 4 <- 3 # cursor column ] check-trace-count-for-label 0, [print-character] ] scenario editor-handles-mouse-clicks-outside-text-2 [ local-scope assume-screen 10/width, 5/height s:text <- new [abc def] e:&:editor <- new-editor s, 0/left, 10/right $clear-trace assume-console [ left-click 1, 7 # interior line, to the right of text ] run [ editor-event-loop screen, console, e 3:num/raw <- get *e, cursor-row:offset 4:num/raw <- get *e, cursor-column:offset ] memory-should-contain [ 3 <- 1 # cursor row 4 <- 3 # cursor column ] check-trace-count-for-label 0, [print-character] ] scenario editor-handles-mouse-clicks-outside-text-3 [ local-scope assume-screen 10/width, 5/height s:text <- new [abc def] e:&:editor <- new-editor s, 0/left, 10/right $clear-trace assume-console [ left-click 3, 7 # below text ] run [ editor-event-loop screen, console, e 3:num/raw <- get *e, cursor-row:offset 4:num/raw <- get *e, cursor-column:offset ] memory-should-contain [ 3 <- 2 # cursor row 4 <- 3 # cursor column ] check-trace-count-for-label 0, [print-character] ] scenario editor-handles-mouse-clicks-outside-column [ local-scope assume-screen 10/width, 5/height # editor occupies only left half of screen e:&:editor <- new-editor [abc], 0/left, 5/right editor-render screen, e $clear-trace assume-console [ # click on right half of screen left-click 3, 8 ] run [ editor-event-loop screen, console, e 3:num/raw <- get *e, cursor-row:offset 4:num/raw <- get *e, cursor-column:offset ] screen-should-contain [ . . .abc . .┈┈┈┈┈ . . . ] memory-should-contain [ 3 <- 1 # no change to cursor row 4 <- 0 # ..or column ] check-trace-count-for-label 0, [print-character] ] scenario editor-inserts-characters-into-empty-editor [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [], 0/left, 5/right editor-render screen, e $clear-trace assume-console [ type [abc] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abc . .┈┈┈┈┈ . . . ] check-trace-count-for-label 3, [print-character] ] scenario editor-inserts-characters-at-cursor [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right editor-render screen, e $clear-trace # type two letters at different places assume-console [ type [0] left-click 1, 2 type [d] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .0adbc . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 7, [print-character] # 4 for first letter, 3 for second ] scenario editor-inserts-characters-at-cursor-2 [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 1, 5 # right of last line type [d] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abcd . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 1, [print-character] ] scenario editor-inserts-characters-at-cursor-5 [ local-scope assume-screen 10/width, 5/height s:text <- new [abc d] e:&:editor <- new-editor s, 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 1, 5 # right of non-last line type [e] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abce . .d . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 1, [print-character] ] scenario editor-inserts-characters-at-cursor-3 [ local-scope assume-screen 10/width, 5/height e:&:editor <- new-editor [abc], 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 3, 5 # below all text type [d] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abcd . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 1, [print-character] ] scenario editor-inserts-characters-at-cursor-4 [ local-scope assume-screen 10/width, 5/height s:text <- new [abc d] e:&:editor <- new-editor s, 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 3, 5 # below all text type [e] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abc . .de . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 1, [print-character] ] scenario editor-inserts-characters-at-cursor-6 [ local-scope assume-screen 10/width, 5/height s:text <- new [abc d] e:&:editor <- new-editor s, 0/left, 10/right editor-render screen, e $clear-trace assume-console [ left-click 3, 5 # below all text type [ef] ] run [ editor-event-loop screen, console, e ] screen-should-contain [ . . .abc . .def . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 2, [print-character] ] after [ # if the line wraps at the cursor, move cursor to start of next row { # if either: # a) we're at the end of the line and at the column of the wrap indicator, or # b) we're not at end of line and just before the column of the wrap indicator wrap-column:num <- copy right before-wrap-column:num <- subtract wrap-column, 1 at-wrap?:bool <- greater-or-equal cursor-column, wrap-column just-before-wrap?:bool <- greater-or-equal cursor-column, before-wrap-column next:&:duplex-list:char <- next before-cursor # at end of line? next == 0 || next.value == 10/newline at-end-of-line?:bool <- equal next, 0 { break-if at-end-of-line? next-character:char <- get *next, value:offset at-end-of-line? <- equal next-character, 10/newline } # break unless ((eol? and at-wrap?) or (~eol? and just-before-wrap?)) move-cursor-to-next-line?:bool <- copy 0/false { break-if at-end-of-line? move-cursor-to-next-line? <- copy just-before-wrap? # if we're moving the cursor because it's in the middle of a wrapping # line, adjust it to left-most column potential-new-cursor-column:num <- copy left } { break-unless at-end-of-line? move-cursor-to-next-line? <- copy at-wrap? # if we're moving the cursor because it's at the end of a wrapping line, # adjust it to one past the left-most column to make room for the # newly-inserted wrap-indicator potential-new-cursor-column:num <- add left, 1/make-room-for-wrap-indicator } break-unless move-cursor-to-next-line? cursor-column <- copy potential-new-cursor-column *editor <- put *editor, cursor-column:offset, cursor-column cursor-row <- add cursor-row, 1 *editor <- put *editor, cursor-row:offset, cursor-row # if we're out of the screen, scroll down { below-screen?:bool <- greater-or-equal cursor-row, screen-height break-unless below-screen? } return 1/go-render } ] after [ { newline?:bool <- equal c, 10/newline break-unless newline? insert-new-line-and-indent editor, screen return 1/go-render } ] after [ { paste-start?:bool <- equal k, 65507/paste-start break-unless paste-start? *editor <- put *editor, indent?:offset, 0/false return 1/go-render } ] after [ { paste-end?:bool <- equal k, 65506/paste-end break-unless paste-end? *editor <- put *editor, indent?:offset, 1/true return 1/go-render } ]