diff options
author | Kartik Agaram <vc@akkartik.com> | 2019-07-27 16:01:55 -0700 |
---|---|---|
committer | Kartik Agaram <vc@akkartik.com> | 2019-07-27 17:47:59 -0700 |
commit | 6e1eeeebfb453fa7c871869c19375ce60fbd7413 (patch) | |
tree | 539c4a3fdf1756ae79770d5c4aaf6366f1d1525e /archive/2.vm/edit/001-editor.mu | |
parent | 8846a7f85cc04b77b2fe8a67b6d317723437b00c (diff) | |
download | mu-6e1eeeebfb453fa7c871869c19375ce60fbd7413.tar.gz |
5485 - promote SubX to top-level
Diffstat (limited to 'archive/2.vm/edit/001-editor.mu')
-rw-r--r-- | archive/2.vm/edit/001-editor.mu | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/archive/2.vm/edit/001-editor.mu b/archive/2.vm/edit/001-editor.mu new file mode 100644 index 00000000..b3399dbb --- /dev/null +++ b/archive/2.vm/edit/001-editor.mu @@ -0,0 +1,464 @@ +## the basic editor data structure, and how it displays text to the screen + +# temporary main for this layer: just render the given text at the given +# screen dimensions, then stop +def main text:text [ + local-scope + load-inputs + open-console + clear-screen null/screen # non-scrolling app + e:&:editor <- new-editor text, 0/left, 5/right + render null/screen, e + wait-for-event null/console + close-console +] + +scenario editor-renders-text-to-screen [ + local-scope + assume-screen 10/width, 5/height + e:&:editor <- new-editor [abc], 0/left, 10/right + run [ + render screen, e + ] + screen-should-contain [ + # top line of screen reserved for menu + . . + .abc . + . . + ] +] + +container editor [ + # editable text: doubly linked list of characters (head contains a special sentinel) + data:&:duplex-list:char + top-of-screen:&:duplex-list:char + bottom-of-screen:&:duplex-list:char + # location before cursor inside data + before-cursor:&:duplex-list:char + + # raw bounds of display area on screen + # always displays from row 1 (leaving row 0 for a menu) and at most until bottom of screen + left:num + right:num + bottom:num + # raw screen coordinates of cursor + cursor-row:num + cursor-column:num +] + +# creates a new editor widget +# right is exclusive +def new-editor s:text, left:num, right:num -> result:&:editor [ + local-scope + load-inputs + # no clipping of bounds + right <- subtract right, 1 + result <- new editor:type + # initialize screen-related fields + *result <- put *result, left:offset, left + *result <- put *result, right:offset, right + # initialize cursor coordinates + *result <- put *result, cursor-row:offset, 1/top + *result <- put *result, cursor-column:offset, left + # initialize empty contents + init:&:duplex-list:char <- push 167/§, null + *result <- put *result, data:offset, init + *result <- put *result, top-of-screen:offset, init + *result <- put *result, before-cursor:offset, init + result <- insert-text result, s + <editor-initialization> +] + +def insert-text editor:&:editor, text:text -> editor:&:editor [ + local-scope + load-inputs + curr:&:duplex-list:char <- get *editor, data:offset + insert curr, text +] + +scenario editor-initializes-without-data [ + local-scope + assume-screen 5/width, 3/height + run [ + e:&:editor <- new-editor null/data, 2/left, 5/right + 1:editor/raw <- copy *e + ] + memory-should-contain [ + # 1,2 (data) <- just the § sentinel + # 3,4 (top of screen) <- the § sentinel + # 5 (bottom of screen) <- null since text fits on screen + 5 <- 0 + 6 <- 0 + # 7,8 (before cursor) <- the § sentinel + 9 <- 2 # left + 10 <- 4 # right (inclusive) + 11 <- 0 # bottom (not set until render) + 12 <- 1 # cursor row + 13 <- 2 # cursor column + ] + screen-should-contain [ + . . + . . + . . + ] +] + +# Assumes cursor should be at coordinates (cursor-row, cursor-column) and +# updates before-cursor to match. Might also move coordinates if they're +# outside text. +def render screen:&:screen, editor:&:editor -> last-row:num, last-column:num, screen:&:screen, editor:&:editor [ + local-scope + load-inputs + return-unless editor, 1/top, 0/left + left:num <- get *editor, left:offset + screen-height:num <- screen-height screen + right:num <- get *editor, right:offset + # traversing editor + curr:&:duplex-list:char <- get *editor, top-of-screen:offset + prev:&:duplex-list:char <- copy curr # just in case curr becomes null and we can't compute prev + curr <- next curr + # traversing screen + color:num <- copy 7/white + row:num <- copy 1/top + column:num <- copy left + cursor-row:num <- get *editor, cursor-row:offset + cursor-column:num <- get *editor, cursor-column:offset + before-cursor:&:duplex-list:char <- get *editor, before-cursor:offset + screen <- move-cursor screen, row, column + { + +next-character + break-unless curr + off-screen?:bool <- greater-or-equal row, screen-height + break-if off-screen? + # update editor.before-cursor + # Doing so at the start of each iteration ensures it stays one step behind + # the current character. + { + at-cursor-row?:bool <- equal row, cursor-row + break-unless at-cursor-row? + at-cursor?:bool <- equal column, cursor-column + break-unless at-cursor? + before-cursor <- copy prev + } + c:char <- get *curr, value:offset + <character-c-received> + { + # newline? move to left rather than 0 + newline?:bool <- equal c, 10/newline + break-unless newline? + # adjust cursor if necessary + { + at-cursor-row?:bool <- equal row, cursor-row + break-unless at-cursor-row? + left-of-cursor?:bool <- lesser-than column, cursor-column + break-unless left-of-cursor? + cursor-column <- copy column + before-cursor <- prev curr + } + # clear rest of line in this window + clear-line-until screen, right + # skip to next line + row <- add row, 1 + column <- copy left + screen <- move-cursor screen, row, column + curr <- next curr + prev <- next prev + loop +next-character + } + { + # at right? wrap. even if there's only one more letter left; we need + # room for clicking on the cursor after it. + at-right?:bool <- equal column, right + break-unless at-right? + # print wrap icon + wrap-icon:char <- copy 8617/loop-back-to-left + print screen, wrap-icon, 245/grey + column <- copy left + row <- add row, 1 + screen <- move-cursor screen, row, column + # don't increment curr + loop +next-character + } + print screen, c, color + curr <- next curr + prev <- next prev + column <- add column, 1 + loop + } + # save first character off-screen + *editor <- put *editor, bottom-of-screen:offset, curr + # is cursor to the right of the last line? move to end + { + at-cursor-row?:bool <- equal row, cursor-row + cursor-outside-line?:bool <- lesser-or-equal column, cursor-column + before-cursor-on-same-line?:bool <- and at-cursor-row?, cursor-outside-line? + above-cursor-row?:bool <- lesser-than row, cursor-row + before-cursor?:bool <- or before-cursor-on-same-line?, above-cursor-row? + break-unless before-cursor? + cursor-row <- copy row + cursor-column <- copy column + before-cursor <- copy prev + } + *editor <- put *editor, bottom:offset, row + *editor <- put *editor, cursor-row:offset, cursor-row + *editor <- put *editor, cursor-column:offset, cursor-column + *editor <- put *editor, before-cursor:offset, before-cursor + clear-line-until screen, right + row <- add row, 1 + return row, left/column +] + +def clear-screen-from screen:&:screen, row:num, column:num, left:num, right:num -> screen:&:screen [ + local-scope + load-inputs + # if it's the real screen, use the optimized primitive + { + break-if screen + clear-display-from row, column, left, right + return + } + # if not, go the slower route + screen <- move-cursor screen, row, column + clear-line-until screen, right + clear-rest-of-screen screen, row, left, right +] + +def clear-rest-of-screen screen:&:screen, row:num, left:num, right:num -> screen:&:screen [ + local-scope + load-inputs + row <- add row, 1 + # if it's the real screen, use the optimized primitive + { + break-if screen + clear-display-from row, left, left, right + return + } + screen <- move-cursor screen, row, left + screen-height:num <- screen-height screen + { + at-bottom-of-screen?:bool <- greater-or-equal row, screen-height + break-if at-bottom-of-screen? + screen <- move-cursor screen, row, left + clear-line-until screen, right + row <- add row, 1 + loop + } +] + +scenario editor-prints-multiple-lines [ + local-scope + assume-screen 5/width, 5/height + s:text <- new [abc +def] + e:&:editor <- new-editor s, 0/left, 5/right + run [ + render screen, e + ] + screen-should-contain [ + . . + .abc . + .def . + . . + ] +] + +scenario editor-handles-offsets [ + local-scope + assume-screen 5/width, 5/height + e:&:editor <- new-editor [abc], 1/left, 5/right + run [ + render screen, e + ] + screen-should-contain [ + . . + . abc . + . . + ] +] + +scenario editor-prints-multiple-lines-at-offset [ + local-scope + assume-screen 5/width, 5/height + s:text <- new [abc +def] + e:&:editor <- new-editor s, 1/left, 5/right + run [ + render screen, e + ] + screen-should-contain [ + . . + . abc . + . def . + . . + ] +] + +scenario editor-wraps-long-lines [ + local-scope + assume-screen 5/width, 5/height + e:&:editor <- new-editor [abc def], 0/left, 5/right + run [ + render screen, e + ] + screen-should-contain [ + . . + .abc ↩. + .def . + . . + ] + screen-should-contain-in-color 245/grey [ + . . + . ↩. + . . + . . + ] +] + +scenario editor-wraps-barely-long-lines [ + local-scope + assume-screen 5/width, 5/height + e:&:editor <- new-editor [abcde], 0/left, 5/right + run [ + render screen, e + ] + # still wrap, even though the line would fit. We need room to click on the + # end of the line + screen-should-contain [ + . . + .abcd↩. + .e . + . . + ] + screen-should-contain-in-color 245/grey [ + . . + . ↩. + . . + . . + ] +] + +scenario editor-with-empty-text [ + local-scope + assume-screen 5/width, 5/height + e:&:editor <- new-editor [], 0/left, 5/right + run [ + render screen, e + 3:num/raw <- get *e, cursor-row:offset + 4:num/raw <- get *e, cursor-column:offset + ] + screen-should-contain [ + . . + . . + . . + ] + memory-should-contain [ + 3 <- 1 # cursor row + 4 <- 0 # cursor column + ] +] + +# just a little color for Mu code + +scenario render-colors-comments [ + local-scope + assume-screen 5/width, 5/height + s:text <- new [abc +# de +f] + e:&:editor <- new-editor s, 0/left, 5/right + run [ + render screen, e + ] + screen-should-contain [ + . . + .abc . + .# de . + .f . + . . + ] + screen-should-contain-in-color 12/lightblue, [ + . . + . . + .# de . + . . + . . + ] + screen-should-contain-in-color 7/white, [ + . . + .abc . + . . + .f . + . . + ] +] + +after <character-c-received> [ + color <- get-color color, c +] + +# so far the previous color is all the information we need; that may change +def get-color color:num, c:char -> color:num [ + local-scope + load-inputs + color-is-white?:bool <- equal color, 7/white + # if color is white and next character is '#', switch color to blue + { + break-unless color-is-white? + starting-comment?:bool <- equal c, 35/# + break-unless starting-comment? + trace 90, [app], [switch color back to blue] + return 12/lightblue + } + # if color is blue and next character is newline, switch color to white + { + color-is-blue?:bool <- equal color, 12/lightblue + break-unless color-is-blue? + ending-comment?:bool <- equal c, 10/newline + break-unless ending-comment? + trace 90, [app], [switch color back to white] + return 7/white + } + # if color is white (no comments) and next character is '<', switch color to red + { + break-unless color-is-white? + starting-assignment?:bool <- equal c, 60/< + break-unless starting-assignment? + return 1/red + } + # if color is red and next character is space, switch color to white + { + color-is-red?:bool <- equal color, 1/red + break-unless color-is-red? + ending-assignment?:bool <- equal c, 32/space + break-unless ending-assignment? + return 7/white + } + # otherwise no change + return color +] + +scenario render-colors-assignment [ + local-scope + assume-screen 8/width, 5/height + s:text <- new [abc +d <- e +f] + e:&:editor <- new-editor s, 0/left, 8/right + run [ + render screen, e + ] + screen-should-contain [ + . . + .abc . + .d <- e . + .f . + . . + ] + screen-should-contain-in-color 1/red, [ + . . + . . + . <- . + . . + . . + ] +] |