summary refs log blame commit diff stats
path: root/ranger.py
blob: cf60433ba0a1bfee47e39e27b3d9841a1e12846e (plain) (tree)
1
2
                 
              
















                                                                              




                                                                         

               
                        
             
                                                                     
                         
    
                                                                


        
 

                                                                

                                                           
 

                                                              
                              
    
                               
 
                   
                  
                                                              




                                                               
 

              
 
#!/usr/bin/python
# coding=utf-8
# ranger: Explore your forest of files from inside your terminal
#
# Copyright (c) 2009, 2010 hut <hut@lavabit.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ----------------------------------------------------------------------------
#
# An embedded shell script. It allows you to change the directory
# of the parent shell to the last visited directory in ranger after exit.
# For more information, check out doc/cd-after-exit.txt
# To enable this, start ranger with:
#     source /path/ranger /path/ranger
"""":
if [ $1 ]; then
	ranger_exec="$1"
	shift
	cd `exec $ranger_exec --cd-after-exit $@ 3>&1 1>&2 2>&3 3>&-`
	unset ranger_exec
else
	echo "usage: source path/to/ranger.py path/to/ranger.py"
fi
return 1
"""

# Redefine the docstring, since the previous one was hijacked to
# embed a shellscript.
__doc__ = """Ranger - file browser for the unix terminal"""


# Importing the main method may fail if the ranger directory
# is neither in the same directory as this file, nor in one of
# pythons global import paths.
try:
	from ranger import main

except ImportError:
	import sys
	if '-d' not in sys.argv and '--debug' not in sys.argv:
		print("Can't import the main module.")
		print("To run an uninstalled copy of ranger,")
		print("launch ranger.py in the top directory.")
	else:
		raise

else:
	main()
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958
# 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 dotted line down the middle
  divider:number, _ <- divide-with-remainder width:number, 2:literal
  draw-vertical 0:literal/screen, divider:number, 1:literal/top, height:number, 9482:literal/vertical-dotted
  # left column consists of multiple recipes
  draw-horizontal 0:literal/screen, 10:literal, 0:literal/left, divider:number, 9480:literal/horizontal-dotted
  draw-horizontal 0:literal/screen, 20:literal, 0:literal/left, divider:number, 9480:literal/horizontal-dotted
  draw-horizontal 0:literal/screen, 30:literal, 0:literal/left, divider:number, 9480:literal/horizontal-dotted
  # right column consists of multiple sandboxes isolated from each other, but
  # with access to the recipes on the left
  column2:number <- add divider:number, 1:literal
  draw-horizontal 0:literal/screen, 3:literal, column2:number, width:number, 9473:literal/horizontal-double
  # nav bar
  button-start:number <- subtract width:number, 20:literal
  move-cursor 0:literal/screen, 0:literal/row, button-start:number/column
  run-button:address:array:character <- new [run (F9)   ]
  print-string-with-gradient-background 0:literal/screen, run-button:address:array:character, 7:literal/white, 196:literal/bright-red, 202:literal/bright-pink
  # editor on the left
  left:address:array:character <- new [abcde]
  left-editor:address:editor-data <- new-editor left:address:array:character, 0:literal/screen, 1:literal/top, 0:literal/left, divider:number/right
  # editor on the right
  right:address:array:character <- new [def]
  new-left:number <- add divider:number, 1:literal
  new-right:number <- add new-left:number, 5:literal
  right-editor:address:editor-data <- new-editor right:address:array:character, 0:literal/screen, 1: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-than 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? wrap. even if there's only one more letter left; we need
      # room for clicking on the cursor after it.
      at-right?:boolean <- equal column:number, right:number
      break-unless at-right?:boolean
      # print wrap icon
      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 [render: cursor moved to ], cursor-row:address:number/deref, [, ], cursor-column:address:number/deref, [ 
#? ] #? 1
    # line not wrapped but cursor outside bounds? wrap cursor
    {
      too-far-right?:boolean <- greater-than cursor-column:address:number/deref, right:number
      break-unless too-far-right?:boolean
      cursor-column:address:number/deref <- copy left:number
      cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
      above-screen-bottom?:boolean <- lesser-than cursor-row:address:number/deref, screen-height:number
      assert above-screen-bottom?:boolean, [unimplemented: wrapping cursor past bottom of screen]
    }
#?     $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
  }
