recipe main [
default-space:address:array:location <- new location:type, 30:literal
open-console
width:number <- display-width
height:number <- display-height
divider:number, _ <- divide-with-remainder width:number, 2:literal
draw-vertical 0:literal/screen, divider:number, 0:literal/top, height:number
in:address:array:character <- new [abcdef
def
ghi
jkl
]
editor:address:editor-data <- new-editor in:address:array:character, 0:literal/screen, 0:literal/top, 0:literal/left, divider:number/right
event-loop 0:literal/screen, 0:literal/events, editor:address:editor-data
close-console
]
scenario editor-initially-prints-string-to-screen [
assume-screen 10:literal/width, 5:literal/height
run [
s:address:array:character <- new [abc]
new-editor s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
]
screen-should-contain [
.abc .
. .
]
]
container editor-data [
data:address:duplex-list
top-of-screen:address:duplex-list
cursor:address:duplex-list
screen:address:screen
top:number
left:number
bottom:number
right:number
cursor-row:number
cursor-column:number
]
recipe new-editor [
default-space:address:array:location <- new location:type, 30:literal
s:address:array:character <- next-ingredient
screen:address <- next-ingredient
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
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
x:address:number <- get-address result:address:editor-data/deref, bottom:offset
x:address:number/deref <- copy top:number
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
x:address:number/deref <- copy left:number
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
init:address:address:duplex-list <- get-address result:address:editor-data/deref, top-of-screen:offset
init:address:address:duplex-list/deref <- copy 0:literal
c:character <- index s:address:array:character/deref, idx:number
idx:number <- add idx:number, 1:literal
init:address:address:duplex-list/deref <- push c:character, init:address:address:duplex-list/deref
curr:address:duplex-list <- copy init:address:address:duplex-list/deref
{
done?:boolean <- greater-or-equal idx:number, len:number
break-if done?:boolean
c:character <- index s:address:array:character/deref, idx:number
insert-duplex c:character, curr:address:duplex-list
curr:address:duplex-list <- next-duplex curr:address:duplex-list
idx:number <- add idx:number, 1:literal
loop
}
y:address:address:duplex-list <- get-address result:address:editor-data/deref, cursor:offset
y:address:address:duplex-list/deref <- copy init:address:address:duplex-list/deref
bottom:address:number <- get-address result:address:editor-data/deref, bottom:offset
bottom:address:number/deref, screen:address <- render result:address:editor-data, screen:address, top:number, left:number, right:number
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 <- 0
3 <- 0
4 <- 0
6 <- 1
7 <- 2
8 <- 1
9 <- 4
10 <- 1
11 <- 2
]
screen-should-contain [
. .
. .
. .
]
]
recipe render [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
screen:address <- next-ingredient
top:number <- next-ingredient
left:number <- next-ingredient
screen-height:number <- screen-height screen:address
right:number <- next-ingredient
cursor:address:duplex-list <- get editor:address:editor-data/deref, cursor:offset
curr:address:duplex-list <- get editor:address:editor-data/deref, top-of-screen:offset
row:number <- copy top:number
column:number <- copy left:number
move-cursor screen:address, row:number, column:number
{
+next-character
break-unless curr:address:duplex-list
off-screen?:boolean <- greater-or-equal row:number, screen-height:number
break-if off-screen?:boolean
{
at-cursor?:boolean <- equal curr:address:duplex-list, cursor:address:duplex-list
break-unless at-cursor?:boolean
cursor-row:number <- copy row:number
cursor-column:number <- copy column:number
}
c:character <- get curr:address:duplex-list/deref, value:offset
{
newline?:boolean <- equal c:character, 10:literal/newline
break-unless newline?:boolean
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
loop +next-character:label
}
{
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
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
loop +next-character:label
}
print-character screen:address, c:character
curr:address:duplex-list <- next-duplex curr:address:duplex-list
column:number <- add column:number, 1:literal
loop
}
move-cursor screen:address, cursor-row:number, cursor-column:number
reply row:number, screen:address/same-as-ingredient:1
]
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 [
. ↩.
. .
. .
]
]
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
{
+next-event
e:event, console:address, found?:boolean, quit?:boolean <- read-event console:address
loop-unless found?:boolean
break-if quit?:boolean
trace [app], [next-event]
{
t:address:touch-event <- maybe-convert e:event, touch:variant
break-unless t:address:touch-event
editor:address:editor-data <- move-cursor-in-editor editor:address:editor-data, t:address:touch-event
loop +next-event:label
}
c:address:character <- maybe-convert e:event, text:variant
assert c:address:character, [event was of unknown type; neither keyboard nor mouse]
loop
}
]
recipe move-cursor-in-editor [
default-space:address:array:location <- new location:type, 30:literal
editor:address:editor-data <- next-ingredient
t:address:touch-event <- next-ingredient
row:address:number <- get-address editor:address:editor-data/deref, cursor-row:offset
row:address:number/deref <- get t:address:touch-event/deref, row:offset
column:address:number <- get-address editor:address:editor-data/deref, cursor-column:offset
column:address:number/deref <- get t:address:touch-event/deref, column:offset
]
scenario editor-handles-empty-event-queue [
assume-screen 10:literal/width, 5:literal/height
assume-console []
run [
s:address:array:character <- new [abc]
editor:address:editor-data <- new-editor s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
event-loop screen:address, console:address, editor:address:editor-data
]
screen-should-contain [
.abc .
. .
]
]
scenario editor-handles-mouse-clicks [
assume-screen 10:literal/width, 5:literal/height
assume-console [
left-click 0, 1
]
run [
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
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
4 <- 1
]
]
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
{
break-if color-found?:boolean
color:number <- copy 245:literal/grey
}
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
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
{
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
{
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
{
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
{
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
{
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
{
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
]