Skip to content

Commit 57f9456

Browse files
committed
Window API; terminal redirections
1 parent 1f94bca commit 57f9456

File tree

9 files changed

+209
-44
lines changed

9 files changed

+209
-44
lines changed

computercraft/rproc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def proc(result):
116116
tuple3_number = fact_tuple(number, number, number)
117117
tuple2_integer = fact_tuple(integer, integer)
118118
tuple3_integer = fact_tuple(integer, integer, integer)
119+
tuple3_string = fact_tuple(string, string, string)
119120
array_integer = fact_array(integer)
120121
array_string = fact_array(string)
121122
option_integer = fact_option(integer)

computercraft/server.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
import json
33
import string
44
from aiohttp import web, WSMsgType
5+
from contextlib import asynccontextmanager
56
from traceback import print_exc
67
from os.path import join, dirname, abspath
78
from importlib import import_module
89
import argparse
910

1011
from .subapis.root import RootAPIMixin
12+
from .lua import lua_string
1113
from . import rproc
1214

1315
from .subapis.colors import ColorsAPI
@@ -148,6 +150,18 @@ async def _stop_queue(self, task_id):
148150
'event': event,
149151
})
150152

153+
@asynccontextmanager
154+
async def _create_temp_object(self, create_expr: str, finalizer_template: str = ''):
155+
fid = self._new_task_id()
156+
var = 'temp[{}]'.format(lua_string(fid))
157+
await self.eval_coro('{} = {}'.format(var, create_expr))
158+
try:
159+
yield var
160+
finally:
161+
finalizer_template += '; {e} = nil'
162+
finalizer_template = finalizer_template.lstrip(' ;')
163+
await self.eval_coro(finalizer_template.format(e=var))
164+
151165

152166
class CCApplication(web.Application):
153167
@staticmethod

computercraft/subapis/fs.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Optional, List
33

44
from .base import BaseSubAPI
5-
from ..lua import lua_string
5+
from ..lua import lua_args
66
from ..rproc import boolean, string, integer, nil, array_string, option_string, fact_scheme_dict
77

88