#?   $print [clearing ], row:number, [ ], column:number, [ ], right:number, [ 
#? ] #? 2
  {
    # clear rest of current line
    done?:boolean <- greater-or-equal row:number, screen-height:number
    break-if done?:boolean
    {
      line-done?:boolean <- greater-than column:number, right:number
      break-if line-done?:boolean
      print-character screen:address, 32:literal/space
      column:number <- add column:number, 1:literal
      loop
    }
    # clear one more line just in case we just backspaced out of it
    row:number <- add row:number, 1:literal
    column:number <- copy left:number
    done?:boolean <- greater-or-equal row:number, screen-height:number
    break-if done?:boolean
    move-cursor screen:address, row:number, column:number
    {
      line-done?:boolean <- greater-or-equal column:number, right:number
      break-if line-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
    cursor-inside-right-margin?:boolean <- lesser-or-equal cursor-column:address:number/deref, right:number
    assert cursor-inside-right-margin?:boolean, [cursor outside right margin]
    cursor-inside-left-margin?:boolean <- greater-or-equal cursor-column:address:number/deref, left:number
    assert cursor-inside-left-margin?:boolean, [cursor outside left margin]
    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-initially-wraps-barely-long-lines [
  assume-screen 5:literal/width, 3:literal/height
  run [
    s:address:array:character <- new [abcde]
    new-editor s:address:array:character, screen:address, 0:literal/top, 0:literal/left, 5:literal/right
  ]
  # 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: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, 50: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
  screen-height:number <- screen-height screen:address
  top:number <- get editor:address:editor-data/deref, top:offset
  left:number <- get editor:address:editor-data/deref, left:offset
  right:number <- get editor:address:editor-data/deref, right:offset
  # arrows; update cursor-row and cursor-column, leave before-cursor to 'render'.
  # right arrow
  {
    move-to-next-character?:boolean <- equal k:address:number/deref, 65514:literal/right-arrow
    break-unless move-to-next-character?:boolean
    # if not at end of text
    old-cursor:address:duplex-list <- next-duplex before-cursor:address:address:duplex-list/deref
    break-unless old-cursor:address:duplex-list
    # scan to next character
    before-cursor:address:address:duplex-list/deref <- copy old-cursor:address:duplex-list
    # if crossed a newline, move cursor to start of next row
    {
      old-cursor-character:character <- get before-cursor:address:address:duplex-list/deref/deref, value:offset
      was-at-newline?:boolean <- equal old-cursor-character:character, 10:literal/newline
      break-unless was-at-newline?:boolean
      cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
      cursor-column:address:number/deref <- copy left:number
      # 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
      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 next character isn't newline
      new-cursor:address:duplex-list <- next-duplex old-cursor:address:duplex-list
      break-unless new-cursor:address:duplex-list
      next-character:character <- get new-cursor:address:duplex-list/deref, value:offset
      newline?:boolean <- equal next-character:character, 10:literal/newline
      break-if newline?:boolean
      cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
      cursor-column:address:number/deref <- copy left:number
      # todo: what happens when cursor is too far down?
      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
  {
    move-to-previous-character?:boolean <- equal k:address:number/deref, 65515:literal/left-arrow
    break-unless move-to-previous-character?:boolean
    # if not at start of text (before-cursor at ยง sentinel)
    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
    cursor-column:address:number/deref <- subtract right:number, 1:literal  # leave room for wrap icon
  }
  # down arrow
  {
    move-to-next-line?:boolean <- equal k:address:number/deref, 65516:literal/down-arrow
    break-unless move-to-next-line?:boolean
    # todo: support scrolling
    already-at-bottom?:boolean <- greater-or-equal cursor-row:address:number/deref, screen-height:number
    break-if already-at-bottom?:boolean
#?     $print [moving down
#? ] #? 1
    cursor-row:address:number/deref <- add cursor-row:address:number/deref, 1:literal
    # that's it; render will adjust cursor-column as necessary
  }
  # up arrow
  {
    move-to-previous-line?:boolean <- equal k:address:number/deref, 65517:literal/up-arrow
    break-unless move-to-previous-line?:boolean
    # todo: support scrolling
    already-at-top?:boolean <- lesser-or-equal cursor-row:address:number/deref, top:number
    break-if already-at-top?:boolean
#?     $print [moving up
#? ] #? 1
    cursor-row:address:number/deref <- subtract cursor-row:address:number/deref, 1:literal
    # that's it; render will adjust cursor-column as necessary
  }
]

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
  before-cursor:address:address:duplex-list/deref <- next-duplex 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
  left:number <- get editor:address:editor-data/deref, left:offset
  right:number <- get editor:address:editor-data/deref, right: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 left:number
    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
    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
#?   $print [delete-before-cursor: ], cursor-column:address:number/deref, [ 
#? ] #? 1
]

# 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 [abc]
  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 [
    .eabc .
    .     .
  ]
  # 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โ†ฉ.
    .c    .
    .     .
  ]
]

scenario editor-moves-cursor-after-inserting-characters [
  assume-screen 10:literal/width, 5:literal/height
  1:address:array:character <- new [ab]
  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 [
    .01ab      .
    .          .
  ]
]

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-moves-cursor-down-after-inserting-newline-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, 1: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-clears-previous-line-completely-after-inserting-newline [
  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
  # press just a 'newline'
  assume-console [
    type [
]
  ]
  run [
    event-loop screen:address, console:address, 2:address:editor-data
  ]
  # line should be fully cleared
  screen-should-contain [
    .          .
    .abcdโ†ฉ     .
    .e         .
    .          .
  ]
]

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-clears-last-line-on-backspace [
  assume-screen 10:literal/width, 5:literal/height
  # just one character in final 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, 5:literal/right
  assume-console [
    left-click 1, 0  # cursor at only character in final line
    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
  ]
  screen-should-contain [
    .abcd      .
    .          .
  ]
]

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-line-with-right-arrow-2 [
  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, 1: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-moves-cursor-to-next-wrapped-line-with-right-arrow-2 [
  assume-screen 10:literal/width, 5:literal/height
  # line just barely wrapping
  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
  # position cursor at last character before wrap and hit right-arrow
  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 <- 1
    4 <- 0
  ]
  # now hit right arrow again
  assume-console [
    press 65514
  ]
  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
    4 <- 1
  ]
]

