# Editor widget: takes a string and screen coordinates, modifying them in place.
recipe main [
default-space:address:array:location <- new location:type, 30:literal
open-console
width:number <- display-width
height:number <- display-height
# draw a line
divider:number, _ <- divide-with-remainder width:number, 2:literal
draw-vertical 0:literal/screen, divider:number, 0:literal/top, height:number
# editor on the left
left:address:array:character <- new [abc]
left-editor:address:editor-data <- new-editor left:address:array:character, 0:literal/screen, 0:literal/top, 0:literal/left, 5:literal/right #divider:number/right
# editor on the right
right:address:array:character <- new [def]
new-left:number <- add divider:number/right, 1:literal
right-editor:address:editor-data <- new-editor right:address:array:character, 0:literal/screen, 0:literal/top, new-left:number, width:number
# chain
x:address:address:editor-data <- get-address left-editor:address:editor-data/deref, next-editor:offset
x:address:address:editor-data/deref <- copy right-editor:address:editor-data
# initialize focus
reset-focus left-editor:address:editor-data
cursor-row:number <- get left-editor:address:editor-data/deref, cursor-row:offset
cursor-column:number <- get left-editor:address:editor-data/deref, cursor-column:offset
move-cursor 0:literal/screen, cursor-row:number, cursor-column:number
# and we're off!
event-loop 0:literal/screen, 0:literal/events, left-editor:address:editor-data
close-console
]
scenario editor-initially-prints-string-to-screen [
assume-screen 10:literal/width, 5:literal/height
run [
1:address:array:character <- new [abc]
new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
]
screen-should-contain [
.abc .
. .
]
]
## In which we introduce the editor data structure, and show how it displays
## text to the screen.
container editor-data [
# doubly linked list of characters (head contains a special sentinel)
data:address:duplex-list
# location of top-left of screen inside data (scrolling)
top-of-screen:address:duplex-list
# location before cursor inside data
before-cursor:address:duplex-list
screen:address:screen
# raw bounds of display area on screen
top:number
left:number
bottom:number
right:number
# raw screen coordinates of cursor
cursor-row:number
cursor-column:number
# pointer to another editor, responsible for a different area of screen.
# helps organize editors in a 'chain'.
next-editor:address:editor-data
in-focus?:boolean # set for the one editor in this chain currently being edited
]
# editor:address, screen:address <- new-editor s:address:array:character, screen:address, top:number, left:number, bottom:number
# creates a new editor widget and renders its initial appearance to screen.
# top/left/right constrain the screen area available to the new editor.
# right is exclusive.
recipe new-editor [
default-space:address:array:location <- new location:type, 30:literal
s:address:array:character <- next-ingredient
screen:address <- next-ingredient
# no clipping of bounds
top:number <- next-ingredient
left:number <- next-ingredient
right:number <- next-ingredient
right:number <- subtract right:number, 1:literal
result:address:editor-data <- new editor-data:type
# initialize screen-related fields
sc:address:address:screen <- get-address result:address:editor-data/deref, screen:offset
sc:address:address:screen/deref <- copy screen:address
x:address:number <- get-address result:address:editor-data/deref, top:offset
x:address:number/deref <- copy top:number
x:address:number <- get-address result:address:editor-data/deref, left:offset
x:address:number/deref <- copy left:number
x:address:number <- get-address result:address:editor-data/deref, right:offset
x:address:number/deref <- copy right:number
# bottom = top (in case of early exit)
x:address:number <- get-address result:address:editor-data/deref, bottom:offset
x:address:number/deref <- copy top:number
# initialize cursor
x:address:number <- get-address result:address:editor-data/deref, cursor-row:offset
x:address:number/deref <- copy top:number
x:address:number <- get-address result:address:editor-data/deref, cursor-column:offset
#? $print left:number, [
#? ] #? 1
x:address:number/deref <- copy left:number
d:address:address:duplex-list <- get-address result:address:editor-data/deref, data:offset
d:address:address:duplex-list/deref <- push-duplex 167:literal/§, 0:literal/tail
y:address:address:duplex-list <- get-address result:address:editor-data/deref, before-cursor:offset
y:address:address:duplex-list/deref <- copy d:address:address:duplex-list/deref
init:address:address:duplex-list <- get-address result:address:editor-data/deref, top-of-screen:offset
init:address:address:duplex-list/deref <- copy d:address:address:duplex-list/deref
# set focus
# if using multiple editors, must call reset-focus after chaining them all
b:address:boolean <- get-address result:address:editor-data/deref, in-focus?:offset
b:address:boolean/deref <- copy 1:literal/true
#? $print d:address:address:duplex-list/deref, [
#? ] #? 1
# early exit if s is empty
reply-unless s:address:array:character, result:address:editor-data
len:number <- length s:address:array:character/deref
reply-unless len:number, result:address:editor-data
idx:number <- copy 0:literal
# now we can start appending the rest, character by character
curr:address:duplex-list <- copy init:address:address:duplex-list/deref
{
#? $print idx:number, [ vs ], len:number, [
#? ] #? 1
#? $print [append to ], curr:address:duplex-list, [
#? ] #? 1
done?:boolean <- greater-or-equal idx:number, len:number
break-if done?:boolean
c:character <- index s:address:array:character/deref, idx:number
#? $print [aa: ], c:character, [
#? ] #? 1
insert-duplex c:character, curr:address:duplex-list
# next iter
curr:address:duplex-list <- next-duplex curr:address:duplex-list
idx:number <- add idx:number, 1:literal
loop
}
# initialize cursor to top of screen
y:address:address:duplex-list <- get-address result:address:editor-data/deref, before-cursor:offset
y:address:address:duplex-list/deref <- copy init:address:address:duplex-list/deref
# perform initial rendering to screen
bottom:address:number <- get-address result:address:editor-data/deref, bottom:offset
result:address:editor-data <- render result:address:editor-data
reply result:address:editor-data
]
scenario editor-initializes-without-data [
assume-screen 5:literal/width, 3:literal/height
run [
1:address:editor-data <- new-editor 0:literal/data, screen:address, 1:literal/top, 2:literal/left, 5:literal/right
2:editor-data <- copy 1:address:editor-data/deref
]
memory-should-contain [
# 2 <- just the § sentinel
# 3 (top of screen) <- the § sentinel
# 4 (before cursor) <- the § sentinel
# 5 <- screen
6 <- 1 # top
7 <- 2 # left
8 <- 1 # bottom
9 <- 4 # right (inclusive)
10 <- 1 # cursor row
11 <- 2 # cursor column
]
screen-should-contain [
. .
. .
. .
]
]
recipe render [
default-space:address:array:location <- new location:type, 40:literal
editor:address:editor-data <- next-ingredient
#? $print [=== render
#? ] #? 2
screen:address <- get editor:address:editor-data/deref, screen:offset
top:number <- get editor:address:editor-data/deref, top:offset
left:number <- get editor:address:editor-data/deref, left:offset
screen-height:number <- screen-height screen:address
right:number <- get editor:address:editor-data/deref, right:offset
hide-screen screen:address
# traversing editor
curr:address:duplex-list <- get editor:address:editor-data/deref, top-of-screen:offset
prev:address:duplex-list <- copy curr:address:duplex-list
curr:address:duplex-list <- next-duplex curr:address:duplex-list
# traversing screen
row:number <- copy top:number
column:number <- copy left:number
cursor-row:address:number <- get-address editor:address:editor-data/deref, cursor-row:offset
cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
before-cursor:address:address:duplex-list <- get-address editor:address:editor-data/deref, before-cursor:offset
move-cursor screen:address, row:number, column:number
{
+next-character
#? $print curr:address:duplex-list, [
#? ] #? 1
break-unless curr:address:duplex-list
off-screen?:boolean <- greater-or-equal row:number, screen-height:number
break-if off-screen?:boolean
# update editor-data.before-cursor
# Doing so at the start of each iteration ensures it stays one step behind
# the current character.
{
at-cursor-row?:boolean <- equal row:number, cursor-row:address:number/deref
break-unless at-cursor-row?:boolean
at-cursor?:boolean <- equal column:number, cursor-column:address:number/deref
break-unless at-cursor?:boolean
before-cursor:address:address:duplex-list/deref <- prev-duplex curr:address:duplex-list
#? new-prev:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset #? 1
#? $print [render 0: cursor adjusted to after ], new-prev:character, [(], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [)
#? ] #? 1
}
c:character <- get curr:address:duplex-list/deref, value:offset
#? $print [rendering ], c:character, [
#? ] #? 2
{
# newline? move to left rather than 0
newline?:boolean <- equal c:character, 10:literal/newline
break-unless newline?:boolean
# adjust cursor if necessary
{
at-cursor-row?:boolean <- equal row:number, cursor-row:address:number/deref
break-unless at-cursor-row?:boolean
left-of-cursor?:boolean <- lesser-than column:number, cursor-column:address:number/deref
break-unless left-of-cursor?:boolean
cursor-column:address:number/deref <- copy column:number
before-cursor:address:address:duplex-list/deref <- prev-duplex curr:address:duplex-list
#? new-prev:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset #? 1
#? $print [render 1: cursor adjusted to after ], new-prev:character, [(], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [)
#? ] #? 1
}
# clear rest of line in this window
#? $print row:number, [ ], column:number, [ ], right:number, [
#? ] #? 1
{
done?:boolean <- greater-or-equal column:number, right:number
break-if done?:boolean
print-character screen:address, 32:literal/space
column:number <- add column:number, 1:literal
#? $print column:number, [
#? ] #? 1
loop
}
# skip to next line
row:number <- add row:number, 1:literal
column:number <- copy left:number
move-cursor screen:address, row:number, column:number
curr:address:duplex-list <- next-duplex curr:address:duplex-list
prev:address:duplex-list <- next-duplex prev:address:duplex-list
loop +next-character:label
}
{
# at right? more than one letter left in the line? wrap
at-right?:boolean <- equal column:number, right:number
break-unless at-right?:boolean
next-node:address:duplex-list <- next-duplex curr:address:duplex-list
break-unless next-node:address:duplex-list
next:character <- get next-node:address:duplex-list/deref, value:offset
next-character-is-newline?:boolean <- equal next:character, 10:literal/newline
break-if next-character-is-newline?:boolean
# wrap
print-character screen:address, 8617:literal/loop-back-to-left, 245:literal/grey
column:number <- copy left:number
row:number <- add row:number, 1:literal
move-cursor screen:address, row:number, column:number
# don't increment curr
loop +next-character:label
}
print-character screen:address, c:character
curr:address:duplex-list <- next-duplex curr:address:duplex-list
prev:address:duplex-list <- next-duplex prev:address:duplex-list
column:number <- add column:number, 1:literal
loop
}
# bottom = row
bottom:address:number <- get-address editor:address:editor-data/deref, bottom:offset
bottom:address:number/deref <- copy row:number
# is cursor to the right of the last line? move to end
{
at-cursor-row?:boolean <- equal row:number, cursor-row:address:number/deref
cursor-outside-line?:boolean <- lesser-or-equal column:number, cursor-column:address:number/deref
before-cursor-on-same-line?:boolean <- and at-cursor-row?:boolean, cursor-outside-line?:boolean
above-cursor-row?:boolean <- lesser-than row:number, cursor-row:address:number/deref
before-cursor?:boolean <- or before-cursor-on-same-line?:boolean, above-cursor-row?:boolean
break-unless before-cursor?:boolean
#? $print [pointed after all text
#? ] #? 1
cursor-row:address:number/deref <- copy row:number
cursor-column:address:number/deref <- copy column:number
#? $print [now ], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [
#? ] #? 1
before-cursor:address:address:duplex-list/deref <- copy prev:address:duplex-list
#? new-prev:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset #? 1
#? $print [render Ω: cursor adjusted to after ], new-prev:character, [(], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [)
#? ] #? 1
}
# clear rest of final line
#? $print [clearing ], row:number, [ ], column:number, [ ], right:number, [
#? ] #? 2
{
done?:boolean <- greater-or-equal column:number, right:number
break-if done?:boolean
print-character screen:address, 32:literal/space
column:number <- add column:number, 1:literal
loop
}
# update cursor
{
in-focus?:boolean <- get editor:address:editor-data/deref, in-focus?:offset
break-unless in-focus?:boolean
move-cursor screen:address, cursor-row:address:number/deref, cursor-column:address:number/deref
}
show-screen screen:address
reply editor:address:editor-data/same-as-ingredient:0
]
scenario editor-initially-prints-multiple-lines [
assume-screen 5:literal/width, 3:literal/height
run [
s:address:array:character <- new [abc
def]
new-editor s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
]
screen-should-contain [
.abc .
.def .
. .
]
]
scenario editor-initially-handles-offsets [
assume-screen 5:literal/width, 3:literal/height
run [
s:address:array:character <- new [abc]
new-editor s:address:array:character, screen:address, 0:literal/top, 1:literal/left, 5:literal/right
]
screen-should-contain [
. abc .
. .
. .
]
]
scenario editor-initially-prints-multiple-lines-at-offset [
assume-screen 5:literal/width, 3:literal/height
run [
s:address:array:character <- new [abc
def]
new-editor s:address:array:character, screen:address, 0:literal/top, 1:literal/left, 5:literal/right
]
screen-should-contain [
. abc .
. def .
. .
]
]
scenario editor-initially-wraps-long-lines [
assume-screen 5:literal/width, 3:literal/height
run [
s:address:array:character <- new [abc def]
new-editor s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
]
screen-should-contain [
.abc ↩.
.def .
. .
]
screen-should-contain-in-color, 245:literal/grey [
. ↩.
. .
. .
]
]
scenario editor-initializes-empty-text [
assume-screen 5:literal/width, 3:literal/height
run [
1:address:array:character <- new []
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
. .
. .
. .
]
memory-should-contain [
3 <- 0 # cursor row
4 <- 0 # cursor column
]
]
## handling events from the keyboard, mouse, touch screen, ...
# Takes a chain of editors (chained using editor-data.next-editor), sends each
# event from the console to each editor.
recipe event-loop [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
console:address <- next-ingredient
editor:address:editor-data <- next-ingredient
{
# send each event to each editor
e:event, console:address, found?:boolean, quit?:boolean <- read-event console:address
loop-unless found?:boolean
break-if quit?:boolean # only in tests
trace [app], [next-event]
#? $print [--- new event
#? ] #? 1
curr:address:editor-data <- copy editor:address:editor-data
{
break-unless curr:address:editor-data
handle-event screen:address, console:address, curr:address:editor-data, e:event
curr:address:editor-data <- get curr:address:editor-data/deref, next-editor:offset
loop
}
# after each non-trivial event, render all editors
curr:address:editor-data <- copy editor:address:editor-data
{
break-unless curr:address:editor-data
render curr:address:editor-data
curr:address:editor-data <- get curr:address:editor-data/deref, next-editor:offset
loop
}
# ..and position the cursor
curr:address:editor-data <- copy editor:address:editor-data
{
break-unless curr:address:editor-data
{
in-focus?:boolean <- get curr:address:editor-data/deref, in-focus?:offset
break-unless in-focus?:boolean
cursor-row:number <- get curr:address:editor-data/deref, cursor-row:offset
cursor-column:number <- get curr:address:editor-data/deref, cursor-column:offset
move-cursor screen:address, cursor-row:number, cursor-column:number
}
curr:address:editor-data <- get curr:address:editor-data/deref, next-editor:offset
loop
}
loop
}
]
recipe handle-event [
default-space:address:array:location <- new location:type, 40:literal
screen:address <- next-ingredient
console:address <- next-ingredient
editor:address:editor-data <- next-ingredient
e:event <- next-ingredient
# 'touch' event
{
t:address:touch-event <- maybe-convert e:event, touch:variant
break-unless t:address:touch-event
move-cursor-in-editor editor:address:editor-data, t:address:touch-event/deref
reply
}
# other events trigger only if this editor is in focus
#? $print [checking ], editor:address:editor-data, [
#? ] #? 1
#? x:address:boolean <- get-address editor:address:editor-data/deref, in-focus?:offset #? 1
#? $print [address of focus: ], x:address:boolean, [
#? ] #? 1
in-focus?:address:boolean <- get-address editor:address:editor-data/deref, in-focus?:offset
#? $print [ at ], in-focus?:address:boolean, [
#? ] #? 1
reply-unless in-focus?:address:boolean/deref
#? $print [in focus: ], editor:address:editor-data, [
#? ] #? 1
# typing a character
{
c:address:character <- maybe-convert e:event, text:variant
break-unless c:address:character
# unless it's a backspace
{
backspace?:boolean <- equal c:address:character/deref, 8:literal/backspace
break-unless backspace?:boolean
delete-before-cursor editor:address:editor-data
reply
}
insert-at-cursor editor:address:editor-data, c:address:character/deref
reply
}
# otherwise it's a special key to control the editor
k:address:number <- maybe-convert e:event, keycode:variant
assert k:address:number, [event was of unknown type; neither keyboard nor mouse]
d:address:duplex-list <- get editor:address:editor-data/deref, data:offset
before-cursor:address:address:duplex-list <- get-address editor:address:editor-data/deref, before-cursor:offset
cursor-row:address:number <- get-address editor:address:editor-data/deref, cursor-row:offset
cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
# arrows; update cursor-row and cursor-column, leave before-cursor to 'render'
# right arrow
{
next-character?:boolean <- equal k:address:number/deref, 65514:literal/right-arrow
break-unless next-character?:boolean
# if not at end of text
next:address:duplex-list <- next-duplex before-cursor:address:address:duplex-list/deref
break-unless next:address:duplex-list
# scan to next character
before-cursor:address:address:duplex-list/deref <- copy next:address:duplex-list
nextc:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset
# if it's a newline, move cursor to start of next row
{
at-newline?:boolean <- equal nextc:character, 10:literal/newline
break-unless at-newline?:boolean
cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
cursor-column:address:number/deref <- copy 0:literal
# todo: what happens when cursor is too far down?
screen-height:number <- screen-height screen:address
above-screen-bottom?:boolean <- lesser-than cursor-row:address:number/deref, screen-height:number
assert above-screen-bottom?:boolean, [unimplemented: moving past bottom of screen]
reply
}
# if the line wraps, move cursor to start of next row
{
# if we're at the column just before the wrap indicator
right:number <- get editor:address:editor-data/deref, right:offset
wrap-column:number <- subtract right:number, 1:literal
at-wrap?:boolean <- equal cursor-column:address:number/deref, wrap-column:number
break-unless at-wrap?:boolean
# and if character after next isn't newline
#? $print [aaa] #? 1
next-next:address:duplex-list <- next-duplex next:address:duplex-list
break-unless next-next:address:duplex-list
next-next-next:address:duplex-list <- next-duplex next-next:address:duplex-list
break-unless next-next-next:address:duplex-list
# TODO
cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
cursor-column:address:number/deref <- copy 0:literal
# todo: what happens when cursor is too far down?
screen-height:number <- screen-height screen:address
above-screen-bottom?:boolean <- lesser-than cursor-row:address:number/deref, screen-height:number
assert above-screen-bottom?:boolean, [unimplemented: moving past bottom of screen]
reply
}
# otherwise move cursor one character right
cursor-column:address:number/deref <- add cursor-column:address:number/deref, 1:literal
}
# left arrow
{
prev-character?:boolean <- equal k:address:number/deref, 65515:literal/left-arrow
break-unless prev-character?:boolean
# if not at start of text
prev:address:duplex-list <- prev-duplex before-cursor:address:address:duplex-list/deref
break-unless prev:address:duplex-list
# if cursor not at left margin, move one character left
{
at-left-margin?:boolean <- equal cursor-column:address:number/deref, 0:literal
break-if at-left-margin?:boolean
cursor-column:address:number/deref <- subtract cursor-column:address:number/deref, 1:literal
reply
}
# if at left margin, there's guaranteed to be a previous line, since we're
# not at start of text
{
# if before-cursor is at newline, figure out how long the previous line is
prevc:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset
previous-character-is-newline?:boolean <- equal prevc:character, 10:literal/newline
break-unless previous-character-is-newline?:boolean
# compute length of previous line
end-of-line:number <- previous-line-length before-cursor:address:address:duplex-list/deref, d:address:duplex-list
cursor-row:address:number/deref <- subtract cursor-row:address:number/deref, 1:literal
cursor-column:address:number/deref <- copy end-of-line:number
reply
}
# if before-cursor is not at newline, we're just at a wrapped line
assert cursor-row:address:number/deref, [unimplemented: moving cursor above top of screen]
cursor-row:address:number/deref <- subtract cursor-row:address:number/deref, 1:literal
right:number <- get editor:address:editor-data/deref, right:offset
cursor-column:address:number/deref <- subtract right:number, 1:literal # leave room for wrap icon
}
]
recipe move-cursor-in-editor [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
t:touch-event <- next-ingredient
# always reset focus to start
in-focus?:address:boolean <- get-address editor:address:editor-data/deref, in-focus?:offset
in-focus?:address:boolean/deref <- copy 0:literal/true
click-column:number <- get t:touch-event, column:offset
left:number <- get editor:address:editor-data/deref, left:offset
too-far-left?:boolean <- lesser-than click-column:number, left:number
reply-if too-far-left?:boolean
right:number <- get editor:address:editor-data/deref, right:offset
too-far-right?:boolean <- greater-than click-column:number, right:number
reply-if too-far-right?:boolean
#? $print [focus now at ], editor:address:editor-data, [
#? ] #? 2
# click on this window; gain focus
in-focus?:address:boolean/deref <- copy 1:literal/true
# update cursor
cursor-row:address:number <- get-address editor:address:editor-data/deref, cursor-row:offset
cursor-row:address:number/deref <- get t:touch-event, row:offset
cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
cursor-column:address:number/deref <- get t:touch-event, column:offset
#? $print [column is at: ], cursor-column:address:number, [
#? ] #? 1
]
recipe insert-at-cursor [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
c:character <- next-ingredient
#? $print [insert ], c:character, [
#? ] #? 1
before-cursor:address:address:duplex-list <- get-address editor:address:editor-data/deref, before-cursor:offset
d:address:duplex-list <- get editor:address:editor-data/deref, data:offset
insert-duplex c:character, before-cursor:address:address:duplex-list/deref
screen:address <- get editor:address:editor-data/deref, screen:offset
cursor-row:address:number <- get-address editor:address:editor-data/deref, cursor-row:offset
cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
# update cursor: if newline, move cursor to start of next line
# todo: bottom of screen
{
newline?:boolean <- equal c:character, 10:literal/newline
break-unless newline?:boolean
cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
cursor-column:address:number/deref <- copy 0:literal
reply
}
# if the line wraps at the cursor, move cursor to start of next row
{
# if we're at the column just before the wrap indicator
right:number <- get editor:address:editor-data/deref, right:offset
wrap-column:number <- subtract right:number, 1:literal
#? $print [wrap? ], cursor-column:address:number/deref, [ vs ], wrap-column:number, [
#? ] #? 1
at-wrap?:boolean <- greater-or-equal cursor-column:address:number/deref, wrap-column:number
break-unless at-wrap?:boolean
#? $print [wrap!
#? ] #? 1
cursor-column:address:number/deref <- subtract cursor-column:address:number/deref, wrap-column:number
cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
# todo: what happens when cursor is too far down?
screen-height:number <- screen-height screen:address
above-screen-bottom?:boolean <- lesser-than cursor-row:address:number/deref, screen-height:number
assert above-screen-bottom?:boolean, [unimplemented: typing past bottom of screen]
#? $print [return
#? ] #? 1
reply
}
# otherwise move cursor right
cursor-column:address:number/deref <- add cursor-column:address:number/deref, 1:literal
]
recipe delete-before-cursor [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
before-cursor:address:address:duplex-list <- get-address editor:address:editor-data/deref, before-cursor:offset
d:address:duplex-list <- get editor:address:editor-data/deref, data:offset
# unless already at start
at-start?:boolean <- equal before-cursor:address:address:duplex-list/deref, d:address:duplex-list
reply-if at-start?:boolean
# delete character
prev:address:duplex-list <- prev-duplex before-cursor:address:address:duplex-list/deref
remove-duplex before-cursor:address:address:duplex-list/deref
# update cursor
before-cursor:address:address:duplex-list/deref <- copy prev:address:duplex-list
cursor-column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
cursor-column:address:number/deref <- subtract cursor-column:address:number/deref, 1:literal
]
# 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 [
default-space:address:array:location <- new location:type, 30:literal
curr:address:duplex-list <- next-ingredient
start:address:duplex-list <- next-ingredient
result:number <- copy 0:literal
reply-unless curr:address:duplex-list, result:number
at-start?:boolean <- equal curr:address:duplex-list, start:address:duplex-list
reply-if at-start?:boolean, result:number
{
curr:address:duplex-list <- prev-duplex curr:address:duplex-list
break-unless curr:address:duplex-list
at-start?:boolean <- equal curr:address:duplex-list, start:address:duplex-list
break-if at-start?:boolean
c:character <- get curr:address:duplex-list/deref, value:offset
at-newline?:boolean <- equal c:character 10:literal/newline
break-if at-newline?:boolean
result:number <- add result:number, 1:literal
loop
}
reply result:number
]
scenario editor-handles-empty-event-queue [
assume-screen 10:literal/width, 5:literal/height
#? 3:number <- get screen:address/deref, num-rows:offset #? 1
#? $print [0: ], screen:address, [: ], 3:number, [
#? ] #? 1
1:address:array:character <- new [abc]
#? $print [1: ], screen:address, [
#? ] #? 1
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console []
#? $print [8: ], screen:address, [
#? ] #? 1
run [
event-loop screen:address, console:address, 2:address:editor-data
]
#? $print [9: ], screen:address, [
#? ] #? 1
screen-should-contain [
.abc .
. .
]
]
scenario editor-handles-mouse-clicks [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 1 # on the 'b'
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.abc .
. .
]
memory-should-contain [
3 <- 0 # cursor is at row 0..
4 <- 1 # ..and column 1
]
]
scenario editor-handles-mouse-clicks-outside-text [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 7 # last line, to the right of text
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
memory-should-contain [
3 <- 0 # cursor row
4 <- 3 # cursor column
]
]
scenario editor-handles-mouse-clicks-outside-text-2 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
def]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 7 # interior line, to the right of text
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
memory-should-contain [
3 <- 0 # cursor row
4 <- 3 # cursor column
]
]
scenario editor-handles-mouse-clicks-outside-text-3 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
def]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 2, 7 # below text
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
memory-should-contain [
3 <- 1 # cursor row
4 <- 3 # cursor column
]
]
scenario editor-handles-mouse-clicks-outside-column [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
# editor occupies only left half of screen
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
# click on right half of screen
left-click 3, 8
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.abc .
. .
]
memory-should-contain [
3 <- 0 # no change to cursor row
4 <- 0 # ..or column
]
]
scenario editor-inserts-characters-into-empty-editor [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new []
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
type [abc]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
. .
]
]
scenario editor-inserts-characters-at-cursor [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
type [0]
left-click 0, 2
type [d]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.0adbc .
. .
]
]
scenario editor-inserts-characters-at-cursor-2 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 5 # right of last line
type [d] # should append
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abcd .
. .
]
]
scenario editor-inserts-characters-at-cursor-3 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 3, 5 # below all text
type [d] # should append
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abcd .
. .
]
]
scenario editor-inserts-characters-at-cursor-4 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 3, 5 # below all text
type [e] # should append
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.de .
. .
]
]
scenario editor-inserts-characters-at-cursor-5 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 3, 5 # below all text
type [ef] # should append multiple characters in order
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.def .
. .
]
]
scenario editor-wraps-line-on-insert [
assume-screen 5:literal/width, 3:literal/height
1:address:array:character <- new [abcd]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
# type a letter
assume-console [
type [e]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
# no wrap yet
screen-should-contain [
.eabcd.
. .
]
# type a second letter
assume-console [
type [f]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
# now wrap
screen-should-contain [
.efab↩.
.cd .
. .
]
]
scenario editor-moves-cursor-after-inserting-characters [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
type [01]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.01abc .
. .
]
]
scenario editor-wraps-cursor-after-inserting-characters [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abcde]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
left-click 0, 4 # line is full; no wrap icon yet
type [f]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.abcd↩ .
.fe .
. .
]
memory-should-contain [
3 <- 1 # cursor row
4 <- 1 # cursor column
]
]
scenario editor-wraps-cursor-after-inserting-characters-2 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abcde]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
left-click 0, 3 # right before the wrap icon
type [f]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.abcf↩ .
.de .
. .
]
memory-should-contain [
3 <- 1 # cursor row
4 <- 0 # cursor column
]
]
scenario editor-moves-cursor-down-after-inserting-newline [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
type [0
1]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.0 .
.1abc .
. .
]
]
scenario editor-handles-backspace-key [
#? $print [=== new test
#? ] #? 1
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
#? $print [editor: ], 2:address:editor-data, [
#? ] #? 1
assume-console [
left-click 0, 1
type [«]
]
3:event/backspace <- merge 0:literal/text, 8:literal/backspace, 0:literal/dummy, 0:literal/dummy
replace-in-console 171:literal/«, 3:event/backspace
run [
event-loop screen:address, console:address, 2:address:editor-data
4:number <- get 2:address:editor-data/deref, cursor-row:offset
5:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.bc .
. .
]
memory-should-contain [
4 <- 0
5 <- 0
]
]
scenario editor-moves-cursor-right-with-key [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
press 65514 # right arrow
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.a0bc .
. .
]
]
scenario editor-moves-cursor-to-next-line-with-right-arrow [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
press 65514 # right arrow
press 65514 # right arrow
press 65514 # right arrow
press 65514 # right arrow - next line
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.0d .
. .
]
]
scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abcdef]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
left-click 0, 3
press 65514 # right arrow
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
screen-should-contain [
.abcd↩ .
.ef .
. .
]
memory-should-contain [
3 <- 1
4 <- 0
]
]
scenario editor-does-not-wrap-cursor-when-line-does-not-wrap [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abcde]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
assume-console [
left-click 0, 3
press 65514 # right arrow
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
memory-should-contain [
3 <- 0
4 <- 4
]
]
scenario editor-moves-cursor-to-next-line-with-right-arrow-at-end-of-line [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 3
press 65514 # right arrow - next line
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.0d .
. .
]
]
scenario editor-moves-cursor-left-with-key [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
assume-console [
left-click 0, 2
press 65515 # left arrow
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.a0bc .
. .
]
]
scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line [
assume-screen 10:literal/width, 5:literal/height
# initialize editor with two lines
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
# position cursor at start of second line (so there's no previous newline)
assume-console [
left-click 1, 0
press 65515 # left arrow
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc0 .
.d .
. .
]
]
scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-2 [
assume-screen 10:literal/width, 5:literal/height
# initialize editor with three lines
1:address:array:character <- new [abc
def
g]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
# position cursor further down (so there's a newline before the character at
# the cursor)
assume-console [
left-click 2, 0
press 65515 # left arrow
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.def0 .
.g .
. .
]
]
scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-3 [
assume-screen 10:literal/width, 5:literal/height
1:address:array:character <- new [abc
def
g]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
# position cursor at start of text
assume-console [
left-click 0, 0
press 65515 # left arrow should have no effect
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.0abc .
.def .
.g .
. .
]
]
scenario editor-moves-cursor-to-previous-line-with-left-arrow-at-start-of-line-4 [
assume-screen 10:literal/width, 5:literal/height
# initialize editor with text containing an empty line
1:address:array:character <- new [abc
d]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 10:literal/right
# position cursor right after empty line
assume-console [
left-click 2, 0
press 65515 # left arrow
type [0]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
]
screen-should-contain [
.abc .
.0 .
.d .
. .
]
]
scenario editor-moves-across-screen-lines-across-wrap-with-left-arrow [
assume-screen 10:literal/width, 5:literal/height
# initialize editor with text containing an empty line
1:address:array:character <- new [abcdef]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
screen-should-contain [
.abcd↩ .
.ef .
. .
]
# position cursor right after empty line
assume-console [
left-click 1, 0
press 65515 # left arrow
]
run [
event-loop screen:address, console:address, 2:address:editor-data
3:number <- get 2:address:editor-data/deref, cursor-row:offset
4:number <- get 2:address:editor-data/deref, cursor-column:offset
]
memory-should-contain [
3 <- 0 # previous row
4 <- 3 # end of wrapped line
]
]
scenario point-at-multiple-editors [
assume-screen 10:literal/width, 5:literal/height
# initialize an editor covering left half of screen
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
3:address:array:character <- new [def]
# chain new editor to it, covering the right half of the screen
4:address:address:editor-data <- get-address 2:address:editor-data/deref, next-editor:offset
4:address:address:editor-data/deref <- new-editor 3:address:array:character, screen:address, 0:literal/top, 5:literal/left, 10:literal/right
# type one letter in each of them
assume-console [
left-click 0, 1
left-click 0, 8
]
run [
event-loop screen:address, console:address, 2:address:editor-data
5:number <- get 2:address:editor-data/deref, cursor-column:offset
6:number <- get 4:address:address:editor-data/deref/deref, cursor-column:offset
]
memory-should-contain [
5 <- 1
6 <- 8
]
]
scenario editors-chain-to-cover-multiple-columns [
assume-screen 10:literal/width, 5:literal/height
# initialize an editor covering left half of screen
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
3:address:array:character <- new [def]
# chain new editor to it, covering the right half of the screen
4:address:address:editor-data <- get-address 2:address:editor-data/deref, next-editor:offset
4:address:address:editor-data/deref <- new-editor 3:address:array:character, screen:address, 0:literal/top, 5:literal/left, 10:literal/right
reset-focus 2:address:editor-data
# type one letter in each of them
assume-console [
left-click 0, 1
type [0]
left-click 0, 6
type [1]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
5:number <- get 2:address:editor-data/deref, cursor-column:offset
6:number <- get 4:address:address:editor-data/deref/deref, cursor-column:offset
]
screen-should-contain [
.a0bc d1ef .
. .
]
memory-should-contain [
5 <- 2
6 <- 7
]
# show the cursor at the right window
run [
screen:address <- print-character screen:address, 9251:literal/␣
]
screen-should-contain [
.a0bc d1␣f .
. .
]
]
scenario editor-in-focus-keeps-cursor [
assume-screen 10:literal/width, 5:literal/height
# initialize an editor covering left half of screen
1:address:array:character <- new [abc]
2:address:editor-data <- new-editor 1:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
3:address:array:character <- new [def]
# chain new editor to it, covering the right half of the screen
4:address:address:editor-data <- get-address 2:address:editor-data/deref, next-editor:offset
4:address:address:editor-data/deref <- new-editor 3:address:array:character, screen:address, 0:literal/top, 5:literal/left, 10:literal/right
# initialize cursor
run [
reset-focus 2:address:editor-data
5:number <- get 2:address:editor-data/deref, cursor-row:offset
6:number <- get 2:address:editor-data/deref, cursor-column:offset
move-cursor screen:address, 5:number, 6:number
screen:address <- print-character screen:address, 9251:literal/␣
]
# is it at the right place?
screen-should-contain [
.␣bc def .
. .
]
# now try typing a letter
assume-console [
type [z]
]
run [
event-loop screen:address, console:address, 2:address:editor-data
screen:address <- print-character screen:address, 9251:literal/␣
]
# cursor should still be right
screen-should-contain [
.z␣bc def .
. .
]
]
# set focus to first editor, reset it in later ones
recipe reset-focus [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
in-focus:address:boolean <- get-address editor:address:editor-data/deref, in-focus?:offset
in-focus:address:boolean/deref <- copy 1:literal/true
e:address:editor-data <- get editor:address:editor-data/deref, next-editor:offset
{
break-unless e:address:editor-data
#? $print [resetting focus in ], e:address:editor-data, [
#? ] #? 1
x:address:boolean <- get-address e:address:editor-data/deref, in-focus?:offset
#? $print [ at ], x:address:boolean, [
#? ] #? 1
x:address:boolean/deref <- copy 0:literal/false
e:address:editor-data <- get e:address:editor-data/deref, next-editor:offset
loop
}
]
## helpers for drawing editor borders
recipe draw-box [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
top:number <- next-ingredient
left:number <- next-ingredient
bottom:number <- next-ingredient
right:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
# top border
draw-horizontal screen:address, top:number, left:number, right:number, color:number
draw-horizontal screen:address, bottom:number, left:number, right:number, color:number
draw-vertical screen:address, left:number, top:number, bottom:number, color:number
draw-vertical screen:address, right:number, top:number, bottom:number, color:number
draw-top-left screen:address, top:number, left:number, color:number
draw-top-right screen:address, top:number, right:number, color:number
draw-bottom-left screen:address, bottom:number, left:number, color:number
draw-bottom-right screen:address, bottom:number, right:number, color:number
# position cursor inside box
move-cursor screen:address, top:number, left:number
cursor-down screen:address
cursor-right screen:address
]
recipe draw-horizontal [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
row:number <- next-ingredient
x:number <- next-ingredient
right:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
move-cursor screen:address, row:number, x:number
{
continue?:boolean <- lesser-than x:number, right:number
break-unless continue?:boolean
print-character screen:address, 9472:literal/horizontal, color:number
x:number <- add x:number, 1:literal
loop
}
]
recipe draw-vertical [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
col:number <- next-ingredient
x:number <- next-ingredient
bottom:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
{
continue?:boolean <- lesser-than x:number, bottom:number
break-unless continue?:boolean
move-cursor screen:address, x:number, col:number
print-character screen:address, 9474:literal/vertical, color:number
x:number <- add x:number, 1:literal
loop
}
]
recipe draw-top-left [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
top:number <- next-ingredient
left:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
move-cursor screen:address, top:number, left:number
print-character screen:address, 9484:literal/down-right, color:number
]
recipe draw-top-right [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
top:number <- next-ingredient
right:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
move-cursor screen:address, top:number, right:number
print-character screen:address, 9488:literal/down-left, color:number
]
recipe draw-bottom-left [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
bottom:number <- next-ingredient
left:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
move-cursor screen:address, bottom:number, left:number
print-character screen:address, 9492:literal/up-right, color:number
]
recipe draw-bottom-right [
default-space:address:array:location <- new location:type, 30:literal
screen:address <- next-ingredient
bottom:number <- next-ingredient
right:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
# default color to white
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
move-cursor screen:address, bottom:number, right:number
print-character screen:address, 9496:literal/up-left, color:number
]