## special shortcuts for manipulating the editor # Some keys on the keyboard generate unicode characters, others generate # terminfo key codes. We need to modify different places in the two cases. # tab - insert two spaces scenario editor-inserts-two-spaces-on-tab [ assume-screen 10/width, 5/height # just one character in final line 1:address:shared:array:character <- new [ab cd] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 5/right assume-console [ press tab ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . . ab . .cd . ] ] after [ { tab?:boolean <- equal *c, 9/tab break-unless tab? editor, screen, go-render?:boolean <- insert-at-cursor editor, 32/space, screen editor, screen, go-render?:boolean <- insert-at-cursor editor, 32/space, screen go-render? <- copy 1/true reply } ] # backspace - delete character before cursor scenario editor-handles-backspace-key [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 1, 1 press backspace ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 4:number <- get *2:address:shared:editor-data, cursor-row:offset 5:number <- get *2:address:shared:editor-data, cursor-column:offset ] screen-should-contain [ . . .bc . .┈┈┈┈┈┈┈┈┈┈. . . ] memory-should-contain [ 4 <- 1 5 <- 0 ] check-trace-count-for-label 3, [print-character] # length of original line to overwrite ] after [ { delete-previous-character?:boolean <- equal *c, 8/backspace break-unless delete-previous-character? editor, screen, go-render?:boolean, backspaced-cell:address:shared:duplex-list:character <- delete-before-cursor editor, screen reply } ] # return values: # go-render? - whether caller needs to update the screen # backspaced-cell - value deleted (or 0 if nothing was deleted) so we can save it for undo, etc. recipe delete-before-cursor editor:address:shared:editor-data, screen:address:shared:screen -> editor:address:shared:editor-data, screen:address:shared:screen, go-render?:boolean, backspaced-cell:address:shared:duplex-list:character [ local-scope load-ingredients before-cursor:address:address:shared:duplex-list:character <- get-address *editor, before-cursor:offset data:address:shared:duplex-list:character <- get *editor, data:offset # if at start of text (before-cursor at § sentinel), return prev:address:shared:duplex-list:character <- prev *before-cursor go-render?, backspaced-cell <- copy 0/no-more-render, 0/nothing-deleted reply-unless prev trace 10, [app], [delete-before-cursor] original-row:number <- get *editor, cursor-row:offset editor, scroll?:boolean <- move-cursor-coordinates-left editor backspaced-cell:address:shared:duplex-list:character <- copy *before-cursor data <- remove *before-cursor, data # will also neatly trim next/prev pointers in backspaced-cell/*before-cursor *before-cursor <- copy prev go-render? <- copy 1/true reply-if scroll? screen-width:number <- screen-width screen cursor-row:number <- get *editor, cursor-row:offset cursor-column:number <- get *editor, cursor-column:offset # did we just backspace over a newline? same-row?:boolean <- equal cursor-row, original-row go-render? <- copy 1/true reply-unless same-row? left:number <- get *editor, left:offset right:number <- get *editor, right:offset curr:address:shared:duplex-list:character <- next *before-cursor screen <- move-cursor screen, cursor-row, cursor-column curr-column:number <- copy cursor-column { # hit right margin? give up and let caller render at-right?:boolean <- greater-or-equal curr-column, right go-render? <- copy 1/true reply-if at-right? break-unless curr # newline? done. currc:character <- get *curr, value:offset at-newline?:boolean <- equal currc, 10/newline break-if at-newline? screen <- print screen, currc curr-column <- add curr-column, 1 curr <- next curr loop } # we're guaranteed not to be at the right margin space:character <- copy 32/space screen <- print screen, space go-render? <- copy 0/false ] recipe move-cursor-coordinates-left editor:address:shared:editor-data -> editor:address:shared:editor-data, go-render?:boolean [ local-scope load-ingredients before-cursor:address:shared:duplex-list:character <- get *editor, before-cursor:offset cursor-row:address:number <- get-address *editor, cursor-row:offset cursor-column:address:number <- get-address *editor, cursor-column:offset left:number <- get *editor, left:offset # if not at left margin, move one character left { at-left-margin?:boolean <- equal *cursor-column, left break-if at-left-margin? trace 10, [app], [decrementing cursor column] *cursor-column <- subtract *cursor-column, 1 go-render? <- copy 0/false reply } # if at left margin, we must move to previous row: top-of-screen?:boolean <- equal *cursor-row, 1 # exclude menu bar go-render?:boolean <- copy 0/false { break-if top-of-screen? *cursor-row <- subtract *cursor-row, 1 } { break-unless top-of-screen? go-render? <- copy 1/true } { # case 1: if previous character was newline, figure out how long the previous line is previous-character:character <- get *before-cursor, value:offset previous-character-is-newline?:boolean <- equal previous-character, 10/newline break-unless previous-character-is-newline? # compute length of previous line trace 10, [app], [switching to previous line] d:address:shared:duplex-list:character <- get *editor, data:offset end-of-line:number <- previous-line-length before-cursor, d *cursor-column <- add left, end-of-line reply } # case 2: if previous-character was not newline, we're just at a wrapped line trace 10, [app], [wrapping to previous line] right:number <- get *editor, right:offset *cursor-column <- subtract right, 1 # leave room for wrap icon ] # takes a pointer 'curr' into the doubly-linked list and its sentinel, counts # the length of the previous line before the 'curr' pointer. recipe previous-line-length curr:address:shared:duplex-list:character, start:address:shared:duplex-list:character -> result:number [ local-scope load-ingredients result:number <- copy 0 reply-unless curr at-start?:boolean <- equal curr, start reply-if at-start? { curr <- prev curr break-unless curr at-start?:boolean <- equal curr, start break-if at-start? c:character <- get *curr, value:offset at-newline?:boolean <- equal c, 10/newline break-if at-newline? result <- add result, 1 loop } ] scenario editor-clears-last-line-on-backspace [ assume-screen 10/width, 5/height # just one character in final line 1:address:shared:array:character <- new [ab cd] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right assume-console [ left-click 2, 0 # cursor at only character in final line press backspace ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 4:number <- get *2:address:shared:editor-data, cursor-row:offset 5:number <- get *2:address:shared:editor-data, cursor-column:offset ] screen-should-contain [ . . .abcd . .┈┈┈┈┈┈┈┈┈┈. . . ] memory-should-contain [ 4 <- 1 5 <- 2 ] ] scenario editor-joins-and-wraps-lines-on-backspace [ assume-screen 10/width, 5/height # initialize editor with two long-ish but non-wrapping lines 1:address:shared:array:character <- new [abc def ghi jkl] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # position the cursor at the start of the second and hit backspace assume-console [ left-click 2, 0 press backspace ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] # resulting single line should wrap correctly screen-should-contain [ . . .abc defgh↩. .i jkl . .┈┈┈┈┈┈┈┈┈┈. . . ] ] scenario editor-wraps-long-lines-on-backspace [ assume-screen 10/width, 5/height # initialize editor in part of the screen with a long line 1:address:shared:array:character <- new [abc def ghij] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 8/right editor-render screen, 2:address:shared:editor-data # confirm that it wraps screen-should-contain [ . . .abc def↩ . . ghij . .┈┈┈┈┈┈┈┈ . ] $clear-trace # position the cursor somewhere in the middle of the top screen line and hit backspace assume-console [ left-click 1, 4 press backspace ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] # resulting single line should wrap correctly and not overflow its bounds screen-should-contain [ . . .abcdef ↩ . .ghij . .┈┈┈┈┈┈┈┈ . . . ] ] # delete - delete character at cursor scenario editor-handles-delete-key [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ press delete ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .bc . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 3, [print-character] # length of original line to overwrite $clear-trace assume-console [ press delete ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .c . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 2, [print-character] # new length to overwrite ] after [ { delete-next-character?:boolean <- equal *k, 65522/delete break-unless delete-next-character? editor, screen, go-render?:boolean, deleted-cell:address:shared:duplex-list:character <- delete-at-cursor editor, screen reply } ] recipe delete-at-cursor editor:address:shared:editor-data, screen:address:shared:screen -> editor:address:shared:editor-data, screen:address:shared:screen, go-render?:boolean, deleted-cell:address:shared:duplex-list:character [ local-scope load-ingredients before-cursor:address:address:shared:duplex-list:character <- get-address *editor, before-cursor:offset data:address:shared:duplex-list:character <- get *editor, data:offset deleted-cell:address:shared:duplex-list:character <- next *before-cursor go-render? <- copy 0/false reply-unless deleted-cell currc:character <- get *deleted-cell, value:offset data <- remove deleted-cell, data deleted-newline?:boolean <- equal currc, 10/newline go-render? <- copy 1/true reply-if deleted-newline? # wasn't a newline? render rest of line curr:address:shared:duplex-list:character <- next *before-cursor # refresh after remove above cursor-row:address:number <- get-address *editor, cursor-row:offset cursor-column:address:number <- get-address *editor, cursor-column:offset screen <- move-cursor screen, *cursor-row, *cursor-column curr-column:number <- copy *cursor-column screen-width:number <- screen-width screen { # hit right margin? give up and let caller render at-right?:boolean <- greater-or-equal curr-column, screen-width go-render? <- copy 1/true reply-if at-right? break-unless curr # newline? done. currc:character <- get *curr, value:offset at-newline?:boolean <- equal currc, 10/newline break-if at-newline? screen <- print screen, currc curr-column <- add curr-column, 1 curr <- next curr loop } # we're guaranteed not to be at the right margin space:character <- copy 32/space screen <- print screen, space go-render? <- copy 0/false ] # right arrow scenario editor-moves-cursor-right-with-key [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ press right-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .a0bc . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 3, [print-character] # 0 and following characters ] after [ { move-to-next-character?:boolean <- equal *k, 65514/right-arrow break-unless move-to-next-character? # if not at end of text next-cursor:address:shared:duplex-list:character <- next *before-cursor break-unless next-cursor # scan to next character *before-cursor <- copy next-cursor editor, go-render?:boolean <- move-cursor-coordinates-right editor, screen-height screen <- move-cursor screen, *cursor-row, *cursor-column undo-coalesce-tag:number <- copy 2/right-arrow reply } ] recipe move-cursor-coordinates-right editor:address:shared:editor-data, screen-height:number -> editor:address:shared:editor-data, go-render?:boolean [ local-scope load-ingredients before-cursor:address:shared:duplex-list:character <- get *editor before-cursor:offset cursor-row:address:number <- get-address *editor, cursor-row:offset cursor-column:address:number <- get-address *editor, cursor-column:offset left:number <- get *editor, left:offset right:number <- get *editor, right:offset # if crossed a newline, move cursor to start of next row { old-cursor-character:character <- get *before-cursor, value:offset was-at-newline?:boolean <- equal old-cursor-character, 10/newline break-unless was-at-newline? *cursor-row <- add *cursor-row, 1 *cursor-column <- copy left below-screen?:boolean <- greater-or-equal *cursor-row, screen-height # must be equal go-render? <- copy 0/false reply-unless below-screen? *cursor-row <- subtract *cursor-row, 1 # bring back into screen range go-render? <- copy 1/true reply } # if the line wraps, move cursor to start of next row { # if we're at the column just before the wrap indicator wrap-column:number <- subtract right, 1 at-wrap?:boolean <- equal *cursor-column, wrap-column break-unless at-wrap? # and if next character isn't newline next:address:shared:duplex-list:character <- next before-cursor break-unless next next-character:character <- get *next, value:offset newline?:boolean <- equal next-character, 10/newline break-if newline? *cursor-row <- add *cursor-row, 1 *cursor-column <- copy left below-screen?:boolean <- greater-or-equal *cursor-row, screen-height # must be equal reply-unless below-screen?, editor/same-as-ingredient:0, 0/no-more-render *cursor-row <- subtract *cursor-row, 1 # bring back into screen range go-render? <- copy 1/true reply } # otherwise move cursor one character right *cursor-column <- add *cursor-column, 1 go-render? <- copy 0/false ] scenario editor-moves-cursor-to-next-line-with-right-arrow [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc d] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # type right-arrow a few times to get to start of second line assume-console [ press right-arrow press right-arrow press right-arrow press right-arrow # next line ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] check-trace-count-for-label 0, [print-character] # type something and ensure it goes where it should assume-console [ type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .abc . .0d . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 2, [print-character] # new length of second line ] scenario editor-moves-cursor-to-next-line-with-right-arrow-2 [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc d] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 1/left, 10/right editor-render screen, 2:address:shared:editor-data assume-console [ press right-arrow press right-arrow press right-arrow press right-arrow # next line type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . . abc . . 0d . . ┈┈┈┈┈┈┈┈┈. . . ] ] scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abcdef] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 5/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 1, 3 press right-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] screen-should-contain [ . . .abcd↩ . .ef . .┈┈┈┈┈ . . . ] memory-should-contain [ 3 <- 2 4 <- 0 ] check-trace-count-for-label 0, [print-character] ] scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [ assume-screen 10/width, 5/height # line just barely wrapping 1:address:shared:array:character <- new [abcde] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 5/right editor-render screen, 2:address:shared:editor-data $clear-trace # position cursor at last character before wrap and hit right-arrow assume-console [ left-click 1, 3 press right-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 2 4 <- 0 ] # now hit right arrow again assume-console [ press right-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 2 4 <- 1 ] check-trace-count-for-label 0, [print-character] ] scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abcdef] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 1/left, 6/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 1, 4 press right-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] screen-should-contain [ . . . abcd↩ . . ef . . ┈┈┈┈┈ . . . ] memory-should-contain [ 3 <- 2 4 <- 1 ] check-trace-count-for-label 0, [print-character] ] scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc d] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # move to end of line, press right-arrow, type a character assume-console [ left-click 1, 3 press right-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] # new character should be in next line screen-should-contain [ . . .abc . .0d . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 2, [print-character] ] # todo: ctrl-right: next word-end # left arrow scenario editor-moves-cursor-left-with-key [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 1, 2 press left-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .a0bc . .┈┈┈┈┈┈┈┈┈┈. . . ] check-trace-count-for-label 3, [print-character] ] after [ { move-to-previous-character?:boolean <- equal *k, 65515/left-arrow break-unless move-to-previous-character? trace 10, [app], [left arrow] # if not at start of text (before-cursor at § sentinel) prev:address:shared:duplex-list:character <- prev *before-cursor go-render? <- copy 0/false reply-unless prev editor, go-render? <- move-cursor-coordinates-left editor *before-cursor <- copy prev undo-coalesce-tag:number <- copy 1/left-arrow reply } ] scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [ assume-screen 10/width, 5/height # initialize editor with two lines 1:address:shared:array:character <- new [abc d] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # position cursor at start of second line (so there's no previous newline) assume-console [ left-click 2, 0 press left-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 1 4 <- 3 ] check-trace-count-for-label 0, [print-character] ] scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [ assume-screen 10/width, 5/height # initialize editor with three lines 1:address:shared:array:character <- new [abc def g] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # position cursor further down (so there's a newline before the character at # the cursor) assume-console [ left-click 3, 0 press left-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .abc . .def0 . .g . .┈┈┈┈┈┈┈┈┈┈. ] check-trace-count-for-label 1, [print-character] # just the '0' ] scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc def g] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # position cursor at start of text, press left-arrow, then type a character assume-console [ left-click 1, 0 press left-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] # left-arrow should have had no effect screen-should-contain [ . . .0abc . .def . .g . .┈┈┈┈┈┈┈┈┈┈. ] check-trace-count-for-label 4, [print-character] # length of first line ] scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [ assume-screen 10/width, 5/height # initialize editor with text containing an empty line 1:address:shared:array:character <- new [abc d] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # position cursor right after empty line assume-console [ left-click 3, 0 press left-arrow type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .abc . .0 . .d . .┈┈┈┈┈┈┈┈┈┈. ] check-trace-count-for-label 1, [print-character] # just the '0' ] scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [ assume-screen 10/width, 5/height # initialize editor with text containing an empty line 1:address:shared:array:character <- new [abcdef] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 5/right editor-render screen, 2:address:shared:editor-data $clear-trace screen-should-contain [ . . .abcd↩ . .ef . .┈┈┈┈┈ . . . ] # position cursor right after empty line assume-console [ left-click 2, 0 press left-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 1 # previous row 4 <- 3 # end of wrapped line ] check-trace-count-for-label 0, [print-character] ] # todo: ctrl-left: previous word-start # up arrow scenario editor-moves-to-previous-line-with-up-arrow [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc def] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 2, 1 press up-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 1 4 <- 1 ] check-trace-count-for-label 0, [print-character] assume-console [ type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .a0bc . .def . .┈┈┈┈┈┈┈┈┈┈. . . ] ] after [ { move-to-previous-line?:boolean <- equal *k, 65517/up-arrow break-unless move-to-previous-line? editor, go-render? <- move-to-previous-line editor undo-coalesce-tag:number <- copy 3/up-arrow reply } ] recipe move-to-previous-line editor:address:shared:editor-data -> editor:address:shared:editor-data, go-render?:boolean [ local-scope load-ingredients cursor-row:address:number <- get-address *editor, cursor-row:offset cursor-column:address:number <- get-address *editor, cursor-column:offset before-cursor:address:address:shared:duplex-list:character <- get-address *editor, before-cursor:offset left:number <- get *editor, left:offset right:number <- get *editor, right:offset already-at-top?:boolean <- lesser-or-equal *cursor-row, 1/top { # if cursor not at top, move it break-if already-at-top? # if not at newline, move to start of line (previous newline) # then scan back another line # if either step fails, give up without modifying cursor or coordinates curr:address:shared:duplex-list:character <- copy *before-cursor { old:address:shared:duplex-list:character <- copy curr c2:character <- get *curr, value:offset at-newline?:boolean <- equal c2, 10/newline break-if at-newline? curr:address:shared:duplex-list:character <- before-previous-line curr, editor no-motion?:boolean <- equal curr, old go-render? <- copy 0/false reply-if no-motion? } { old <- copy curr curr <- before-previous-line curr, editor no-motion?:boolean <- equal curr, old go-render? <- copy 0/false reply-if no-motion? } *before-cursor <- copy curr *cursor-row <- subtract *cursor-row, 1 # scan ahead to right column or until end of line target-column:number <- copy *cursor-column *cursor-column <- copy left { done?:boolean <- greater-or-equal *cursor-column, target-column break-if done? curr:address:shared:duplex-list:character <- next *before-cursor break-unless curr currc:character <- get *curr, value:offset at-newline?:boolean <- equal currc, 10/newline break-if at-newline? # *before-cursor <- copy curr *cursor-column <- add *cursor-column, 1 loop } go-render? <- copy 0/false reply } { # if cursor already at top, scroll up break-unless already-at-top? go-render? <- copy 1/true reply } ] scenario editor-adjusts-column-at-previous-line [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [ab def] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 2, 3 press up-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 1 4 <- 2 ] check-trace-count-for-label 0, [print-character] assume-console [ type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .ab0 . .def . .┈┈┈┈┈┈┈┈┈┈. . . ] ] scenario editor-adjusts-column-at-empty-line [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [ def] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace assume-console [ left-click 2, 3 press up-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 1 4 <- 0 ] check-trace-count-for-label 0, [print-character] assume-console [ type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .0 . .def . .┈┈┈┈┈┈┈┈┈┈. . . ] ] scenario editor-moves-to-previous-line-from-left-margin [ assume-screen 10/width, 5/height # start out with three lines 1:address:shared:array:character <- new [abc def ghi] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # click on the third line and hit up-arrow, so you end up just after a newline assume-console [ left-click 3, 0 press up-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] memory-should-contain [ 3 <- 2 4 <- 0 ] check-trace-count-for-label 0, [print-character] assume-console [ type [0] ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data ] screen-should-contain [ . . .abc . .0def . .ghi . .┈┈┈┈┈┈┈┈┈┈. ] ] # down arrow scenario editor-moves-to-next-line-with-down-arrow [ assume-screen 10/width, 5/height 1:address:shared:array:character <- new [abc def] 2:address:shared:editor-data <- new-editor 1:address:shared:array:character, screen:address:shared:screen, 0/left, 10/right editor-render screen, 2:address:shared:editor-data $clear-trace # cursor starts out at (1, 0) assume-console [ press down-arrow ] run [ editor-event-loop screen:address:shared:screen, console:address:shared:console, 2:address:shared:editor-data 3:number <- get *2:address:shared:editor-data, cursor-row:offset 4:number <- get *2:address:shared:editor-data, cursor-column:offset ] # ..and ends at (2, 0) memory-should-contain [ 3 <- 2 4 <- 0 ] check-trace-count-for-label 0, [print-chara
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - subx/059read-byte.subx</title>
<meta name="Generator" content="Vim/8.0">
<meta name="plugin-version" content="vim7.4_v2">
<meta name="syntax" content="none">
<meta name="settings" content="number_lines,use_css,no_foldcolumn,expand_tabs,line_ids,prevent_copy=">
<meta name="colorscheme" content="minimal">
<style type="text/css">
<!--
pre { font-family: monospace; color: #aaaaaa; background-color: #080808; }
body { font-size:12pt; font-family: monospace; color: #aaaaaa; background-color: #080808; }
.subxS2Comment a { color:inherit; }
.subxS1Comment a { color:inherit; }
.subxComment a { color:inherit; }
.subxH2Comment a { color:inherit; }
.subxH1Comment a { color:inherit; }
* { font-size:12pt; font-size: 1em; }
.subxComment { color:#16bfff; }
.subxS2Comment { color:#4466ff; }
.LineNr { color:#444444; }
.subxS1Comment { color:#2d8cff; }
.SpecialChar { color: #ff0000; }
.subxFunction { color: #ff8700; }
.subxTest { color: #00af00; }
.subxMinorFunction { color: #875f5f; }
.Constant { color:#00a0a0; }
.CommentedCode { color: #6c6c6c; }
.subxH1Comment { color:#00ffff; }
-->
</style>

<script type='text/javascript'>
<!--

/* function to open any folds containing a jumped-to line before jumping to it */
function JumpToLine()
{
  var lineNum;
  lineNum = window.location.hash;
  lineNum = lineNum.substr(1); /* strip off '#' */

  if (lineNum.indexOf('L') == -1) {
    lineNum = 'L'+lineNum;
  }
  lineElem = document.getElementById(lineNum);
  /* Always jump to new location even if the line was hidden inside a fold, or
   * we corrected the raw number to a line ID.
   */
  if (lineElem) {
    lineElem.scrollIntoView(true);
  }
  return true;
}
if ('onhashchange' in window) {
  window.onhashchange = JumpToLine;
}

-->
</script>
</head>
<body onload='JumpToLine();'>
<a href='https://github.com/akkartik/mu/blob/master/subx/059read-byte.subx'>https://github.com/akkartik/mu/blob/master/subx/059read-byte.subx</a>
<pre id='vimCodeElement'>
<span id="L1" class="LineNr">  1 </span><span class="subxComment"># read-byte: one higher-level abstraction atop 'read'.</span>
<span id="L2" class="LineNr">  2 </span><span class="subxComment">#</span>
<span id="L3" class="LineNr">  3 </span><span class="subxComment"># There are many situations where 'read' is a lot to manage, and we need</span>
<span id="L4" class="LineNr">  4 </span><span class="subxComment"># to abstract some details away. One of them is when we want to read a file</span>
<span id="L5" class="LineNr">  5 </span><span class="subxComment"># character by character. In this situation we follow C's FILE data structure,</span>
<span id="L6" class="LineNr">  6 </span><span class="subxComment"># which manages the underlying file descriptor together with the buffer it</span>
<span id="L7" class="LineNr">  7 </span><span class="subxComment"># reads into. We call our version 'buffered-file'. Should be useful with other</span>
<span id="L8" class="LineNr">  8 </span><span class="subxComment"># primitives as well, in later layers.</span>
<span id="L9" class="LineNr">  9 </span>
<span id="L10" class="LineNr"> 10 </span>== data
<span id="L11" class="LineNr"> 11 </span>
<span id="L12" class="LineNr"> 12 </span><span class="subxComment"># The buffered file for standard input. Also illustrates the layout for</span>
<span id="L13" class="LineNr"> 13 </span><span class="subxComment"># buffered-file.</span>
<span id="L14" class="LineNr"> 14 </span>
<span id="L15" class="LineNr"> 15 </span><span class="SpecialChar">Stdin</span>:
<span id="L16" class="LineNr"> 16 </span>    <span class="subxComment"># file descriptor or (address stream)</span>
<span id="L17" class="LineNr"> 17 </span>    00 00 00 00  <span class="subxComment"># 0 = standard input</span>
<span id="L18" class="LineNr"> 18 </span>    <span class="subxComment"># current write index</span>
<span id="L19" class="LineNr"> 19 </span>    00 00 00 00
<span id="L20" class="LineNr"> 20 </span>    <span class="subxComment"># current read index</span>
<span id="L21" class="LineNr"> 21 </span>    00 00 00 00
<span id="L22" class="LineNr"> 22 </span>    <span class="subxComment"># length (8)</span>
<span id="L23" class="LineNr"> 23 </span>    08 00 00 00
<span id="L24" class="LineNr"> 24 </span>    <span class="subxComment"># data</span>
<span id="L25" class="LineNr"> 25 </span>    00 00 00 00 00 00 00 00  <span class="subxComment"># 8 bytes</span>
<span id="L26" class="LineNr"> 26 </span>
<span id="L27" class="LineNr"> 27 </span><span class="subxComment"># TODO: 8 bytes is too small. We'll need to grow the buffer for efficiency. But</span>
<span id="L28" class="LineNr"> 28 </span><span class="subxComment"># I don't want to type 1024 bytes here.</span>
<span id="L29" class="LineNr"> 29 </span>
<span id="L30" class="LineNr"> 30 </span>== code
<span id="L31" class="LineNr"> 31 </span><span class="subxComment">#   instruction                     effective address                                                   register    displacement    immediate</span>
<span id="L32" class="LineNr"> 32 </span><span class="subxS1Comment"># . op          subop               mod             rm32          base        index         scale       r32</span>
<span id="L33" class="LineNr"> 33 </span><span class="subxS1Comment"># . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes</span>
<span id="L34" class="LineNr"> 34 </span>
<span id="L35" class="LineNr"> 35 </span><span class="subxComment"># main:</span>
<span id="L36" class="LineNr"> 36 </span>    e8/call  run-tests/disp32  <span class="subxComment"># 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'.</span>
<span id="L37" class="LineNr"> 37 </span><span class="CommentedCode">#?     e8/call  test-read-byte-multiple/disp32</span>
<span id="L38" class="LineNr"> 38 </span>    <span class="subxComment"># syscall(exit, Num-test-failures)</span>
<span id="L39" class="LineNr"> 39 </span>    8b/copy                         0/mod/indirect  5/rm32/.disp32           <span class="CommentedCode"> . </span>           <span class="CommentedCode"> . </span>          3/r32/EBX   <span class="SpecialChar">Num-test-failures</span>/disp32          <span class="subxComment"># copy *Num-test-failures to EBX</span>
<span id="L40" class="LineNr"> 40 </span>    b8/copy-to-EAX  1/imm32
<span id="L41" class="LineNr"> 41 </span>    cd/syscall  0x80/imm8
<span id="L42" class="LineNr"> 42 </span>
<span id="L43" class="LineNr"> 43 </span><span class="subxComment"># return next byte value in EAX, with top 3 bytes cleared.</span>
<span id="L44" class="LineNr"> 44 </span><span class="subxComment"># On EOF, return 0xffffffff.</span>
<span id="L45" class="LineNr"> 45 </span><span class="subxFunction">read-byte</span>:  <span class="subxComment"># f : (address buffered-file) -&gt; byte-or-eof/EAX</span>
<span id="L46" class="LineNr"> 46 </span>    <span class="subxS1Comment"># . prolog</span>
<span id="L47" class="LineNr"> 47 </span>    55/push-EBP
<span id="L48" class="LineNr"> 48 </span>    89/copy                         3/mod/direct    5/rm32/EBP   <span class="CommentedCode"> . </span>         <span class="CommentedCode"> . </span>           <span class="CommentedCode"> . </span>          4/r32/ESP  <span class="CommentedCode"> . </span>             <span class="CommentedCode"> . </span>                <span class="subxComment"># copy ESP to EBP</span>
<span id="L49" class="LineNr"> 49 </span>    <span class="subxS1Comment"># . save registers</span>
<span id="L50" class="LineNr"> 50 </span>    51/push-ECX
<span id="L51" class="LineNr"> 51 </span>    56/push-ESI
<span id="L52" class="LineNr"> 52 </span>    <span class="subxComment"># ESI = f</span>
<span id="L53" class="LineNr"> 53 </span>    8b/copy                         1/mod/*+disp8   4/rm32/sib    5/base/EBP  4/index/none <span class="CommentedCode"> . </span>          6/r32/ESI   8/disp8        <span class="CommentedCode"> . </span>                <span class="subxComment"># copy *(EBP+8) to ESI</span>
<span id="L54" class="LineNr"> 54 </span>    <span class="subxComment"># ECX = f-&gt;read</span>
<span id="L55" class="LineNr"> 55 </span>    8b/copy                         1/mod/*+disp8   6/rm32/ESI   <span class="CommentedCode"> . </span>         <span class="CommentedCode"> . </span>           <span class="CommentedCode"> . </span>          1/r32/ECX   8/disp8        <span class="CommentedCode"> . </span>                <span class="subxComment"># copy *(ESI+8) to ECX</span>
<span id="L56" class="LineNr"> 56 </span>    <span class="subxComment"># if (f-&gt;read &gt;= f-&gt;write) populate stream from file</span>
<span id="L57" class="LineNr"> 57 </span>    3b/compare                      1/mod/*+disp8   6/rm32/ESI   <span class="CommentedCode"> . </span>         <span class="CommentedCode"> . </span>           <span class="CommentedCode"> . </span>          1/r32/ECX   4/disp8        <span class="CommentedCode"> . </span>                <span class="subxComment"># compare ECX with *(ESI+4)</span>
<span id="L58" class="LineNr"> 58 </span>    7c/jump-if-lesser  $read-byte:from-stream/disp8
<span id="L59" class="LineNr"> 59 </span>    <span