scenario editor-moves-cursor-to-next-wrapped-line-with-right-arrow-3 [
  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, 1:literal/left, 6:literal/right
  assume-console [
    left-click 0, 4
    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 <- 1
  ]
]

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 editor-moves-to-previous-line-with-up-arrow [
  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 1, 1
    press 65517  # up 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 <- 1
  ]
]

scenario editor-moves-to-next-line-with-down-arrow [
  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
  # cursor starts out at (0, 0)
  assume-console [
    press 65516  # down 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
  ]
  # ..and ends at (1, 0)
  memory-should-contain [
    3 <- 1
    4 <- 0
  ]
]

scenario editor-adjusts-column-at-previous-line [
  assume-screen 10:literal/width, 5:literal/height
  1:address:array:character <- new [ab
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 1, 3
    press 65517  # up 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 <- 2
  ]
]

scenario editor-adjusts-column-at-next-line [
  assume-screen 10:literal/width, 5:literal/height
  1:address:array:character <- new [abc
de]
  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 65516  # down 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 <- 1
    4 <- 2
  ]
]

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 multiple-editors-cover-only-their-own-areas [
  assume-screen 10:literal/width, 5:literal/height
  run [
    # draw a divider
    draw-vertical screen:address, 5:literal/divider, 0:literal/top, 5:literal/height
    # initialize editors on both sides of it and chain the two
    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]
    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, 6:literal/left, 10:literal/right
  ]
  # divider isn't messed up
  screen-should-contain [
    .abc  โ”‚def .
    .     โ”‚    .
    .     โ”‚    .
    .     โ”‚    .
    .     โ”‚    .
  ]
]

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
  }
]

## Running code from the editors

recipe editor-contents [
  default-space:address:array:location <- new location:type, 30:literal
  editor:address:editor-data <- next-ingredient
  buf:address:buffer <- new-buffer 80:literal
#?   $print [buffer: ], buf:address:buffer, [ 
#? ] #? 1
  curr:address:duplex-list <- get editor:address:editor-data/deref, data:offset
  # skip ยง sentinel
  assert curr:address:duplex-list, [editor without data is illegal; must have at least a sentinel]
  curr:address:duplex-list <- next-duplex curr:address:duplex-list
  {
    break-unless curr:address:duplex-list
    c:character <- get curr:address:duplex-list/deref, value:offset
#?     $print [appending ], c:character, [ 
#? ] #? 1
    buffer-append buf:address:buffer, c:character
    curr:address:duplex-list <- next-duplex curr:address:duplex-list
    loop
  }
  result:address:array:character <- buffer-to-array buf:address:buffer
  reply result:address:array:character
]

scenario editor-provides-edited-contents [
  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
    type [def]
  ]
  run [
    event-loop screen:address, console:address, 2:address:editor-data
    3:address:array:character <- editor-contents 2:address:editor-data
    4:array:character <- copy 3:address:array:character/deref
#?     $dump-memory #? 1
  ]
  memory-should-contain [
    4:string <- [abdefc]
  ]
]

## 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
  style:character, style-found?:boolean <- next-ingredient
  {
    break-if style-found?:boolean
    style:character <- copy 9472:literal/horizontal
  }
  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, style:character, 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
  style:character, style-found?:boolean <- next-ingredient
  {
    break-if style-found?:boolean
    style:character <- copy 9474:literal/vertical
  }
  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, style:character, 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
]

recipe print-string-with-gradient-background [
  default-space:address:array:location <- new location:type, 30:literal
  x:address:screen <- next-ingredient
  s:address:array:character <- next-ingredient
  color:number <- next-ingredient
  bg-color1:number <- next-ingredient
  bg-color2:number <- next-ingredient
  len:number <- length s:address:array:character/deref
  color-range:number <- subtract bg-color2:number, bg-color1:number
  color-quantum:number <- divide-with-remainder len:number, color-range:number
#?   close-console #? 1
#?   $print len:number, [, ], color-range:number, [, ], color-quantum:number, [ 
#? ] #? 1
#?   $exit #? 1
  bg-color:number <- copy bg-color1:number
  i:number <- copy 0:literal
  {
    done?:boolean <- greater-or-equal i:number, len:number
    break-if done?:boolean
    c:character <- index s:address:array:character/deref, i:number
    print-character x:address:screen, c:character, color:number, bg-color:number
    i:number <- add i:number, 1:literal
    # if i divides color-quantum, increment bg-color
    {
      _, continue-bg-color?:boolean <- divide-with-remainder i:number, color-quantum:number
      break-if continue-bg-color?:number
      # new bg color
      bg-color:number <- add bg-color:number, 1:literal
    }
    loop
  }
  reply x:address:screen/same-as-ingredient:0
]