@@ -97,14 +97,13 @@ async def open(self, path: str, mode: str):
9797
async for line in f:
9898
...
9999
'''
100-
fid = self._cc._new_task_id()
101-
var = 'temp[{}]'.format(lua_string(fid))
102-
await self._cc.eval_coro('{} = fs.open({}, {})'.format(
103-
var, lua_string(path), lua_string(mode)))
104-
try:
100+
create_expr = '{}.open({})'.format(
101+
self.get_expr_code(),
102+
lua_args(path, mode),
103+
)
104+
fin_tpl = '{e}.close()'
105+
async with self._cc._create_temp_object(create_expr, fin_tpl) as var:
105106
yield (ReadHandle if 'r' in mode else WriteHandle)(self._cc, var)
106-
finally:
107-
await self._cc.eval_coro('{}.close(); {} = nil'.format(var, var))
108107

109108
async def find(self, wildcard: str) -> List[str]:
110109
return array_string(await self._send('find', wildcard))

computercraft/subapis/mixins.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from typing import Tuple
22

3+
from ..lua import LuaExpr
34
from ..rproc import boolean, nil, integer, tuple3_number, tuple2_integer
45

56

@@ -54,3 +55,11 @@ async def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]:
5455

5556
async def setPaletteColor(self, colorID: int, r: float, g: float, b: float):
5657
return nil(await self._send('setPaletteColor', colorID, r, g, b))
58+
59+
60+
class TermTarget(LuaExpr):
61+
def __init__(self, code):
62+
self._code = code
63+
64+
def get_expr_code(self):
65+
return self._code

computercraft/subapis/os.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Optional, List
33

44
from .base import BaseSubAPI
5-
from ..lua import LuaTable, LuaNum
5+
from ..lua import LuaNum
66
from ..rproc import nil, string, option_string, number, integer, boolean
77

88

@@ -36,7 +36,7 @@ async def getComputerLabel(self) -> Optional[str]:
3636
async def setComputerLabel(self, label: Optional[str]):
3737
return nil(await self._send('setComputerLabel', label))
3838

39-
async def run(self, environment: LuaTable, programPath: str, *args: List[str]):
39+
async def run(self, environment: dict, programPath: str, *args: List[str]):
4040
return boolean(await self._send('run', environment, programPath, *args))
4141

4242
@asynccontextmanager

computercraft/subapis/peripheral.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import Optional, List, Tuple, Any, Union
44

55
from .base import BaseSubAPI
6-
from .mixins import TermMixin
6+
from .mixins import TermMixin, TermTarget
77
from ..lua import LuaNum, lua_args
88
from ..rproc import (
99
boolean, nil, integer, string, option_integer, option_string,
@@ -118,6 +118,10 @@ async def transmit(self, channel: int, replyChannel: int, message: Any):
118118
async def isWireless(self) -> bool:
119119
return boolean(await self._send('isWireless'))
120120

121+
@property
122+
def _side(self):
123+
return self._prepend_params[0]
124+
121125
def _mk_recv_filter(self, channel):
122126
def filter(msg):
123127
if msg[0] != self._side:
@@ -159,6 +163,7 @@ async def isPresentRemote(self, peripheralName: str) -> bool:
159163

160164
async def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]:
161165
# use instead getMethodsRemote and callRemote
166+
# NOTE: you can also use peripheral.wrap(peripheralName)
162167

163168
ptype = await self.getTypeRemote(peripheralName)
164169
if ptype is None:
@@ -170,6 +175,8 @@ async def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]:
170175
'callRemote', peripheralName,
171176
)
172177

178+
# NOTE: for TermTarget use peripheral.get_term_target(peripheralName)
179+
173180

174181
class CCPrinter(BasePeripheral):
175182
async def newPage(self) -> bool:
@@ -276,3 +283,9 @@ async def wrap(self, side: str) -> Optional[BasePeripheral]:
276283
return CCWiredModem(self._cc, m, side)
277284
else:
278285
return TYPE_MAP[ptype](self._cc, m, side)
286+
287+
def get_term_target(self, side: str) -> TermTarget:
288+
return TermTarget('{}.wrap({})'.format(
289+
self.get_expr_code(),
290+
lua_args(side),
291+
))

computercraft/subapis/term.py

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
1+
from contextlib import asynccontextmanager
12
from typing import Tuple
23

34
from .base import BaseSubAPI
4-
from .mixins import TermMixin
5-
from ..rproc import nil, tuple2_integer, tuple3_number
6-
7-
8-
class CCWindow(BaseSubAPI, TermMixin):
9-
# TODO
10-
def __init__(self, cc):
11-
super().__init__(cc)
12-
13-
async def setVisible(self, visibility: bool):
14-
return nil(await self._send('setVisible', visibility))
15-
16-
async def redraw(self):
17-
return nil(await self._send('redraw'))
18-
19-
async def restoreCursor(self):
20-
return nil(await self._send('restoreCursor'))
21-
22-
async def getPosition(self) -> Tuple[int, int]:
23-
return tuple2_integer(await self._send('getPosition'))
24-
25-
async def reposition(self, x: int, y: int, width: int = None, height: int = None):
26-
return nil(await self._send('reposition', x, y, width, height))
5+
from .mixins import TermMixin, TermTarget
6+
from ..lua import lua_args
7+
from ..rproc import tuple3_number
278

289

2910
class TermAPI(BaseSubAPI, TermMixin):
3011
async def nativePaletteColor(self, colorID: int) -> Tuple[float, float, float]:
3112
return tuple3_number(await self._send('nativePaletteColor', colorID))
3213

33-
# TODO
34-
# term.redirect(target) table previous terminal object
35-
# term.current() table terminal object
36-
# term.native() table terminal object
14+
@asynccontextmanager
15+
async def redirect(self, target: TermTarget):
16+
create_expr = '{}.redirect({})'.format(
17+
self.get_expr_code(),
18+
lua_args(target),
19+
)
20+
fin_tpl = '{}.redirect({{e}})'.format(
21+
self.get_expr_code(),
22+
)
23+
async with self._cc._create_temp_object(create_expr, fin_tpl):
24+
yield
25+
26+
def get_current_target(self) -> TermTarget:
27+
return TermTarget('{}.current()'.format(self.get_expr_code()))
28+
29+
def get_native_target(self) -> TermTarget:
30+
return TermTarget('{}.native()'.format(self.get_expr_code()))

computercraft/subapis/window.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
1+
from contextlib import asynccontextmanager
2+
from typing import Tuple
3+
4+
from ..lua import lua_args
5+
from ..rproc import nil, tuple2_integer, tuple3_string
16
from .base import BaseSubAPI
2-
from ..lua import LuaTable
7+
from .mixins import TermMixin, TermTarget
8+
9+
10+
class CCWindow(BaseSubAPI, TermMixin):
11+
async def setVisible(self, visibility: bool):
12+
return nil(await self._send('setVisible', visibility))
13+
14+
async def redraw(self):
15+
return nil(await self._send('redraw'))
16+
17+
async def restoreCursor(self):
18+
return nil(await self._send('restoreCursor'))
19+
20+
async def getPosition(self) -> Tuple[int, int]:
21+
return tuple2_integer(await self._send('getPosition'))
22+
23+
async def reposition(self, x: int, y: int, width: int = None, height: int = None, parent: TermTarget = None):
24+
return nil(await self._send('reposition', x, y, width, height, parent))
25+
26+
async def getLine(self, y: int) -> Tuple[str, str, str]:
27+
return tuple3_string(await self._send('getLine', y))
28+
29+
def get_term_target(self) -> TermTarget:
30+
return TermTarget(self.get_expr_code())
331

432

533
class WindowAPI(BaseSubAPI):
34+
@asynccontextmanager
635
async def create(
7-
self, parentTerm: LuaTable, x: int, y: int, width: int, height: int, visible: bool = None,
8-
) -> LuaTable:
9-
# TODO
10-
return await self._send('create', parentTerm, x, y, width, height, visible)
36+
self, parentTerm: TermTarget, x: int, y: int, width: int, height: int, visible: bool = None,
37+
) -> CCWindow:
38+
create_expr = '{}.create({})'.format(
39+
self.get_expr_code(),
40+
lua_args(parentTerm, x, y, width, height, visible),
41+
)
42+
async with self._cc._create_temp_object(create_expr) as var:
43+
yield CCWindow(self._cc, var)

testmod.py

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22
import random
3-
from contextlib import contextmanager
3+
from contextlib import contextmanager, AsyncExitStack
44
from time import monotonic
55
from types import FunctionType
66

@@ -1632,4 +1632,106 @@ async def test_shell(api):
16321632
await api.print('Test finished successfully')
16331633

16341634

1635+
async def test_window(api):
1636+
async with api.window.create(
1637+
api.term.get_current_target(),
1638+
15, 5, 5, 5, False,
1639+
) as win:
1640+
assert await win.getPosition() == (15, 5)
1641+
assert await win.getSize() == (5, 5)
1642+
1643+
await win.setBackgroundColor(api.colors.red)
1644+
await win.clear()
1645+
await win.setVisible(True)
1646+
1647+
await asyncio.sleep(1)
1648+
1649+
await win.setVisible(False)
1650+
await win.setCursorPos(1, 1)
1651+
await win.setTextColor(api.colors.yellow)
1652+
await win.write('*********')
1653+
await win.setVisible(True)
1654+
1655+
await asyncio.sleep(1)
1656+
1657+
await api.term.clear()
1658+
1659+
await asyncio.sleep(1)
1660+
1661+
await win.redraw()
1662+
assert await win.getLine(1) == ('*****', '44444', 'eeeee')
1663+
1664+
# draws immediately
1665+
await win.reposition(21, 5)
1666+
await win.reposition(27, 5)
1667+
1668+
await api.print('Test finished successfully')
1669+
1670+
1671+
async def test_redirect_to_window(api):
1672+
w, h = await api.term.getSize()
1673+
async with AsyncExitStack() as stack:
1674+
left = await stack.enter_async_context(api.window.create(
1675+
api.term.get_current_target(),
1676+
1, 1, w // 2, h, True,
1677+
))
1678+
right = await stack.enter_async_context(api.window.create(
1679+
api.term.get_current_target(),
1680+
w // 2 + 1, 1, w // 2, h, True,
1681+
))
1682+
async with api.term.redirect(left.get_term_target()):
1683+
await api.term.setBackgroundColor(api.colors.green)
1684+
await api.term.setTextColor(api.colors.white)
1685+
await api.term.clear()
1686+
await api.term.setCursorPos(1, h // 2)
1687+
await api.print('Left part')
1688+
async with api.term.redirect(right.get_term_target()):
1689+
await api.term.setBackgroundColor(api.colors.red)
1690+
await api.term.setTextColor(api.colors.yellow)
1691+
await api.term.clear()
1692+
await api.term.setCursorPos(1, h // 2)
1693+
await api.print('Right part')
1694+
await api.print('Default terminal restored')
1695+
1696+
await api.print('Test finished successfully')
1697+
1698+
1699+
async def test_redirect_to_local_monitor(api):
1700+
side = 'left'
1701+
await step(api, f'Attach 3x3 color monitor to {side} side of computer')
1702+
1703+
async with api.term.redirect(api.peripheral.get_term_target(side)):
1704+
await api.term.setBackgroundColor(api.colors.green)
1705+
await api.term.setTextColor(api.colors.white)
1706+
await api.term.clear()
1707+
await api.term.setCursorPos(1, 1)
1708+
await api.print('Redirected to monitor')
1709+
1710+
await api.print('Test finished successfully')
1711+
1712+
1713+
async def test_redirect_to_remote_monitor(api):
1714+
side = 'back'
1715+
await step(api, f'Attach wired modem to {side} side of computer')
1716+
1717+
mod = await api.peripheral.wrap(side)
1718+
1719+
await step(api, 'Connect remote monitor using wires, activate its modem')
1720+
1721+
for name in await mod.getNamesRemote():
1722+
if await mod.getTypeRemote(name) == 'monitor':
1723+
break
1724+
else:
1725+
assert False
1726+
1727+
async with api.term.redirect(api.peripheral.get_term_target(name)):
1728+
await api.term.setBackgroundColor(api.colors.blue)
1729+
await api.term.setTextColor(api.colors.white)
1730+
await api.term.clear()
1731+
await api.term.setCursorPos(1, 1)
1732+
await api.print('Redirected to monitor')
1733+
1734+
await api.print('Test finished successfully')
1735+
1736+
16351737
# vector won't be implemented, use python equivalent

0 commit comments

Comments
 (0)