Remove the types stuff et. al.
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2018 Simon Forman
|
||||
#
|
||||
# This file is part of Joypy.
|
||||
#
|
||||
# Joypy is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Joypy is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Joypy. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import sys
|
||||
import joy.gui.main
|
||||
|
||||
|
||||
sys.exit(joy.gui.main.main())
|
||||
@@ -1,156 +0,0 @@
|
||||
'''
|
||||
Copyright (C) 2004 - 2008 Simon Forman
|
||||
|
||||
This file is part of Xerblin.
|
||||
|
||||
Xerblin is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
'''
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
from builtins import map, object, str
|
||||
from tkinter import Listbox, SINGLE
|
||||
from tkinter.dnd import dnd_start
|
||||
from joy.utils.stack import iter_stack, list_to_stack, expression_to_string
|
||||
|
||||
|
||||
class SourceWrapper(object):
|
||||
'''
|
||||
Helper object for drag and drop.
|
||||
'''
|
||||
def __init__(self, source, widget, index=None):
|
||||
'''
|
||||
source is the object being dragged, widget is the container that's
|
||||
initialing the drag operation, and index s thu index of the item
|
||||
in the widget's model object (which presumably is a ListModel
|
||||
containing the source object.)
|
||||
'''
|
||||
self.source = source
|
||||
self.widget = widget
|
||||
self.index = index
|
||||
|
||||
def dnd_end(self, target, event):
|
||||
try:
|
||||
self.widget.clear()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
class DraggyListbox(Listbox):
|
||||
|
||||
def __init__(self, master=None, **kw):
|
||||
|
||||
# Get our stack.
|
||||
self.stack = kw.pop('items')
|
||||
|
||||
# Override any passed in selectmode.
|
||||
kw['selectmode'] = SINGLE
|
||||
|
||||
Listbox.__init__(self, master, **kw)
|
||||
|
||||
self.bind('<Button-1>', self.startDrag)
|
||||
self.bind('<ButtonRelease-1>', self.clear)
|
||||
|
||||
def clear(self, event=None):
|
||||
i = self.curselection()
|
||||
if i:
|
||||
i = int(i[0])
|
||||
self.selection_clear(i)
|
||||
|
||||
def startDrag(self, event):
|
||||
i = self.nearest(event.y)
|
||||
if i >= 0:
|
||||
self.selection_set(i)
|
||||
source = self.stack[i]
|
||||
source = SourceWrapper(source, self, i)
|
||||
event.num = 1 # Don't ask. (See Tkdnd.py)
|
||||
dnd_start(source, event)
|
||||
return "break"
|
||||
|
||||
|
||||
class ControllerListbox(DraggyListbox):
|
||||
|
||||
def __init__(self, master=None, **kw):
|
||||
DraggyListbox.__init__(self, master, **kw)
|
||||
self._dragIndex = -1
|
||||
|
||||
def dnd_accept(self, source, event):
|
||||
self.focus_force()
|
||||
return self
|
||||
|
||||
def dnd_enter(self, source, event):
|
||||
pass
|
||||
|
||||
def dnd_motion(self, source, event):
|
||||
I = self.nearest(event.y_root - self.winfo_rooty())
|
||||
if self._dragIndex >= 0:
|
||||
self.delete(self._dragIndex)
|
||||
self._dragIndex = I
|
||||
self.insert(I, '---')
|
||||
|
||||
def dnd_leave(self, source, event):
|
||||
if self._dragIndex >= 0:
|
||||
self.delete(self._dragIndex)
|
||||
self._dragIndex = -1
|
||||
|
||||
def dnd_commit(self, source, event):
|
||||
i = self._dragIndex
|
||||
|
||||
if i >= 0:
|
||||
self.delete(i)
|
||||
self._dragIndex = -1
|
||||
|
||||
try:
|
||||
if self is source.widget:
|
||||
|
||||
# Don't duplicate something by dropping it on itself.
|
||||
if i == source.index:
|
||||
return
|
||||
|
||||
# Instead, move it by removing it before the pending append.
|
||||
del self.stack[source.index]
|
||||
if i > source.index:
|
||||
i -= 1
|
||||
|
||||
self.stack.insert(i, source.source)
|
||||
|
||||
finally:
|
||||
self.clear()
|
||||
|
||||
|
||||
class StackListbox(ControllerListbox):
|
||||
|
||||
def __init__(self, world, master=None, **kw):
|
||||
ControllerListbox.__init__(self, master, **kw)
|
||||
self.world = world
|
||||
|
||||
def _update(self):
|
||||
self.delete(0, 'end')
|
||||
self.insert(0, *map(self.format, self.stack))
|
||||
|
||||
def update_stack(self, stack):
|
||||
self.stack = list(iter_stack(stack))
|
||||
self._update()
|
||||
|
||||
def dnd_commit(self, source, event):
|
||||
ControllerListbox.dnd_commit(self, source, event)
|
||||
self._update()
|
||||
self.world.stack = list_to_stack(self.stack)
|
||||
|
||||
@staticmethod
|
||||
def format(item):
|
||||
if isinstance(item, tuple):
|
||||
return '[%s]' % expression_to_string(item)
|
||||
return str(item)
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
round_to_cents == 100 * ++ floor 100 /
|
||||
|
||||
|
||||
Ordered Binary Tree datastructure functions.
|
||||
|
||||
fourth == rest_two rest first
|
||||
?fourth == [] [fourth] [] ifte
|
||||
first_two == uncons uncons pop
|
||||
ccons == cons cons
|
||||
cinf == cons infra
|
||||
rest_two == rest rest
|
||||
|
||||
_Tree_T> == [dipd] cinf
|
||||
_Tree_T< == [dipdd] cinf
|
||||
|
||||
_Tree_add_P == over [popop popop first] nullary
|
||||
_Tree_add_T> == ccons _Tree_T<
|
||||
_Tree_add_T< == ccons _Tree_T>
|
||||
_Tree_add_Ee = = pop swap roll< rest_two ccons
|
||||
_Tree_add_R == _Tree_add_P [_Tree_add_T>] [_Tree_add_Ee] [_Tree_add_T<] cmp
|
||||
_Tree_add_E == [pop] dipd Tree-new
|
||||
|
||||
_Tree_iter_order_left == [cons dip] dupdip
|
||||
_Tree_iter_order_current == [[F] dupdip] dip
|
||||
_Tree_iter_order_right == [fourth] dip i
|
||||
_Tree_iter_order_R == _Tree_iter_order_left _Tree_iter_order_current _Tree_iter_order_right
|
||||
|
||||
_Tree_get_P == over [pop popop first] nullary
|
||||
_Tree_get_T> == [fourth] dipd i
|
||||
_Tree_get_T< == [third] dipd i
|
||||
_Tree_get_E = = popop second
|
||||
_Tree_get_R == _Tree_get_P [_Tree_get_T>] [_Tree_get_E] [_Tree_get_T<] cmp
|
||||
|
||||
_Tree_delete_rightmost == [?fourth] [fourth] while
|
||||
_Tree_delete_clear_stuff = = roll> popop rest
|
||||
_Tree_delete_del == dip cons dipd swap
|
||||
_Tree_delete_W == dup _Tree_delete_rightmost first_two over
|
||||
_Tree_delete_E.0 == _Tree_delete_clear_stuff [_Tree_delete_W] _Tree_delete_del
|
||||
_Tree_delete_E == [[[pop third not] pop fourth] [[pop fourth not] pop third] [[_Tree_delete_E.0] cinf]] cond
|
||||
_Tree_delete_R0 = = over first swap dup
|
||||
_Tree_delete_R1 == cons roll> [_Tree_T>] [_Tree_delete_E] [_Tree_T<] cmp
|
||||
|
||||
Tree-new == swap [[] []] ccons
|
||||
Tree-add == [popop not] [_Tree_add_E] [] [_Tree_add_R] genrec
|
||||
Tree-iter == [not] [pop] roll< [dupdip rest_two] cons [step] genrec
|
||||
Tree-iter-order == [not] [pop] [dup third] [_Tree_iter_order_R] genrec
|
||||
Tree-get == [pop not] swap [] [_Tree_get_R] genrec
|
||||
Tree-delete == [pop not] [pop] [_Tree_delete_R0] [_Tree_delete_R1] genrec
|
||||
@@ -1,4 +0,0 @@
|
||||
Joypy - Copyright © 2018 Simon Forman
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details right-click "warranty". This is free software, and you are welcome to redistribute it under certain conditions; right-click "sharing" for details. Right-click on these commands to see docs on UI commands: key_bindings mouse_bindings
|
||||
|
||||
<-
|
||||
@@ -1,59 +0,0 @@
|
||||
reset_log words mouse_bindings key_bindings
|
||||
|
||||
Stack Chatter
|
||||
|
||||
dup dupd dupdd over tuck
|
||||
pop popd popdd popop popopd popopdd
|
||||
swap roll< roll> rolldown rollup
|
||||
unit clear
|
||||
|
||||
Math
|
||||
|
||||
add + sub - mul * truediv / mod %
|
||||
div divmod floor pm
|
||||
abs sqr sqrt neg pow
|
||||
max min sum average product
|
||||
pred -- succ ++ lshift << rshift >>
|
||||
|
||||
Logic
|
||||
|
||||
ge gt eq le lt ne
|
||||
< <= = >= > != <>
|
||||
and & or not xor ^
|
||||
bool truthy ?
|
||||
|
||||
Combinators
|
||||
|
||||
i x b infra dip dipd dipdd dupdip dupdipd
|
||||
cleave fork app1 app2 app3 map pam
|
||||
nullary unary binary ternary
|
||||
|
||||
Control Flow
|
||||
|
||||
branch cond ifte choice
|
||||
loop while genrec primrec
|
||||
make_generator
|
||||
|
||||
List Manipulation
|
||||
|
||||
enstacken disenstacken stack unstack
|
||||
first first_two second third fourth rest rrest
|
||||
flatten drop take reverse select zip
|
||||
size sort shunt getitem
|
||||
step step_zero times
|
||||
cons ccons uncons swons unswons
|
||||
concat unique
|
||||
remove
|
||||
at of pick
|
||||
unquoted quoted
|
||||
|
||||
Misc
|
||||
|
||||
down_to_zero cmp gcd help id
|
||||
least_fraction parse quoted
|
||||
range range_to_zero
|
||||
reset_log show_log
|
||||
run
|
||||
stuncons stununcons
|
||||
swaack
|
||||
void
|
||||
@@ -1 +0,0 @@
|
||||
(t.
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
[key bindings]
|
||||
<F5> = swap
|
||||
<F6> = dup
|
||||
<Shift-F5> = roll<
|
||||
<Shift-F6> = roll>
|
||||
<F7> = over
|
||||
<Shift-F7> = tuck
|
||||
<F8> = parse
|
||||
<F12> = words
|
||||
<F1> = reset_log show_log
|
||||
<Escape> = clear reset_log show_log
|
||||
<Control-Delete> = pop
|
||||
<Control-i> = i
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
'''
|
||||
Utility module to help with setting up the initial contents of the
|
||||
JOY_HOME directory.
|
||||
|
||||
These contents are kept in this Python module as a base64-encoded zip
|
||||
file, so you can just do, e.g.:
|
||||
|
||||
import init_joy_home
|
||||
init_joy_home.initialize(JOY_HOME)
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
import base64, os, io, zipfile
|
||||
|
||||
|
||||
def initialize(joy_home):
|
||||
Z.extractall(joy_home)
|
||||
|
||||
|
||||
def create_data(from_dir='./default_joy_home'):
|
||||
f = io.StringIO()
|
||||
z = zipfile.ZipFile(f, mode='w')
|
||||
for fn in os.listdir(from_dir):
|
||||
from_fn = os.path.join(from_dir, fn)
|
||||
z.write(from_fn, fn)
|
||||
z.close()
|
||||
return base64.encodestring(f.getvalue())
|
||||
|
||||
|
||||
Z = zipfile.ZipFile(io.BytesIO(base64.decodestring(b'''\
|
||||
UEsDBBQAAAAAAJKh9Uw/yHAgFQQAABUEAAALAAAAc2NyYXRjaC50eHRyZXNldF9sb2cgd29yZHMg
|
||||
bW91c2VfYmluZGluZ3Mga2V5X2JpbmRpbmdzCgpTdGFjayBDaGF0dGVyCgogZHVwIGR1cGQgZHVw
|
||||
ZGQgb3ZlciB0dWNrCiBwb3AgcG9wZCBwb3BkZCBwb3BvcCBwb3BvcGQgcG9wb3BkZAogc3dhcCBy
|
||||
b2xsPCByb2xsPiByb2xsZG93biByb2xsdXAgCiB1bml0IGNsZWFyCgpNYXRoCgogYWRkICsgc3Vi
|
||||
IC0gbXVsICogdHJ1ZWRpdiAvIG1vZCAlCiBkaXYgZGl2bW9kIGZsb29yIHBtCiBhYnMgc3FyIHNx
|
||||
cnQgbmVnIHBvdwogbWF4IG1pbiBzdW0gYXZlcmFnZSBwcm9kdWN0CiBwcmVkIC0tIHN1Y2MgKysg
|
||||
bHNoaWZ0IDw8IHJzaGlmdCA+PgoKTG9naWMKCiBnZSBndCBlcSBsZSBsdCBuZQogIDwgPD0gPSAg
|
||||
Pj0gPiAgIT0gPD4KIGFuZCAmIG9yIG5vdCB4b3IgXgogYm9vbCB0cnV0aHkgPwoKQ29tYmluYXRv
|
||||
cnMKCiBpIHggYiBpbmZyYSBkaXAgZGlwZCBkaXBkZCBkdXBkaXAgZHVwZGlwZAogY2xlYXZlIGZv
|
||||
cmsgYXBwMSBhcHAyIGFwcDMgbWFwIHBhbQogbnVsbGFyeSB1bmFyeSBiaW5hcnkgdGVybmFyeSAK
|
||||
CkNvbnRyb2wgRmxvdwoKIGJyYW5jaCBjb25kIGlmdGUgY2hvaWNlCiBsb29wIHdoaWxlIGdlbnJl
|
||||
YyBwcmltcmVjCiBtYWtlX2dlbmVyYXRvcgoKTGlzdCBNYW5pcHVsYXRpb24KCiBlbnN0YWNrZW4g
|
||||
ZGlzZW5zdGFja2VuIHN0YWNrIHVuc3RhY2sKIGZpcnN0IGZpcnN0X3R3byBzZWNvbmQgdGhpcmQg
|
||||
Zm91cnRoIHJlc3QgcnJlc3QKIGZsYXR0ZW4gZHJvcCB0YWtlIHJldmVyc2Ugc2VsZWN0IHppcAog
|
||||
c2l6ZSBzb3J0IHNodW50IGdldGl0ZW0KIHN0ZXAgc3RlcF96ZXJvIHRpbWVzIAogY29ucyBjY29u
|
||||
cyB1bmNvbnMgc3dvbnMgdW5zd29ucwogY29uY2F0IHVuaXF1ZQogcmVtb3ZlCiBhdCBvZiBwaWNr
|
||||
CiB1bnF1b3RlZCBxdW90ZWQKCk1pc2MKCiBkb3duX3RvX3plcm8gY21wIGdjZCBoZWxwIGlkIAog
|
||||
bGVhc3RfZnJhY3Rpb24gcGFyc2UgcXVvdGVkCiByYW5nZSByYW5nZV90b196ZXJvCiByZXNldF9s
|
||||
b2cgIHNob3dfbG9nCiBydW4gCiBzdHVuY29ucyBzdHVudW5jb25zCiBzd2FhY2sgCiB2b2lkICAg
|
||||
ICAKUEsDBBQAAAAAAEBB9Uzn5GRHUQEAAFEBAAAHAAAAbG9nLnR4dEpveXB5IC0gQ29weXJpZ2h0
|
||||
IMKpIDIwMTggU2ltb24gRm9ybWFuClRoaXMgcHJvZ3JhbSBjb21lcyB3aXRoIEFCU09MVVRFTFkg
|
||||
Tk8gV0FSUkFOVFk7IGZvciBkZXRhaWxzIHJpZ2h0LWNsaWNrICJ3YXJyYW50eSIuIFRoaXMgaXMg
|
||||
ZnJlZSBzb2Z0d2FyZSwgYW5kIHlvdSBhcmUgd2VsY29tZSB0byByZWRpc3RyaWJ1dGUgaXQgdW5k
|
||||
ZXIgY2VydGFpbiBjb25kaXRpb25zOyByaWdodC1jbGljayAic2hhcmluZyIgZm9yIGRldGFpbHMu
|
||||
IFJpZ2h0LWNsaWNrIG9uIHRoZXNlIGNvbW1hbmRzIHRvIHNlZSBkb2NzIG9uIFVJIGNvbW1hbmRz
|
||||
OiBrZXlfYmluZGluZ3MgbW91c2VfYmluZGluZ3MKCiA8LQpQSwMEFAAAAAAAQUH1THd/ml4DAAAA
|
||||
AwAAAAwAAABzdGFjay5waWNrbGUodC5QSwMEFAAAAAAApqH1TNL4a8/sAAAA7AAAAAsAAAB0aHVu
|
||||
LmNvbmZpZwpba2V5IGJpbmRpbmdzXQo8RjU+ID0gc3dhcAo8RjY+ID0gZHVwCjxTaGlmdC1GNT4g
|
||||
PSByb2xsPAo8U2hpZnQtRjY+ID0gcm9sbD4KPEY3PiA9IG92ZXIKPFNoaWZ0LUY3PiA9IHR1Y2sK
|
||||
PEY4PiA9IHBhcnNlCjxGMTI+ID0gd29yZHMKPEYxPiA9IHJlc2V0X2xvZyBzaG93X2xvZwo8RXNj
|
||||
YXBlPiA9IGNsZWFyIHJlc2V0X2xvZyBzaG93X2xvZwo8Q29udHJvbC1EZWxldGU+ID0gcG9wCjxD
|
||||
b250cm9sLWk+ID0gaQoKUEsDBBQAAAAAAHOh9UyQUJ4KOgcAADoHAAAPAAAAZGVmaW5pdGlvbnMu
|
||||
dHh0CnJvdW5kX3RvX2NlbnRzID09IDEwMCAqICsrIGZsb29yIDEwMCAvCgoKT3JkZXJlZCBCaW5h
|
||||
cnkgVHJlZSBkYXRhc3RydWN0dXJlIGZ1bmN0aW9ucy4KCmZvdXJ0aCA9PSByZXN0X3R3byByZXN0
|
||||
IGZpcnN0Cj9mb3VydGggPT0gW10gW2ZvdXJ0aF0gW10gaWZ0ZQpmaXJzdF90d28gPT0gdW5jb25z
|
||||
IHVuY29ucyBwb3AKY2NvbnMgPT0gY29ucyBjb25zCmNpbmYgPT0gY29ucyBpbmZyYQpyZXN0X3R3
|
||||
byA9PSByZXN0IHJlc3QKCl9UcmVlX1Q+ID09IFtkaXBkXSBjaW5mCl9UcmVlX1Q8ID09IFtkaXBk
|
||||
ZF0gY2luZgoKX1RyZWVfYWRkX1AgPT0gb3ZlciBbcG9wb3AgcG9wb3AgZmlyc3RdIG51bGxhcnkK
|
||||
X1RyZWVfYWRkX1Q+ID09IGNjb25zIF9UcmVlX1Q8Cl9UcmVlX2FkZF9UPCA9PSBjY29ucyBfVHJl
|
||||
ZV9UPgpfVHJlZV9hZGRfRWUgPSA9IHBvcCBzd2FwIHJvbGw8IHJlc3RfdHdvIGNjb25zCl9UcmVl
|
||||
X2FkZF9SID09IF9UcmVlX2FkZF9QIFtfVHJlZV9hZGRfVD5dIFtfVHJlZV9hZGRfRWVdIFtfVHJl
|
||||
ZV9hZGRfVDxdIGNtcApfVHJlZV9hZGRfRSA9PSBbcG9wXSBkaXBkIFRyZWUtbmV3CgpfVHJlZV9p
|
||||
dGVyX29yZGVyX2xlZnQgPT0gW2NvbnMgZGlwXSBkdXBkaXAKX1RyZWVfaXRlcl9vcmRlcl9jdXJy
|
||||
ZW50ID09IFtbRl0gZHVwZGlwXSBkaXAKX1RyZWVfaXRlcl9vcmRlcl9yaWdodCA9PSBbZm91cnRo
|
||||
XSBkaXAgaQpfVHJlZV9pdGVyX29yZGVyX1IgPT0gX1RyZWVfaXRlcl9vcmRlcl9sZWZ0IF9UcmVl
|
||||
X2l0ZXJfb3JkZXJfY3VycmVudCBfVHJlZV9pdGVyX29yZGVyX3JpZ2h0CgpfVHJlZV9nZXRfUCA9
|
||||
PSBvdmVyIFtwb3AgcG9wb3AgZmlyc3RdIG51bGxhcnkKX1RyZWVfZ2V0X1Q+ID09IFtmb3VydGhd
|
||||
IGRpcGQgaQpfVHJlZV9nZXRfVDwgPT0gW3RoaXJkXSBkaXBkIGkKX1RyZWVfZ2V0X0UgPSA9IHBv
|
||||
cG9wIHNlY29uZApfVHJlZV9nZXRfUiA9PSBfVHJlZV9nZXRfUCBbX1RyZWVfZ2V0X1Q+XSBbX1Ry
|
||||
ZWVfZ2V0X0VdIFtfVHJlZV9nZXRfVDxdIGNtcAoKX1RyZWVfZGVsZXRlX3JpZ2h0bW9zdCA9PSBb
|
||||
P2ZvdXJ0aF0gW2ZvdXJ0aF0gd2hpbGUKX1RyZWVfZGVsZXRlX2NsZWFyX3N0dWZmID0gPSByb2xs
|
||||
PiBwb3BvcCByZXN0Cl9UcmVlX2RlbGV0ZV9kZWwgPT0gZGlwIGNvbnMgZGlwZCBzd2FwCl9UcmVl
|
||||
X2RlbGV0ZV9XID09IGR1cCBfVHJlZV9kZWxldGVfcmlnaHRtb3N0IGZpcnN0X3R3byBvdmVyCl9U
|
||||
cmVlX2RlbGV0ZV9FLjAgPT0gX1RyZWVfZGVsZXRlX2NsZWFyX3N0dWZmIFtfVHJlZV9kZWxldGVf
|
||||
V10gX1RyZWVfZGVsZXRlX2RlbApfVHJlZV9kZWxldGVfRSA9PSBbW1twb3AgdGhpcmQgbm90XSBw
|
||||
b3AgZm91cnRoXSBbW3BvcCBmb3VydGggbm90XSBwb3AgdGhpcmRdIFtbX1RyZWVfZGVsZXRlX0Uu
|
||||
MF0gY2luZl1dIGNvbmQKX1RyZWVfZGVsZXRlX1IwID0gPSBvdmVyIGZpcnN0IHN3YXAgZHVwCl9U
|
||||
cmVlX2RlbGV0ZV9SMSA9PSBjb25zIHJvbGw+IFtfVHJlZV9UPl0gW19UcmVlX2RlbGV0ZV9FXSBb
|
||||
X1RyZWVfVDxdIGNtcAoKVHJlZS1uZXcgPT0gc3dhcCBbW10gW11dIGNjb25zClRyZWUtYWRkID09
|
||||
IFtwb3BvcCBub3RdIFtfVHJlZV9hZGRfRV0gW10gW19UcmVlX2FkZF9SXSBnZW5yZWMKVHJlZS1p
|
||||
dGVyID09IFtub3RdIFtwb3BdIHJvbGw8IFtkdXBkaXAgcmVzdF90d29dIGNvbnMgW3N0ZXBdIGdl
|
||||
bnJlYwpUcmVlLWl0ZXItb3JkZXIgPT0gW25vdF0gW3BvcF0gW2R1cCB0aGlyZF0gW19UcmVlX2l0
|
||||
ZXJfb3JkZXJfUl0gZ2VucmVjClRyZWUtZ2V0ID09IFtwb3Agbm90XSBzd2FwIFtdIFtfVHJlZV9n
|
||||
ZXRfUl0gZ2VucmVjClRyZWUtZGVsZXRlID09IFtwb3Agbm90XSBbcG9wXSBbX1RyZWVfZGVsZXRl
|
||||
X1IwXSBbX1RyZWVfZGVsZXRlX1IxXSBnZW5yZWNQSwECFAMUAAAAAACSofVMP8hwIBUEAAAVBAAA
|
||||
CwAAAAAAAAAAAAAAgIEAAAAAc2NyYXRjaC50eHRQSwECFAMUAAAAAABAQfVM5+RkR1EBAABRAQAA
|
||||
BwAAAAAAAAAAAAAAgIE+BAAAbG9nLnR4dFBLAQIUAxQAAAAAAEFB9Ux3f5peAwAAAAMAAAAMAAAA
|
||||
AAAAAAAAAACAgbQFAABzdGFjay5waWNrbGVQSwECFAMUAAAAAACmofVM0vhrz+wAAADsAAAACwAA
|
||||
AAAAAAAAAAAAtIHhBQAAdGh1bi5jb25maWdQSwECFAMUAAAAAABzofVMkFCeCjoHAAA6BwAADwAA
|
||||
AAAAAAAAAAAAtIH2BgAAZGVmaW5pdGlvbnMudHh0UEsFBgAAAAAFAAUAHgEAAF0OAAAAAA==''')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(create_data())
|
||||
-198
@@ -1,198 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# This is a script, the module namespace is used as a kind of singleton
|
||||
# for organizing the moving parts of the system. I forget why I didn't
|
||||
# use a more typical class.
|
||||
#
|
||||
# This docstring doubles as the log header that the system prints when
|
||||
# the log is reset.
|
||||
|
||||
('''\
|
||||
Joypy - Copyright © 2018 Simon Forman
|
||||
'''
|
||||
'This program comes with ABSOLUTELY NO WARRANTY; for details right-click "warranty".'
|
||||
' This is free software, and you are welcome to redistribute it under certain conditions;'
|
||||
' right-click "sharing" for details.'
|
||||
' Right-click on these commands to see docs on UI commands: key_bindings mouse_bindings')
|
||||
|
||||
from __future__ import print_function
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
import logging, os, pickle, sys
|
||||
from datetime import datetime
|
||||
from textwrap import dedent
|
||||
from configparser import RawConfigParser
|
||||
|
||||
from joy.gui.utils import init_home, argparser, FileFaker
|
||||
|
||||
|
||||
DATETIME_FORMAT = "Thun • %B %d %a • %I:%M %p"
|
||||
VIEWER_DEFAULTS = dict(width=80, height=25)
|
||||
|
||||
|
||||
args = argparser.parse_args()
|
||||
JOY_HOME = args.joy_home
|
||||
repo = init_home(JOY_HOME)
|
||||
homed = lambda fn: os.path.join(JOY_HOME, fn)
|
||||
|
||||
# Set up logging before doing anything else.
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
logging.basicConfig(
|
||||
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
|
||||
filename=os.path.join(JOY_HOME, 'thun.log'),
|
||||
level=logging.INFO,
|
||||
)
|
||||
_log.info('Starting with JOY_HOME=%s', JOY_HOME)
|
||||
|
||||
# Now that logging is set up, continue loading the system.
|
||||
|
||||
from joy.gui.textwidget import TextViewerWidget, tk, get_font
|
||||
from joy.gui.world import StackWorld
|
||||
from joy.gui.controllerlistbox import StackListbox
|
||||
from joy.library import initialize, DefinitionWrapper
|
||||
from joy.utils.stack import stack_to_string
|
||||
|
||||
|
||||
cp = RawConfigParser()
|
||||
# Don't mess with uppercase. We need it for Tk event binding.
|
||||
cp.optionxform = str
|
||||
with open(os.path.join(args.joy_home, 'thun.config')) as f:
|
||||
cp.readfp(f)
|
||||
|
||||
|
||||
GLOBAL_COMMANDS = dict(cp.items('key bindings'))
|
||||
|
||||
|
||||
def repo_relative_path(path):
|
||||
return os.path.relpath(
|
||||
path,
|
||||
os.path.commonprefix((repo.controldir(), path))
|
||||
)
|
||||
|
||||
def commands():
|
||||
'''
|
||||
We define a bunch of meta-interpreter command functions here and
|
||||
return them in a dictionary. They have all the contents of this
|
||||
module in their scope so they can e.g. modify the log viewer window.
|
||||
'''
|
||||
# pylint: disable=unused-variable
|
||||
|
||||
def key_bindings(*args):
|
||||
commands = [ # These are bound in the TextViewerWidget.
|
||||
'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.',
|
||||
'F3 - Copy selection to stack.',
|
||||
'Shift-F3 - Cut selection to stack.',
|
||||
'F4 - Paste item on top of stack to insertion cursor.',
|
||||
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
|
||||
]
|
||||
for key, command in GLOBAL_COMMANDS.items():
|
||||
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
|
||||
print('\n'.join([''] + sorted(commands)))
|
||||
return args
|
||||
|
||||
|
||||
def mouse_bindings(*args):
|
||||
print(dedent('''
|
||||
Mouse button chords (to cancel a chord, click the third mouse button.)
|
||||
|
||||
Left - Point, sweep selection
|
||||
Left-Middle - Copy the selection, place text on stack
|
||||
Left-Right - Run the selection as Joy code
|
||||
|
||||
Middle - Paste selection (bypass stack); click and drag to scroll.
|
||||
Middle-Left - Paste from top of stack, preserve
|
||||
Middle-Right - Paste from top of stack, pop
|
||||
|
||||
Right - Execute command word under mouse cursor
|
||||
Right-Left - Print docs of command word under mouse cursor
|
||||
Right-Middle - Lookup word (kinda useless now)
|
||||
'''))
|
||||
return args
|
||||
|
||||
|
||||
def reset_log(*args):
|
||||
log.delete('0.0', tk.END)
|
||||
print(datetime.now().strftime(DATETIME_FORMAT))
|
||||
return args
|
||||
|
||||
|
||||
def Thun(*args):
|
||||
print(__doc__)
|
||||
return args
|
||||
|
||||
|
||||
def show_log(*args):
|
||||
log_window.wm_deiconify()
|
||||
log_window.update()
|
||||
return args
|
||||
|
||||
|
||||
def show_stack(*args):
|
||||
stack_window.wm_deiconify()
|
||||
stack_window.update()
|
||||
return args
|
||||
|
||||
|
||||
def grand_reset(s, e, d):
|
||||
stack = world.load_stack() or ()
|
||||
log.reset()
|
||||
t.reset()
|
||||
return stack, e, d
|
||||
|
||||
return locals()
|
||||
|
||||
|
||||
# Identify the system core files.
|
||||
DEFS_FN = homed('definitions.txt')
|
||||
JOY_FN = homed('scratch.txt')
|
||||
LOG_FN = homed('log.txt')
|
||||
STACK_FN = homed('stack.pickle')
|
||||
REL_STACK_FN = repo_relative_path(STACK_FN)
|
||||
|
||||
# Initialize the Joy dictionary.
|
||||
D = initialize()
|
||||
D.update(commands())
|
||||
DefinitionWrapper.load_definitions(DEFS_FN, D)
|
||||
|
||||
world = StackWorld(repo, STACK_FN, REL_STACK_FN, dictionary=D)
|
||||
|
||||
t = TextViewerWidget(world, **VIEWER_DEFAULTS)
|
||||
|
||||
log_window = tk.Toplevel()
|
||||
# Make it so that you can't actually close the log window, if you try it
|
||||
# will just "withdraw" (which is like minifying but without a entry in
|
||||
# the taskbar or icon or whatever.)
|
||||
log_window.protocol("WM_DELETE_WINDOW", log_window.withdraw)
|
||||
log = TextViewerWidget(world, log_window, **VIEWER_DEFAULTS)
|
||||
|
||||
FONT = get_font('Iosevka', size=14) # Requires Tk root already set up.
|
||||
|
||||
stack_window = tk.Toplevel()
|
||||
stack_window.title("Stack")
|
||||
stack_window.protocol("WM_DELETE_WINDOW", log_window.withdraw)
|
||||
stack_viewer = StackListbox(world, stack_window, items=[], font=FONT)
|
||||
stack_viewer.pack(expand=True, fill=tk.BOTH)
|
||||
world.set_viewer(stack_viewer)
|
||||
|
||||
|
||||
log.init('Log', LOG_FN, repo_relative_path(LOG_FN), repo, FONT)
|
||||
t.init('Joy - ' + JOY_HOME, JOY_FN, repo_relative_path(JOY_FN), repo, FONT)
|
||||
|
||||
for event, command in GLOBAL_COMMANDS.items():
|
||||
callback = lambda _, _command=command: world.interpret(_command)
|
||||
t.bind_all(event, callback)
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout, old_stdout = FileFaker(log), sys.stdout
|
||||
try:
|
||||
t.mainloop()
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,211 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2014, 2015 Simon Forman
|
||||
#
|
||||
# This file is part of joy.py
|
||||
#
|
||||
# joy.py is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# joy.py is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with joy.py. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from builtins import object
|
||||
|
||||
|
||||
#Do-nothing event handler.
|
||||
nothing = lambda event: None
|
||||
|
||||
|
||||
class MouseBindingsMixin(object):
|
||||
"""TextViewerWidget mixin class to provide mouse bindings."""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
#Remember our mouse button state
|
||||
self.B1_DOWN = False
|
||||
self.B2_DOWN = False
|
||||
self.B3_DOWN = False
|
||||
|
||||
#Remember our pending action.
|
||||
self.dothis = nothing
|
||||
|
||||
#We'll need to remember whether or not we've been moving B2.
|
||||
self.beenMovingB2 = False
|
||||
|
||||
#Unbind the events we're interested in.
|
||||
for sequence in (
|
||||
"<Button-1>", "<B1-Motion>", "<ButtonRelease-1>",
|
||||
"<Button-2>", "<B2-Motion>", "<ButtonRelease-2>",
|
||||
"<Button-3>", "<B3-Motion>", "<ButtonRelease-3>",
|
||||
"<B1-Leave>", "<B2-Leave>", "<B3-Leave>", "<Any-Leave>", "<Leave>"
|
||||
):
|
||||
self.unbind(sequence)
|
||||
self.unbind_all(sequence)
|
||||
|
||||
self.event_delete('<<PasteSelection>>') #I forgot what this was for! :-P D'oh!
|
||||
|
||||
#Bind our event handlers to their events.
|
||||
self.bind("<Button-1>", self.B1d)
|
||||
self.bind("<B1-Motion>", self.B1m)
|
||||
self.bind("<ButtonRelease-1>", self.B1r)
|
||||
|
||||
self.bind("<Button-2>", self.B2d)
|
||||
self.bind("<B2-Motion>", self.B2m)
|
||||
self.bind("<ButtonRelease-2>", self.B2r)
|
||||
|
||||
self.bind("<Button-3>", self.B3d)
|
||||
self.bind("<B3-Motion>", self.B3m)
|
||||
self.bind("<ButtonRelease-3>", self.B3r)
|
||||
|
||||
self.bind("<Any-Leave>", self.leave)
|
||||
self.bind("<Motion>", self.scan_command)
|
||||
|
||||
def B1d(self, event):
|
||||
'''button one pressed'''
|
||||
self.B1_DOWN = True
|
||||
|
||||
if self.B2_DOWN:
|
||||
|
||||
self.unhighlight_command()
|
||||
|
||||
if self.B3_DOWN :
|
||||
self.dothis = self.cancel
|
||||
|
||||
else:
|
||||
#copy TOS to the mouse (instead of system selection.)
|
||||
self.dothis = self.copyto #middle-left-interclick
|
||||
|
||||
elif self.B3_DOWN :
|
||||
self.unhighlight_command()
|
||||
self.dothis = self.opendoc #right-left-interclick
|
||||
|
||||
else:
|
||||
##button 1 down, set insertion and begin selection.
|
||||
##Actually, do nothing. Tk Text widget defaults take care of it.
|
||||
self.dothis = nothing
|
||||
return
|
||||
|
||||
#Prevent further event handling by returning "break".
|
||||
return "break"
|
||||
|
||||
def B2d(self, event):
|
||||
'''button two pressed'''
|
||||
self.B2_DOWN = 1
|
||||
|
||||
if self.B1_DOWN :
|
||||
|
||||
if self.B3_DOWN :
|
||||
self.dothis = self.cancel
|
||||
|
||||
else:
|
||||
#left-middle-interclick - copy selection to stack
|
||||
self.dothis = self.copy_selection_to_stack
|
||||
|
||||
elif self.B3_DOWN :
|
||||
self.unhighlight_command()
|
||||
self.dothis = self.lookup #right-middle-interclick - lookup
|
||||
|
||||
else:
|
||||
#middle-click - paste X selection to mouse pointer
|
||||
self.set_insertion_point(event)
|
||||
self.dothis = self.paste_X_selection_to_mouse_pointer
|
||||
return
|
||||
|
||||
return "break"
|
||||
|
||||
def B3d(self, event):
|
||||
'''button three pressed'''
|
||||
self.B3_DOWN = 1
|
||||
|
||||
if self.B1_DOWN :
|
||||
|
||||
if self.B2_DOWN :
|
||||
self.dothis = self.cancel
|
||||
|
||||
else:
|
||||
#left-right-interclick - run selection
|
||||
self.dothis = self.run_selection
|
||||
|
||||
elif self.B2_DOWN :
|
||||
#middle-right-interclick - Pop/Cut from TOS to insertion cursor
|
||||
self.unhighlight_command()
|
||||
self.dothis = self.pastecut
|
||||
|
||||
else:
|
||||
#right-click
|
||||
self.CommandFirstDown(event)
|
||||
|
||||
return "break"
|
||||
|
||||
def B1m(self, event):
|
||||
'''button one moved'''
|
||||
if self.B2_DOWN or self.B3_DOWN:
|
||||
return "break"
|
||||
|
||||
def B2m(self, event):
|
||||
'''button two moved'''
|
||||
if self.dothis == self.paste_X_selection_to_mouse_pointer and \
|
||||
not (self.B1_DOWN or self.B3_DOWN):
|
||||
|
||||
self.beenMovingB2 = True
|
||||
return
|
||||
|
||||
return "break"
|
||||
|
||||
def B3m(self, event):
|
||||
'''button three moved'''
|
||||
if self.dothis == self.do_command and \
|
||||
not (self.B1_DOWN or self.B2_DOWN):
|
||||
|
||||
self.update_command_word(event)
|
||||
|
||||
return "break"
|
||||
|
||||
def scan_command(self, event):
|
||||
self.update_command_word(event)
|
||||
|
||||
def B1r(self, event):
|
||||
'''button one released'''
|
||||
self.B1_DOWN = False
|
||||
|
||||
if not (self.B2_DOWN or self.B3_DOWN):
|
||||
self.dothis(event)
|
||||
|
||||
return "break"
|
||||
|
||||
def B2r(self, event):
|
||||
'''button two released'''
|
||||
self.B2_DOWN = False
|
||||
|
||||
if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2):
|
||||
self.dothis(event)
|
||||
|
||||
self.beenMovingB2 = False
|
||||
|
||||
return "break"
|
||||
|
||||
def B3r(self, event):
|
||||
'''button three released'''
|
||||
self.B3_DOWN = False
|
||||
|
||||
if not (self.B1_DOWN or self.B2_DOWN) :
|
||||
self.dothis(event)
|
||||
|
||||
return "break"
|
||||
|
||||
def InsertFirstDown(self, event):
|
||||
self.focus()
|
||||
self.dothis = nothing
|
||||
self.set_insertion_point(event)
|
||||
|
||||
def CommandFirstDown(self, event):
|
||||
self.dothis = self.do_command
|
||||
self.update_command_word(event)
|
||||
@@ -1,474 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2014, 2015, 2018 Simon Forman
|
||||
#
|
||||
# This file is part of joy.py
|
||||
#
|
||||
# joy.py is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# joy.py is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with joy.py. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
|
||||
A Graphical User Interface for a dialect of Joy in Python.
|
||||
|
||||
|
||||
The GUI
|
||||
|
||||
History
|
||||
Structure
|
||||
Commands
|
||||
Mouse Chords
|
||||
Keyboard
|
||||
Output from Joy
|
||||
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
from builtins import str, map, object
|
||||
from past.builtins import basestring
|
||||
try:
|
||||
import tkinter as tk
|
||||
from tkinter.font import families, Font
|
||||
except ImportError:
|
||||
import Tkinter as tk
|
||||
from tkFont import families, Font
|
||||
|
||||
from re import compile as regular_expression
|
||||
from traceback import format_exc
|
||||
import os, sys
|
||||
|
||||
from joy.utils.stack import stack_to_string
|
||||
|
||||
from .mousebindings import MouseBindingsMixin
|
||||
from .utils import is_numerical
|
||||
from .world import World
|
||||
|
||||
|
||||
def make_gui(dictionary):
|
||||
t = TextViewerWidget(World(dictionary=dictionary))
|
||||
t['font'] = get_font()
|
||||
t._root().title('Joy')
|
||||
t.pack(expand=True, fill=tk.BOTH)
|
||||
return t
|
||||
|
||||
|
||||
def get_font(family='EB Garamond', size=14):
|
||||
if family not in families():
|
||||
family = 'Times'
|
||||
return Font(family=family, size=size)
|
||||
|
||||
|
||||
#: Define mapping between Tkinter events and functions or methods. The
|
||||
#: keys are string Tk "event sequences" and the values are callables that
|
||||
#: get passed the TextViewer instance (so you can bind to methods) and
|
||||
#: must return the actual callable to which to bind the event sequence.
|
||||
TEXT_BINDINGS = {
|
||||
|
||||
#I want to ensure that these keyboard shortcuts work.
|
||||
'<Control-Return>': lambda tv: tv._control_enter,
|
||||
'<Control-v>': lambda tv: tv._paste,
|
||||
'<Control-V>': lambda tv: tv._paste,
|
||||
'<F3>': lambda tv: tv.copy_selection_to_stack,
|
||||
'<F4>': lambda tv: tv.copyto,
|
||||
'<Shift-F3>': lambda tv: tv.cut,
|
||||
'<Shift-F4>': lambda tv: tv.pastecut,
|
||||
'<Shift-Insert>': lambda tv: tv._paste,
|
||||
}
|
||||
|
||||
|
||||
class SavingMixin(object):
|
||||
|
||||
def __init__(self, saver=None, filename=None, save_delay=2000):
|
||||
self.saver = self._saver if saver is None else saver
|
||||
self.filename = filename
|
||||
self._save_delay = save_delay
|
||||
self.tk.call(self._w, 'edit', 'modified', 0)
|
||||
self.bind('<<Modified>>', self._beenModified)
|
||||
self._resetting_modified_flag = False
|
||||
self._save = None
|
||||
|
||||
def save(self):
|
||||
'''
|
||||
Call _saveFunc() after a certain amount of idle time.
|
||||
|
||||
Called by _beenModified().
|
||||
'''
|
||||
self._cancelSave()
|
||||
if self.saver:
|
||||
self._saveAfter(self._save_delay)
|
||||
|
||||
def _saveAfter(self, delay):
|
||||
'''
|
||||
Trigger a cancel-able call to _saveFunc() after delay milliseconds.
|
||||
'''
|
||||
self._save = self.after(delay, self._saveFunc)
|
||||
|
||||
def _saveFunc(self):
|
||||
self._save = None
|
||||
self.saver(self._get_contents())
|
||||
|
||||
def _saver(self, text):
|
||||
if not self.filename:
|
||||
return
|
||||
with open(self.filename, 'wb') as f:
|
||||
os.chmod(self.filename, 0o600)
|
||||
f.write(text.encode('UTF_8'))
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
if hasattr(self, 'repo'):
|
||||
self.repo.stage([self.repo_relative_filename])
|
||||
self.world.save()
|
||||
|
||||
def _cancelSave(self):
|
||||
if self._save is not None:
|
||||
self.after_cancel(self._save)
|
||||
self._save = None
|
||||
|
||||
def _get_contents(self):
|
||||
self['state'] = 'disabled'
|
||||
try:
|
||||
return self.get('0.0', 'end')[:-1]
|
||||
finally:
|
||||
self['state'] = 'normal'
|
||||
|
||||
def _beenModified(self, event):
|
||||
if self._resetting_modified_flag:
|
||||
return
|
||||
self._clearModifiedFlag()
|
||||
self.save()
|
||||
|
||||
def _clearModifiedFlag(self):
|
||||
self._resetting_modified_flag = True
|
||||
try:
|
||||
self.tk.call(self._w, 'edit', 'modified', 0)
|
||||
finally:
|
||||
self._resetting_modified_flag = False
|
||||
|
||||
## tags = self._saveTags()
|
||||
## chunks = self.DUMP()
|
||||
## print chunks
|
||||
|
||||
|
||||
class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
|
||||
"""
|
||||
This class is a Tkinter Text with special mousebindings to make
|
||||
it act as a Xerblin Text Viewer.
|
||||
"""
|
||||
|
||||
#This is a regular expression for finding commands in the text.
|
||||
command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+')
|
||||
|
||||
#These are the config tags for command text when it's highlighted.
|
||||
command_tags = dict(
|
||||
#underline = 1,
|
||||
#bgstipple = "gray50",
|
||||
borderwidth = 2,
|
||||
relief=tk.RIDGE,
|
||||
foreground = "green"
|
||||
)
|
||||
|
||||
def __init__(self, world, master=None, **kw):
|
||||
|
||||
self.world = world
|
||||
if self.world.text_widget is None:
|
||||
self.world.text_widget = self
|
||||
|
||||
#Turn on undo, but don't override a passed-in setting.
|
||||
kw.setdefault('undo', True)
|
||||
|
||||
# kw.setdefault('bg', 'white')
|
||||
kw.setdefault('wrap', 'word')
|
||||
kw.setdefault('font', 'arial 12')
|
||||
|
||||
text_bindings = kw.pop('text_bindings', TEXT_BINDINGS)
|
||||
|
||||
#Create ourselves as a Tkinter Text
|
||||
tk.Text.__init__(self, master, **kw)
|
||||
|
||||
#Initialize our mouse mixin.
|
||||
MouseBindingsMixin.__init__(self)
|
||||
|
||||
#Initialize our saver mixin.
|
||||
SavingMixin.__init__(self)
|
||||
|
||||
#Add tag config for command highlighting.
|
||||
self.tag_config('command', **self.command_tags)
|
||||
self.tag_config('bzzt', foreground = "orange")
|
||||
self.tag_config('huh', foreground = "grey")
|
||||
self.tag_config('number', foreground = "blue")
|
||||
|
||||
#Create us a command instance variable
|
||||
self.command = ''
|
||||
|
||||
#Activate event bindings. Modify text_bindings in your config
|
||||
#file to affect the key bindings and whatnot here.
|
||||
for event_sequence, callback_finder in text_bindings.items():
|
||||
callback = callback_finder(self)
|
||||
self.bind(event_sequence, callback)
|
||||
|
||||
## T.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||
|
||||
def find_command_in_line(self, line, index):
|
||||
'''
|
||||
Return the command at index in line and its begin and end indices.
|
||||
find_command_in_line(line, index) => command, begin, end
|
||||
'''
|
||||
for match in self.command_re.finditer(line):
|
||||
b, e = match.span()
|
||||
if b <= index <= e:
|
||||
return match.group(), b, e
|
||||
|
||||
def paste_X_selection_to_mouse_pointer(self, event):
|
||||
'''Paste the X selection to the mouse pointer.'''
|
||||
try:
|
||||
text = self.selection_get()
|
||||
except tk.TclError:
|
||||
return 'break'
|
||||
self.insert_it(text)
|
||||
|
||||
def update_command_word(self, event):
|
||||
'''Highlight the command under the mouse.'''
|
||||
self.unhighlight_command()
|
||||
self.command = ''
|
||||
index = '@%d,%d' % (event.x, event.y)
|
||||
linestart = self.index(index + 'linestart')
|
||||
lineend = self.index(index + 'lineend')
|
||||
line = self.get(linestart, lineend)
|
||||
row, offset = self._get_index(index)
|
||||
|
||||
if offset >= len(line) or line[offset].isspace():
|
||||
# The mouse is off the end of the line or on a space so there's no
|
||||
# command, we're done.
|
||||
return
|
||||
|
||||
cmd = self.find_command_in_line(line, offset)
|
||||
if cmd is None:
|
||||
return
|
||||
|
||||
cmd, b, e = cmd
|
||||
if is_numerical(cmd):
|
||||
extra_tags = 'number',
|
||||
elif self.world.has(cmd):
|
||||
check = self.world.check(cmd)
|
||||
if check: extra_tags = ()
|
||||
elif check is None: extra_tags = 'huh',
|
||||
else: extra_tags = 'bzzt',
|
||||
else:
|
||||
return
|
||||
self.command = cmd
|
||||
self.highlight_command(
|
||||
'%d.%d' % (row, b),
|
||||
'%d.%d' % (row, e),
|
||||
*extra_tags)
|
||||
|
||||
def highlight_command(self, from_, to, *extra_tags):
|
||||
'''Apply command style from from_ to to.'''
|
||||
cmdstart = self.index(from_)
|
||||
cmdend = self.index(to)
|
||||
self.tag_add('command', cmdstart, cmdend)
|
||||
for tag in extra_tags:
|
||||
self.tag_add(tag, cmdstart, cmdend)
|
||||
|
||||
def do_command(self, event):
|
||||
'''Do the currently highlighted command.'''
|
||||
self.unhighlight_command()
|
||||
if self.command:
|
||||
self.run_command(self.command)
|
||||
|
||||
def _control_enter(self, event):
|
||||
select_indices = self.tag_ranges(tk.SEL)
|
||||
if select_indices:
|
||||
command = self.get(select_indices[0], select_indices[1])
|
||||
else:
|
||||
linestart = self.index(tk.INSERT + ' linestart')
|
||||
lineend = self.index(tk.INSERT + ' lineend')
|
||||
command = self.get(linestart, lineend)
|
||||
if command and not command.isspace():
|
||||
self.run_command(command)
|
||||
return 'break'
|
||||
|
||||
def run_command(self, command):
|
||||
'''Given a string run it on the stack, report errors.'''
|
||||
try:
|
||||
self.world.interpret(command)
|
||||
except SystemExit:
|
||||
raise
|
||||
except:
|
||||
self.popupTB(format_exc().rstrip())
|
||||
|
||||
def unhighlight_command(self):
|
||||
'''Remove any command highlighting.'''
|
||||
self.tag_remove('number', 1.0, tk.END)
|
||||
self.tag_remove('huh', 1.0, tk.END)
|
||||
self.tag_remove('bzzt', 1.0, tk.END)
|
||||
self.tag_remove('command', 1.0, tk.END)
|
||||
|
||||
def set_insertion_point(self, event):
|
||||
'''Set the insertion cursor to the current mouse location.'''
|
||||
self.focus()
|
||||
self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y))
|
||||
|
||||
def copy_selection_to_stack(self, event):
|
||||
'''Copy selection to stack.'''
|
||||
select_indices = self.tag_ranges(tk.SEL)
|
||||
if select_indices:
|
||||
s = self.get(select_indices[0], select_indices[1])
|
||||
self.world.push(s)
|
||||
|
||||
def cut(self, event):
|
||||
'''Cut selection to stack.'''
|
||||
self.copy_selection_to_stack(event)
|
||||
# Let the pre-existing machinery take care of cutting the selection.
|
||||
self.event_generate("<<Cut>>")
|
||||
|
||||
def copyto(self, event):
|
||||
'''Actually "paste" from TOS'''
|
||||
s = self.world.peek()
|
||||
if s is not None:
|
||||
self.insert_it(s)
|
||||
|
||||
def insert_it(self, s):
|
||||
if not isinstance(s, basestring):
|
||||
s = stack_to_string(s)
|
||||
|
||||
# When pasting from the mouse we have to remove the current selection
|
||||
# to prevent destroying it by the paste operation.
|
||||
select_indices = self.tag_ranges(tk.SEL)
|
||||
if select_indices:
|
||||
# Set two marks to remember the selection.
|
||||
self.mark_set('_sel_start', select_indices[0])
|
||||
self.mark_set('_sel_end', select_indices[1])
|
||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||
|
||||
self.insert(tk.INSERT, s)
|
||||
|
||||
if select_indices:
|
||||
self.tag_add(tk.SEL, '_sel_start', '_sel_end')
|
||||
self.mark_unset('_sel_start')
|
||||
self.mark_unset('_sel_end')
|
||||
|
||||
def run_selection(self, event):
|
||||
'''Run the current selection if any on the stack.'''
|
||||
select_indices = self.tag_ranges(tk.SEL)
|
||||
if select_indices:
|
||||
selection = self.get(select_indices[0], select_indices[1])
|
||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||
self.run_command(selection)
|
||||
|
||||
def pastecut(self, event):
|
||||
'''Cut the TOS item to the mouse.'''
|
||||
self.copyto(event)
|
||||
self.world.pop()
|
||||
|
||||
def opendoc(self, event):
|
||||
'''OpenDoc the current command.'''
|
||||
if self.command:
|
||||
self.world.do_opendoc(self.command)
|
||||
|
||||
def lookup(self, event):
|
||||
'''Look up the current command.'''
|
||||
if self.command:
|
||||
self.world.do_lookup(self.command)
|
||||
|
||||
def cancel(self, event):
|
||||
'''Cancel whatever we're doing.'''
|
||||
self.leave(None)
|
||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||
self._sel_anchor = '0.0'
|
||||
self.mark_unset(tk.INSERT)
|
||||
|
||||
def leave(self, event):
|
||||
'''Called when mouse leaves the Text window.'''
|
||||
self.unhighlight_command()
|
||||
self.command = ''
|
||||
|
||||
def _get_index(self, index):
|
||||
'''Get the index in (int, int) form of index.'''
|
||||
return tuple(map(int, self.index(index).split('.')))
|
||||
|
||||
def _paste(self, event):
|
||||
'''Paste the system selection to the current selection, replacing it.'''
|
||||
|
||||
# If we're "key" pasting, we have to move the insertion point
|
||||
# to the selection so the pasted text gets inserted at the
|
||||
# location of the deleted selection.
|
||||
|
||||
select_indices = self.tag_ranges(tk.SEL)
|
||||
if select_indices:
|
||||
# Mark the location of the current insertion cursor
|
||||
self.mark_set('tmark', tk.INSERT)
|
||||
# Put the insertion cursor at the selection
|
||||
self.mark_set(tk.INSERT, select_indices[1])
|
||||
|
||||
# Paste to the current selection, or if none, to the insertion cursor.
|
||||
self.event_generate("<<Paste>>")
|
||||
|
||||
# If we mess with the insertion cursor above, fix it now.
|
||||
if select_indices:
|
||||
# Put the insertion cursor back where it was.
|
||||
self.mark_set(tk.INSERT, 'tmark')
|
||||
# And get rid of our unneeded mark.
|
||||
self.mark_unset('tmark')
|
||||
|
||||
return 'break'
|
||||
|
||||
def init(self, title, filename, repo_relative_filename, repo, font):
|
||||
self.set_window_title(title)
|
||||
if os.path.exists(filename):
|
||||
with open(filename) as f:
|
||||
data = f.read()
|
||||
self.insert(tk.END, data)
|
||||
# Prevent this from triggering a git commit.
|
||||
self.update()
|
||||
self._cancelSave()
|
||||
self.pack(expand=True, fill=tk.BOTH)
|
||||
self.filename = filename
|
||||
self.repo_relative_filename = repo_relative_filename
|
||||
self.repo = repo
|
||||
self['font'] = font # See below.
|
||||
|
||||
def set_window_title(self, title):
|
||||
self.winfo_toplevel().title(title)
|
||||
|
||||
def reset(self):
|
||||
if os.path.exists(self.filename):
|
||||
with open(self.filename) as f:
|
||||
data = f.read()
|
||||
if data:
|
||||
self.delete('0.0', tk.END)
|
||||
self.insert(tk.END, data)
|
||||
|
||||
def popupTB(self, tb):
|
||||
top = tk.Toplevel()
|
||||
T = TextViewerWidget(
|
||||
self.world,
|
||||
top,
|
||||
width=max(len(s) for s in tb.splitlines()) + 3,
|
||||
)
|
||||
|
||||
T['background'] = 'darkgrey'
|
||||
T['foreground'] = 'darkblue'
|
||||
T.tag_config('err', foreground='yellow')
|
||||
|
||||
T.insert(tk.END, tb)
|
||||
last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0'
|
||||
T.tag_add('err', last_line, tk.END)
|
||||
T['state'] = tk.DISABLED
|
||||
|
||||
top.title(T.get(last_line, tk.END).strip())
|
||||
|
||||
T.pack(expand=1, fill=tk.BOTH)
|
||||
T.see(tk.END)
|
||||
@@ -1,98 +0,0 @@
|
||||
from __future__ import print_function
|
||||
from builtins import object
|
||||
import argparse, os, sys
|
||||
from os import listdir, mkdir
|
||||
from os.path import abspath, exists, expanduser, isfile, join
|
||||
|
||||
from dulwich.errors import NotGitRepository
|
||||
from dulwich.repo import Repo
|
||||
|
||||
|
||||
COMMITTER = b'Joy <auto-commit@example.com>'
|
||||
DEFAULT_JOY_HOME = expanduser(join('~', '.joypy'))
|
||||
|
||||
|
||||
def is_numerical(s):
|
||||
try:
|
||||
float(s)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def home_dir(path):
|
||||
'''Return the absolute path of an existing directory.'''
|
||||
|
||||
fullpath = expanduser(path) if path.startswith('~') else abspath(path)
|
||||
|
||||
if not exists(fullpath):
|
||||
if path == DEFAULT_JOY_HOME:
|
||||
print('Creating JOY_HOME', repr(fullpath))
|
||||
mkdir(fullpath, 0o700)
|
||||
else:
|
||||
print(repr(fullpath), "doesn't exist.", file=sys.stderr)
|
||||
raise ValueError(path)
|
||||
|
||||
return fullpath
|
||||
|
||||
|
||||
def init_home(fullpath):
|
||||
'''
|
||||
Open or create the Repo.
|
||||
If there are contents in the dir but it's not a git repo, quit.
|
||||
'''
|
||||
try:
|
||||
repo = Repo(fullpath)
|
||||
except NotGitRepository:
|
||||
print(repr(fullpath), "no repository", file=sys.stderr)
|
||||
|
||||
if listdir(fullpath):
|
||||
print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
print('Initializing repository in', fullpath)
|
||||
repo = init_repo(fullpath)
|
||||
|
||||
print('Using repository in', fullpath)
|
||||
return repo
|
||||
|
||||
|
||||
def init_repo(repo_dir):
|
||||
'''
|
||||
Create a repo, load the initial content, and make the first commit.
|
||||
Return the Repo object.
|
||||
'''
|
||||
repo = Repo.init(repo_dir)
|
||||
import joy.gui.init_joy_home
|
||||
joy.gui.init_joy_home.initialize(repo_dir)
|
||||
repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))])
|
||||
repo.do_commit(b'Initial commit.', committer=COMMITTER)
|
||||
return repo
|
||||
|
||||
|
||||
argparser = argparse.ArgumentParser(
|
||||
prog='joy.gui',
|
||||
description='Experimental Brutalist UI for Joy.',
|
||||
)
|
||||
|
||||
|
||||
argparser.add_argument(
|
||||
'-j', '--joy-home',
|
||||
help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME,
|
||||
default=DEFAULT_JOY_HOME,
|
||||
dest='joy_home',
|
||||
type=home_dir,
|
||||
)
|
||||
|
||||
|
||||
class FileFaker(object):
|
||||
|
||||
def __init__(self, T):
|
||||
self.T = T
|
||||
|
||||
def write(self, text):
|
||||
self.T.insert('end', text)
|
||||
self.T.see('end')
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
@@ -1,173 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2014, 2015 Simon Forman
|
||||
#
|
||||
# This file is part of joy.py
|
||||
#
|
||||
# joy.py is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# joy.py is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with joy.py. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import print_function
|
||||
from builtins import object
|
||||
from logging import getLogger
|
||||
|
||||
_log = getLogger(__name__)
|
||||
|
||||
import os, pickle, sys
|
||||
from inspect import getdoc
|
||||
|
||||
from joy.joy import run
|
||||
from joy.library import HELP_TEMPLATE
|
||||
from joy.parser import Symbol
|
||||
from joy.utils.stack import stack_to_string
|
||||
from joy.utils.types import type_check
|
||||
from .utils import is_numerical
|
||||
|
||||
|
||||
class World(object):
|
||||
|
||||
def __init__(self, stack=(), dictionary=None, text_widget=None):
|
||||
self.stack = stack
|
||||
self.dictionary = dictionary or {}
|
||||
self.text_widget = text_widget
|
||||
self.check_cache = {}
|
||||
|
||||
def check(self, name):
|
||||
try:
|
||||
res = self.check_cache[name]
|
||||
except KeyError:
|
||||
res = self.check_cache[name] = type_check(name, self.stack)
|
||||
return res
|
||||
|
||||
def do_lookup(self, name):
|
||||
if name in self.dictionary:
|
||||
self.stack = (Symbol(name), ()), self.stack
|
||||
self.print_stack()
|
||||
self.check_cache.clear()
|
||||
else:
|
||||
assert is_numerical(name)
|
||||
self.interpret(name)
|
||||
|
||||
def do_opendoc(self, name):
|
||||
if is_numerical(name):
|
||||
doc = 'The number ' + str(name)
|
||||
else:
|
||||
try:
|
||||
word = self.dictionary[name]
|
||||
except KeyError:
|
||||
doc = 'Unknown: ' + repr(name)
|
||||
else:
|
||||
doc = getdoc(word)
|
||||
print(HELP_TEMPLATE % (name, doc, name))
|
||||
self.print_stack()
|
||||
|
||||
def pop(self):
|
||||
if self.stack:
|
||||
self.stack = self.stack[1]
|
||||
self.print_stack()
|
||||
self.check_cache.clear()
|
||||
|
||||
def push(self, it):
|
||||
it = it.encode('utf8')
|
||||
self.stack = it, self.stack
|
||||
self.print_stack()
|
||||
self.check_cache.clear()
|
||||
|
||||
def peek(self):
|
||||
if self.stack:
|
||||
return self.stack[0]
|
||||
|
||||
def interpret(self, command):
|
||||
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
||||
return
|
||||
old_stack = self.stack
|
||||
try:
|
||||
self.stack, _, self.dictionary = run(
|
||||
command,
|
||||
self.stack,
|
||||
self.dictionary,
|
||||
)
|
||||
finally:
|
||||
self.print_stack()
|
||||
if old_stack != self.stack:
|
||||
self.check_cache.clear()
|
||||
|
||||
def has(self, name):
|
||||
return name in self.dictionary
|
||||
|
||||
def save(self):
|
||||
pass
|
||||
|
||||
def print_stack(self):
|
||||
stack_out_index = self.text_widget.search('<' 'STACK', 1.0)
|
||||
if stack_out_index:
|
||||
self.text_widget.see(stack_out_index)
|
||||
s = stack_to_string(self.stack) + '\n'
|
||||
self.text_widget.insert(stack_out_index, s)
|
||||
|
||||
|
||||
class StackDisplayWorld(World):
|
||||
|
||||
def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None):
|
||||
self.filename = filename
|
||||
stack = self.load_stack() or ()
|
||||
World.__init__(self, stack, dictionary, text_widget)
|
||||
self.repo = repo
|
||||
self.relative_STACK_FN = rel_filename
|
||||
|
||||
def interpret(self, command):
|
||||
command = command.strip()
|
||||
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
||||
return
|
||||
# print('\njoy?', command)
|
||||
self.print_command(command)
|
||||
super(StackDisplayWorld, self).interpret(command)
|
||||
|
||||
def print_command(self, command):
|
||||
print(command)
|
||||
|
||||
def print_stack(self):
|
||||
print('\n%s <-' % stack_to_string(self.stack))
|
||||
|
||||
def save(self):
|
||||
with open(self.filename, 'wb') as f:
|
||||
os.chmod(self.filename, 0o600)
|
||||
pickle.dump(self.stack, f, protocol=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
self.repo.stage([self.relative_STACK_FN])
|
||||
commit_id = self.repo.do_commit(
|
||||
b'auto-save',
|
||||
committer=b'thun-auto-save <nobody@example.com>',
|
||||
)
|
||||
_log.info('commit %s', commit_id)
|
||||
|
||||
def load_stack(self):
|
||||
if os.path.exists(self.filename):
|
||||
with open(self.filename, 'rb') as f:
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
class StackWorld(StackDisplayWorld):
|
||||
|
||||
viewer = None
|
||||
|
||||
def set_viewer(self, viewer):
|
||||
self.viewer = viewer
|
||||
self.viewer.update_stack(self.stack)
|
||||
|
||||
def print_stack(self):
|
||||
print(stack_to_string(self.stack), '•', end=' ')
|
||||
if self.viewer:
|
||||
self.viewer.update_stack(self.stack)
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2018 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
I really want tracebacks to show which function was being executed when
|
||||
an error in the wrapper function happens. In order to do that, you have
|
||||
to do this (the function in this module.)
|
||||
|
||||
Here's what it looks like when you pass too few arguments to e.g. "mul".
|
||||
|
||||
>>> from joy.library import _dictionary
|
||||
>>> m = _dictionary['*']
|
||||
>>> m((), (), {})
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "<pyshell#49>", line 1, in <module>
|
||||
m((), (), {})
|
||||
File "joy/library.py", line 185, in mul:inner
|
||||
(a, (b, stack)) = stack
|
||||
ValueError: need more than 0 values to unpack
|
||||
>>>
|
||||
|
||||
|
||||
Notice that line 185 in the library.py file is (as of this writing) in
|
||||
the BinaryBuiltinWrapper's inner() function, but this hacky code has
|
||||
managed to insert the name of the wrapped function ("mul") along with a
|
||||
colon into the wrapper function's reported name.
|
||||
|
||||
Normally I would frown on this sort of mad hackery, but... this is in
|
||||
the service of ease-of-debugging! Very valuable. And note that all the
|
||||
hideous patching is finished in the module-load-stage, it shouldn't cause
|
||||
issues of its own at runtime.
|
||||
|
||||
The main problem I see with this is that people coming to this code later
|
||||
might be mystified if they just see a traceback with a ':' in the
|
||||
function name! Hopefully they will discover this documentation.
|
||||
'''
|
||||
|
||||
|
||||
def rename_code_object(new_name):
|
||||
'''
|
||||
If you want to wrap a function in another function and have the wrapped
|
||||
function's name show up in the traceback when an exception occurs in
|
||||
the wrapper function, you must do this brutal hackery to change the
|
||||
func.__code__.co_name attribute. Just functools.wraps() is not enough.
|
||||
|
||||
See:
|
||||
|
||||
https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
|
||||
|
||||
https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
|
||||
|
||||
I'm just glad it's possible.
|
||||
'''
|
||||
def inner(func):
|
||||
name = new_name + ':' + func.__name__
|
||||
code_object = func.__code__
|
||||
return type(func)(
|
||||
type(code_object)(
|
||||
code_object.co_argcount,
|
||||
code_object.co_nlocals,
|
||||
code_object.co_stacksize,
|
||||
code_object.co_flags,
|
||||
code_object.co_code,
|
||||
code_object.co_consts,
|
||||
code_object.co_names,
|
||||
code_object.co_varnames,
|
||||
code_object.co_filename,
|
||||
name,
|
||||
code_object.co_firstlineno,
|
||||
code_object.co_lnotab,
|
||||
code_object.co_freevars,
|
||||
code_object.co_cellvars
|
||||
),
|
||||
func.__globals__,
|
||||
name,
|
||||
func.__defaults__,
|
||||
func.__closure__
|
||||
)
|
||||
return inner
|
||||
@@ -1,241 +0,0 @@
|
||||
'''
|
||||
A crude compiler for a subset of Joy functions.
|
||||
|
||||
I think I'm going about this the wrong way.
|
||||
|
||||
The inference algorithm can "collapse" Yin function sequences into
|
||||
single stack effects which can then be written out as Python functions.
|
||||
Why not keep track of the new variables introduced as results of Yang
|
||||
functions, during inference? Could I write out better code that way?
|
||||
|
||||
In any event, I am proceeding with this sort of ad hoc way for now.
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from builtins import next
|
||||
from builtins import str
|
||||
from builtins import object
|
||||
from joy.parser import text_to_expression, Symbol
|
||||
from joy.utils.stack import concat, iter_stack, list_to_stack
|
||||
from joy.library import SimpleFunctionWrapper, YIN_STACK_EFFECTS
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def import_yin():
|
||||
from joy.utils.generated_library import *
|
||||
return locals()
|
||||
|
||||
|
||||
class InfiniteStack(tuple):
|
||||
|
||||
def _names():
|
||||
n = 0
|
||||
while True:
|
||||
m = yield Symbol('a' + str(n))
|
||||
n = n + 1 if m is None else m
|
||||
|
||||
_NAMES = _names()
|
||||
next(_NAMES)
|
||||
|
||||
names = lambda: next(_NAMES)
|
||||
reset = lambda _self, _n=_NAMES: _n.send(-1)
|
||||
|
||||
def __init__(self, code):
|
||||
self.reset()
|
||||
self.code = code
|
||||
|
||||
def __iter__(self):
|
||||
if not self:
|
||||
new_var = self.names()
|
||||
self.code.append(('pop', new_var))
|
||||
return iter((new_var, self))
|
||||
|
||||
|
||||
def I(expression):
|
||||
code = []
|
||||
stack = InfiniteStack(code)
|
||||
|
||||
while expression:
|
||||
term, expression = expression
|
||||
if isinstance(term, Symbol):
|
||||
func = D[term]
|
||||
stack, expression, _ = func(stack, expression, code)
|
||||
else:
|
||||
stack = term, stack
|
||||
|
||||
code.append(tuple(['ret'] + list(iter_stack(stack))))
|
||||
return code
|
||||
|
||||
|
||||
strtup = lambda a, b: '(%s, %s)' % (b, a)
|
||||
strstk = lambda rest: reduce(strtup, rest, 'stack')
|
||||
|
||||
|
||||
def code_gen(code):
|
||||
#for p in code: print p
|
||||
coalesce_pops(code)
|
||||
lines = []
|
||||
emit = lines.append
|
||||
for t in code:
|
||||
tag, rest = t[0], t[1:]
|
||||
if tag == 'pop': emit(strstk(rest) + ' = stack')
|
||||
elif tag == 'call': emit('%s = %s%s' % rest)
|
||||
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
|
||||
else:
|
||||
raise ValueError(tag)
|
||||
return '\n'.join(' ' + line for line in lines)
|
||||
|
||||
|
||||
def coalesce_pops(code):
|
||||
code.sort(key=lambda p: p[0] != 'pop') # All pops to the front.
|
||||
try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
|
||||
except StopIteration: return
|
||||
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
|
||||
|
||||
|
||||
def compile_yinyang(name, text):
|
||||
return '''
|
||||
def %s(stack):
|
||||
%s
|
||||
''' % (name, code_gen(I(text_to_expression(text))))
|
||||
|
||||
|
||||
def q():
|
||||
memo = {}
|
||||
def bar(type_var):
|
||||
try:
|
||||
res = memo[type_var]
|
||||
except KeyError:
|
||||
res = memo[type_var] = InfiniteStack.names()
|
||||
return res
|
||||
return bar
|
||||
|
||||
|
||||
def type_vars_to_labels(thing, map_):
|
||||
if not thing:
|
||||
return thing
|
||||
if not isinstance(thing, tuple):
|
||||
return map_(thing)
|
||||
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
|
||||
|
||||
|
||||
def remap_inputs(in_, stack, code):
|
||||
map_ = q()
|
||||
while in_:
|
||||
term, in_ = in_
|
||||
arg0, stack = stack
|
||||
term = type_vars_to_labels(term, map_)
|
||||
code.append(('call', term, '', arg0))
|
||||
return stack, map_
|
||||
|
||||
|
||||
class BinaryBuiltin(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __call__(self, stack, expression, code):
|
||||
in1, (in0, stack) = stack
|
||||
out = InfiniteStack.names()
|
||||
code.append(('call', out, self.name, (in0, in1)))
|
||||
return (out, stack), expression, code
|
||||
|
||||
|
||||
YIN = import_yin()
|
||||
|
||||
|
||||
D = {
|
||||
name: SimpleFunctionWrapper(YIN[name])
|
||||
for name in '''
|
||||
ccons
|
||||
cons
|
||||
dup
|
||||
dupd
|
||||
dupdd
|
||||
over
|
||||
pop
|
||||
popd
|
||||
popdd
|
||||
popop
|
||||
popopd
|
||||
popopdd
|
||||
rolldown
|
||||
rollup
|
||||
swap
|
||||
swons
|
||||
tuck
|
||||
unit
|
||||
'''.split()
|
||||
}
|
||||
|
||||
|
||||
for name in '''
|
||||
first
|
||||
first_two
|
||||
fourth
|
||||
rest
|
||||
rrest
|
||||
second
|
||||
third
|
||||
uncons
|
||||
unswons
|
||||
'''.split():
|
||||
|
||||
def foo(stack, expression, code, name=name):
|
||||
in_, out = YIN_STACK_EFFECTS[name]
|
||||
stack, map_ = remap_inputs(in_, stack, code)
|
||||
out = type_vars_to_labels(out, map_)
|
||||
return concat(out, stack), expression, code
|
||||
|
||||
foo.__name__ = name
|
||||
D[name] = foo
|
||||
|
||||
|
||||
for name in '''
|
||||
eq
|
||||
ge
|
||||
gt
|
||||
le
|
||||
lt
|
||||
ne
|
||||
xor
|
||||
lshift
|
||||
rshift
|
||||
and_
|
||||
or_
|
||||
add
|
||||
floordiv
|
||||
mod
|
||||
mul
|
||||
pow
|
||||
sub
|
||||
truediv
|
||||
'''.split():
|
||||
D[name.rstrip('-')] = BinaryBuiltin(name)
|
||||
|
||||
|
||||
'''
|
||||
stack
|
||||
stuncons
|
||||
stununcons
|
||||
swaack
|
||||
'''
|
||||
|
||||
for name in sorted(D):
|
||||
print(name, end=' ')
|
||||
## print compile_yinyang(name, name)
|
||||
print('-' * 100)
|
||||
|
||||
|
||||
print(compile_yinyang('mul_', 'mul'))
|
||||
print(compile_yinyang('pop', 'pop'))
|
||||
print(compile_yinyang('ppm', 'popop mul'))
|
||||
print(compile_yinyang('sqr', 'dup mul'))
|
||||
print(compile_yinyang('foo', 'dup 23 sub mul'))
|
||||
print(compile_yinyang('four_mul', 'mul mul mul mul'))
|
||||
print(compile_yinyang('baz', 'mul dup sub dup'))
|
||||
print(compile_yinyang('to_the_fifth_power', 'dup dup mul dup mul mul'))
|
||||
print(compile_yinyang('dup3', 'dup dup dup'))
|
||||
print(compile_yinyang('df2m', 'dup first_two mul'))
|
||||
print(compile_yinyang('sqr_first', 'uncons swap dup mul swons'))
|
||||
print(compile_yinyang('0BAD', 'uncons dup mul'))
|
||||
print(compile_yinyang('uncons', 'uncons'))
|
||||
@@ -1,4 +1,6 @@
|
||||
# GENERATED FILE. DO NOT EDIT.
|
||||
# The code that generated these functions is in the repo history
|
||||
# at the v0.4.0 tag.
|
||||
|
||||
|
||||
def _Tree_add_Ee(stack):
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
from builtins import str
|
||||
from joy.parser import Symbol
|
||||
|
||||
|
||||
def _names():
|
||||
n = 0
|
||||
while True:
|
||||
yield Symbol('a' + str(n))
|
||||
n += 1
|
||||
|
||||
|
||||
class InfiniteStack(tuple):
|
||||
|
||||
names = lambda n=_names(): next(n)
|
||||
|
||||
def __iter__(self):
|
||||
if not self:
|
||||
return iter((self.names(), self))
|
||||
|
||||
|
||||
i = InfiniteStack()
|
||||
|
||||
a, b = i
|
||||
|
||||
lambda u: (lambda fu, u: fu * fu * u)(
|
||||
(lambda u: (lambda fu, u: fu * fu)(
|
||||
(lambda u: (lambda fu, u: fu * fu * u)(
|
||||
(lambda u: 1)(u), u))(u), u))(u),
|
||||
u)
|
||||
|
||||
lambda u: (lambda fu, u: fu * fu * u)((lambda u: (lambda fu, u: fu * fu)((lambda u: (lambda fu, u: fu * fu * u)((lambda u: 1)(u), u))(u), u))(u), u)
|
||||
@@ -1,756 +0,0 @@
|
||||
# -*- coding: utf_8
|
||||
#
|
||||
# Copyright © 2018 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import print_function
|
||||
from builtins import str
|
||||
from builtins import map
|
||||
from past.builtins import basestring
|
||||
from builtins import object
|
||||
from logging import getLogger, addLevelName
|
||||
from functools import reduce
|
||||
|
||||
_log = getLogger(__name__)
|
||||
addLevelName(15, 'hmm')
|
||||
|
||||
from collections import Counter
|
||||
from itertools import chain, product
|
||||
from inspect import stack as inspect_stack
|
||||
from joy.utils.stack import (
|
||||
concat,
|
||||
expression_to_string,
|
||||
list_to_stack,
|
||||
stack_to_string,
|
||||
)
|
||||
from joy.parser import Symbol, text_to_expression
|
||||
|
||||
|
||||
class AnyJoyType(object):
|
||||
'''
|
||||
Joy type variable. Represents any Joy value.
|
||||
'''
|
||||
|
||||
accept = tuple, int, float, int, complex, str, bool, Symbol
|
||||
prefix = 'a'
|
||||
|
||||
def __init__(self, number):
|
||||
self.number = number
|
||||
|
||||
def __repr__(self):
|
||||
return self.prefix + str(self.number)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and other.prefix == self.prefix
|
||||
and other.number == self.number
|
||||
)
|
||||
|
||||
def __ge__(self, other):
|
||||
return (
|
||||
issubclass(other.__class__, self.__class__)
|
||||
or isinstance(other, self.accept)
|
||||
)
|
||||
|
||||
def __le__(self, other):
|
||||
# 'a string' >= AnyJoyType() should be False.
|
||||
return issubclass(self.__class__, other.__class__)
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(self.number + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
|
||||
class BooleanJoyType(AnyJoyType):
|
||||
accept = bool
|
||||
prefix = 'b'
|
||||
|
||||
|
||||
class NumberJoyType(AnyJoyType):
|
||||
accept = bool, int, float, complex
|
||||
prefix = 'n'
|
||||
|
||||
|
||||
class FloatJoyType(NumberJoyType):
|
||||
accept = float
|
||||
prefix = 'f'
|
||||
|
||||
|
||||
class IntJoyType(FloatJoyType):
|
||||
accept = int
|
||||
prefix = 'i'
|
||||
|
||||
|
||||
class TextJoyType(AnyJoyType):
|
||||
accept = basestring
|
||||
prefix = 't'
|
||||
|
||||
|
||||
class StackJoyType(AnyJoyType):
|
||||
|
||||
accept = tuple
|
||||
prefix = 's'
|
||||
|
||||
def __bool__(self):
|
||||
# Imitate () at the end of cons list.
|
||||
return False
|
||||
|
||||
|
||||
class KleeneStar(object):
|
||||
u'''
|
||||
A sequence of zero or more `AnyJoyType` variables would be:
|
||||
|
||||
A*
|
||||
|
||||
The `A*` works by splitting the universe into two alternate histories:
|
||||
|
||||
A* → ∅
|
||||
|
||||
A* → A A*
|
||||
|
||||
The Kleene star variable disappears in one universe, and in the other
|
||||
it turns into an `AnyJoyType` variable followed by itself again.
|
||||
|
||||
We have to return all universes (represented by their substitution
|
||||
dicts, the "unifiers") that don't lead to type conflicts.
|
||||
'''
|
||||
|
||||
kind = AnyJoyType
|
||||
|
||||
def __init__(self, number):
|
||||
assert number
|
||||
self.number = number
|
||||
self.count = 0
|
||||
self.prefix = repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s%i*' % (self.kind.prefix, self.number)
|
||||
|
||||
def another(self):
|
||||
self.count += 1
|
||||
return self.kind(10000 * self.number + self.count)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and other.number == self.number
|
||||
)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.kind >= other.kind
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(self.number + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
|
||||
class AnyStarJoyType(KleeneStar): kind = AnyJoyType
|
||||
class NumberStarJoyType(KleeneStar): kind = NumberJoyType
|
||||
class FloatStarJoyType(KleeneStar): kind = FloatJoyType
|
||||
class IntStarJoyType(KleeneStar): kind = IntJoyType
|
||||
class StackStarJoyType(KleeneStar): kind = StackJoyType
|
||||
class TextStarJoyType(KleeneStar): kind = TextJoyType
|
||||
|
||||
|
||||
class FunctionJoyType(AnyJoyType):
|
||||
|
||||
def __init__(self, name, sec, number):
|
||||
self.name = name
|
||||
self.stack_effects = sec
|
||||
self.number = number
|
||||
|
||||
def __add__(self, other):
|
||||
return self
|
||||
__radd__ = __add__
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class SymbolJoyType(FunctionJoyType):
|
||||
'''
|
||||
Represent non-combinator functions.
|
||||
|
||||
These type variables carry the stack effect comments and can
|
||||
appear in expressions (as in quoted programs.)
|
||||
'''
|
||||
prefix = 'F'
|
||||
|
||||
|
||||
class CombinatorJoyType(FunctionJoyType):
|
||||
'''
|
||||
Represent combinators.
|
||||
|
||||
These type variables carry Joy functions that implement the
|
||||
behaviour of Joy combinators and they can appear in expressions.
|
||||
For simple combinators the implementation functions can be the
|
||||
combinators themselves.
|
||||
|
||||
These types can also specify a stack effect (input side only) to
|
||||
guard against being used on invalid types.
|
||||
'''
|
||||
|
||||
prefix = 'C'
|
||||
|
||||
def __init__(self, name, sec, number, expect=None):
|
||||
super(CombinatorJoyType, self).__init__(name, sec, number)
|
||||
self.expect = expect
|
||||
|
||||
def enter_guard(self, f):
|
||||
if self.expect is None:
|
||||
return f
|
||||
g = self.expect, self.expect
|
||||
new_f = list(poly_compose(f, g, ()))
|
||||
assert len(new_f) == 1, repr(new_f)
|
||||
return new_f[0][1]
|
||||
|
||||
|
||||
class JoyTypeError(Exception): pass
|
||||
|
||||
|
||||
def reify(meaning, name, seen=None):
|
||||
'''
|
||||
Apply substitution dict to term, returning new term.
|
||||
'''
|
||||
if isinstance(name, tuple):
|
||||
return tuple(reify(meaning, inner) for inner in name)
|
||||
safety = 101
|
||||
while name in meaning and safety:
|
||||
safety -= 1
|
||||
name = meaning[name]
|
||||
if not safety:
|
||||
raise ValueError('Cycle in substitution dict: %s' % (meaning,))
|
||||
return name
|
||||
|
||||
|
||||
def relabel(left, right):
|
||||
'''
|
||||
Re-number type variables to avoid collisions between stack effects.
|
||||
'''
|
||||
return left, _1000(right)
|
||||
|
||||
|
||||
def _1000(right):
|
||||
if isinstance(right, Symbol):
|
||||
return right
|
||||
if not isinstance(right, tuple):
|
||||
return 1000 + right
|
||||
return tuple(_1000(n) for n in right)
|
||||
|
||||
|
||||
def delabel(f, seen=None, c=None):
|
||||
'''
|
||||
Fix up type variable numbers after relabel().
|
||||
'''
|
||||
if seen is None:
|
||||
assert c is None
|
||||
seen, c = {}, Counter()
|
||||
|
||||
try:
|
||||
return seen[f]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not isinstance(f, tuple):
|
||||
try:
|
||||
seen[f] = f.__class__(c[f.prefix] + 1)
|
||||
except (TypeError, # FunctionJoyTypes break this.
|
||||
AttributeError): # Symbol
|
||||
seen[f] = f
|
||||
else:
|
||||
c[f.prefix] += 1
|
||||
return seen[f]
|
||||
|
||||
return tuple(delabel(inner, seen, c) for inner in f)
|
||||
|
||||
|
||||
def uni_unify(u, v, s=None):
|
||||
'''
|
||||
Return a substitution dict representing a unifier for u and v.
|
||||
'''
|
||||
if s is None:
|
||||
s = {}
|
||||
elif s:
|
||||
u = reify(s, u)
|
||||
v = reify(s, v)
|
||||
|
||||
if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
|
||||
if u >= v:
|
||||
s[u] = v
|
||||
elif v >= u:
|
||||
s[v] = u
|
||||
else:
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
elif isinstance(u, tuple) and isinstance(v, tuple):
|
||||
if len(u) != len(v) != 2:
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
(a, b), (c, d) = u, v
|
||||
s = uni_unify(b, d, uni_unify(a, c, s))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
s[u] = v
|
||||
|
||||
elif isinstance(u, tuple):
|
||||
if not _stacky(v):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
|
||||
s[v] = u
|
||||
|
||||
else:
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def _log_uni(U):
|
||||
def inner(u, v, s=None):
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s',
|
||||
len(inspect_stack()), u, v, s,
|
||||
)
|
||||
res = U(u, v, s)
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s => %s',
|
||||
len(inspect_stack()), u, v, s, res,
|
||||
)
|
||||
return res
|
||||
return inner
|
||||
|
||||
|
||||
@_log_uni
|
||||
def unify(u, v, s=None):
|
||||
'''
|
||||
Return a tuple of substitution dicts representing unifiers for u and v.
|
||||
'''
|
||||
if s is None:
|
||||
s = {}
|
||||
elif s:
|
||||
u = reify(s, u)
|
||||
v = reify(s, v)
|
||||
|
||||
if u == v:
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple) and isinstance(v, tuple):
|
||||
if len(u) != 2 or len(v) != 2:
|
||||
if _that_one_special_case(u, v):
|
||||
return s,
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
|
||||
|
||||
(a, b), (c, d) = v, u
|
||||
if isinstance(a, KleeneStar):
|
||||
if isinstance(c, KleeneStar):
|
||||
s = _lil_uni(a, c, s) # Attempt to unify the two K-stars.
|
||||
res = unify(d, b, s[0])
|
||||
|
||||
else:
|
||||
# Two universes, in one the Kleene star disappears and
|
||||
# unification continues without it...
|
||||
s0 = unify(u, b)
|
||||
|
||||
# In the other it spawns a new variable.
|
||||
s1 = unify(u, (a.another(), v))
|
||||
|
||||
res = s0 + s1
|
||||
for sn in res:
|
||||
sn.update(s)
|
||||
|
||||
elif isinstance(c, KleeneStar):
|
||||
res = unify(v, d) + unify(v, (c.another(), u))
|
||||
for sn in res:
|
||||
sn.update(s)
|
||||
|
||||
else:
|
||||
res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
s[u] = v
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple):
|
||||
if not _stacky(v):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
|
||||
s[v] = u
|
||||
res = s,
|
||||
|
||||
else:
|
||||
res = _lil_uni(u, v, s)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _that_one_special_case(u, v):
|
||||
'''
|
||||
Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc...
|
||||
'''
|
||||
return (
|
||||
u == ()
|
||||
and len(v) == 2
|
||||
and isinstance(v[0], KleeneStar)
|
||||
and isinstance(v[1], StackJoyType)
|
||||
)
|
||||
|
||||
|
||||
def flatten(g):
|
||||
return list(chain.from_iterable(g))
|
||||
|
||||
|
||||
def _lil_uni(u, v, s):
|
||||
if u >= v:
|
||||
s[u] = v
|
||||
return s,
|
||||
if v >= u:
|
||||
s[v] = u
|
||||
return s,
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
|
||||
def _stacky(thing):
|
||||
return thing.__class__ in {AnyJoyType, StackJoyType}
|
||||
|
||||
|
||||
def _compose(f, g):
|
||||
'''
|
||||
Return the stack effect of the composition of two stack effects.
|
||||
'''
|
||||
# Relabel, unify, update, delabel.
|
||||
(f_in, f_out), (g_in, g_out) = relabel(f, g)
|
||||
fg = reify(uni_unify(g_in, f_out), (f_in, g_out))
|
||||
return delabel(fg)
|
||||
|
||||
|
||||
def compose(*functions):
|
||||
'''
|
||||
Return the stack effect of the composition of some of stack effects.
|
||||
'''
|
||||
return reduce(_compose, functions)
|
||||
|
||||
|
||||
def compilable(f):
|
||||
'''
|
||||
Return True if a stack effect represents a function that can be
|
||||
automatically compiled (to Python), False otherwise.
|
||||
'''
|
||||
return isinstance(f, tuple) and all(map(compilable, f)) or _stacky(f)
|
||||
|
||||
|
||||
def doc_from_stack_effect(inputs, outputs=('??', ())):
|
||||
'''
|
||||
Return a crude string representation of a stack effect.
|
||||
'''
|
||||
switch = [False] # Do we need to display the '...' for the rest of the main stack?
|
||||
i, o = _f(inputs, switch), _f(outputs, switch)
|
||||
if switch[0]:
|
||||
i.append('...')
|
||||
o.append('...')
|
||||
return '(%s--%s)' % (
|
||||
' '.join(reversed([''] + i)),
|
||||
' '.join(reversed(o + [''])),
|
||||
)
|
||||
|
||||
|
||||
def _f(term, switch):
|
||||
a = []
|
||||
while term and isinstance(term, tuple):
|
||||
item, term = term
|
||||
a.append(item)
|
||||
assert isinstance(term, (tuple, StackJoyType)), repr(term)
|
||||
a = [_to_str(i, term, switch) for i in a]
|
||||
return a
|
||||
|
||||
|
||||
def _to_str(term, stack, switch):
|
||||
if not isinstance(term, tuple):
|
||||
if term == stack:
|
||||
switch[0] = True
|
||||
return '[...]'
|
||||
return (
|
||||
'[...%i]' % term.number
|
||||
if isinstance(term, StackJoyType)
|
||||
else str(term)
|
||||
)
|
||||
|
||||
a = []
|
||||
while term and isinstance(term, tuple):
|
||||
item, term = term
|
||||
a.append(_to_str(item, stack, switch))
|
||||
assert isinstance(term, (tuple, StackJoyType)), repr(term)
|
||||
if term == stack:
|
||||
switch[0] = True
|
||||
end = '' if term == () else '...'
|
||||
#end = '...'
|
||||
else:
|
||||
end = '' if term == () else '...%i' % term.number
|
||||
a.append(end)
|
||||
return '[%s]' % ' '.join(a)
|
||||
|
||||
|
||||
def compile_(name, f, doc=None):
|
||||
'''
|
||||
Return a string of Python code implementing the function described
|
||||
by the stack effect. If no doc string is passed doc_from_stack_effect()
|
||||
is used to generate one.
|
||||
'''
|
||||
i, o = f
|
||||
if doc is None:
|
||||
doc = doc_from_stack_effect(i, o)
|
||||
return '''def %s(stack):
|
||||
"""
|
||||
::
|
||||
|
||||
%s
|
||||
|
||||
"""
|
||||
%s = stack
|
||||
return %s''' % (name, doc, i, o)
|
||||
|
||||
|
||||
def _poly_compose(f, g, e):
|
||||
(f_in, f_out), (g_in, g_out) = f, g
|
||||
for s in unify(g_in, f_out):
|
||||
yield reify(s, (e, (f_in, g_out)))
|
||||
|
||||
|
||||
def poly_compose(f, g, e):
|
||||
'''
|
||||
Yield the stack effects of the composition of two stack effects. An
|
||||
expression is carried along and updated and yielded.
|
||||
'''
|
||||
f, g = relabel(f, g)
|
||||
for fg in _poly_compose(f, g, e):
|
||||
yield delabel(fg)
|
||||
|
||||
|
||||
def _meta_compose(F, G, e):
|
||||
for f, g in product(F, G):
|
||||
try:
|
||||
for result in poly_compose(f, g, e): yield result
|
||||
except JoyTypeError:
|
||||
pass
|
||||
|
||||
|
||||
def meta_compose(F, G, e):
|
||||
'''
|
||||
Yield the stack effects of the composition of two lists of stack
|
||||
effects. An expression is carried along and updated and yielded.
|
||||
'''
|
||||
res = sorted(set(_meta_compose(F, G, e)))
|
||||
if not res:
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (F, G))
|
||||
return res
|
||||
|
||||
|
||||
_S0 = StackJoyType(0)
|
||||
ID = _S0, _S0 # Identity function.
|
||||
|
||||
|
||||
def _infer(e, F=ID):
|
||||
if __debug__:
|
||||
_log_it(e, F)
|
||||
if not e:
|
||||
return [F]
|
||||
|
||||
n, e = e
|
||||
|
||||
if isinstance(n, SymbolJoyType):
|
||||
eFG = meta_compose([F], n.stack_effects, e)
|
||||
res = flatten(_infer(e, Fn) for e, Fn in eFG)
|
||||
|
||||
elif isinstance(n, CombinatorJoyType):
|
||||
fi, fo = n.enter_guard(F)
|
||||
res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
|
||||
|
||||
elif isinstance(n, Symbol):
|
||||
if n in FUNCTIONS:
|
||||
res =_infer((FUNCTIONS[n], e), F)
|
||||
else:
|
||||
raise JoyTypeError(n)
|
||||
# print n
|
||||
# func = joy.library._dictionary[n]
|
||||
# res = _interpret(func, F[0], F[1], e)
|
||||
|
||||
else:
|
||||
fi, fo = F
|
||||
res = _infer(e, (fi, (n, fo)))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _interpret(f, fi, fo, e):
|
||||
new_fo, ee, _ = f(fo, e, {})
|
||||
ee = reify(FUNCTIONS, ee) # Fix Symbols.
|
||||
new_F = fi, new_fo
|
||||
return _infer(ee, new_F)
|
||||
|
||||
|
||||
def _log_it(e, F):
|
||||
_log.log(
|
||||
15,
|
||||
u'%3i %s ∘ %s',
|
||||
len(inspect_stack()),
|
||||
doc_from_stack_effect(*F),
|
||||
expression_to_string(e),
|
||||
)
|
||||
|
||||
|
||||
def infer(*expression):
|
||||
'''
|
||||
Return a list of stack effects for a Joy expression.
|
||||
|
||||
For example::
|
||||
|
||||
h = infer(pop, swap, rolldown, rest, rest, cons, cons)
|
||||
for fi, fo in h:
|
||||
print doc_from_stack_effect(fi, fo)
|
||||
|
||||
Prints::
|
||||
|
||||
([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
|
||||
|
||||
'''
|
||||
return sorted(set(_infer(list_to_stack(expression))))
|
||||
|
||||
|
||||
def infer_string(string):
|
||||
e = reify(FUNCTIONS, text_to_expression(string)) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def infer_expression(expression):
|
||||
e = reify(FUNCTIONS, expression) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def type_check(name, stack):
|
||||
'''
|
||||
Trinary predicate. True if named function type-checks, False if it
|
||||
fails, None if it's indeterminate (because I haven't entered it into
|
||||
the FUNCTIONS dict yet.)
|
||||
'''
|
||||
try:
|
||||
func = FUNCTIONS[name]
|
||||
except KeyError:
|
||||
return # None, indicating unknown
|
||||
if isinstance(func, SymbolJoyType):
|
||||
secs = func.stack_effects
|
||||
elif isinstance(func, CombinatorJoyType):
|
||||
if func.expect is None:
|
||||
return # None, indicating unknown
|
||||
secs = [(func.expect, ())]
|
||||
else:
|
||||
raise TypeError(repr(func)) # wtf?
|
||||
for fi, fo in secs:
|
||||
try:
|
||||
unify(fi, stack)
|
||||
except (JoyTypeError, ValueError):
|
||||
continue
|
||||
except:
|
||||
_log.exception(
|
||||
'Type-checking %s %s against %s',
|
||||
name,
|
||||
doc_from_stack_effect(fi, fo),
|
||||
stack_to_string(stack),
|
||||
)
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
FUNCTIONS = {} # Polytypes (lists of stack effects.)
|
||||
_functions = {} # plain ol' stack effects.
|
||||
|
||||
|
||||
def __(*seq):
|
||||
stack = StackJoyType(23)
|
||||
for item in seq: stack = item, stack
|
||||
return stack
|
||||
|
||||
|
||||
def stack_effect(*inputs):
|
||||
def _stack_effect(*outputs):
|
||||
def _apply_to(function):
|
||||
i, o = _functions[function.name] = __(*inputs), __(*outputs)
|
||||
d = doc_from_stack_effect(i, o)
|
||||
function.__doc__ += (
|
||||
'\nStack effect::\n\n ' # '::' for Sphinx docs.
|
||||
+ d
|
||||
)
|
||||
_log.info('Setting stack effect for %s := %s', function.name, d)
|
||||
return function
|
||||
return _apply_to
|
||||
return _stack_effect
|
||||
|
||||
|
||||
def ef(*inputs):
|
||||
def _ef(*outputs):
|
||||
return __(*inputs), __(*outputs)
|
||||
return _ef
|
||||
|
||||
|
||||
def combinator_effect(number, *expect):
|
||||
def _combinator_effect(c):
|
||||
e = __(*expect) if expect else None
|
||||
FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number, e)
|
||||
if e:
|
||||
sec = doc_from_stack_effect(e)
|
||||
_log.info('Setting stack EXPECT for combinator %s := %s', c.name, sec)
|
||||
return c
|
||||
return _combinator_effect
|
||||
|
||||
|
||||
def show(DEFS):
|
||||
for name, stack_effect_comment in sorted(DEFS.items()):
|
||||
t = ' *'[compilable(stack_effect_comment)]
|
||||
print(name, '=', doc_from_stack_effect(*stack_effect_comment), t)
|
||||
|
||||
|
||||
def generate_library_code(DEFS, f=None):
|
||||
if f is None:
|
||||
import sys
|
||||
f = sys.stdout
|
||||
print('# GENERATED FILE. DO NOT EDIT.\n', file=f)
|
||||
for name, stack_effect_comment in sorted(DEFS.items()):
|
||||
if not compilable(stack_effect_comment):
|
||||
continue
|
||||
print(file=f)
|
||||
print(compile_(name, stack_effect_comment), file=f)
|
||||
print(file=f)
|
||||
|
||||
|
||||
def poly_combinator_effect(number, effect_funcs, *expect):
|
||||
def _poly_combinator_effect(c):
|
||||
e = __(*expect) if expect else None
|
||||
FUNCTIONS[c.name] = CombinatorJoyType(c.name, effect_funcs, number, e)
|
||||
if e:
|
||||
_log.info('Setting stack EXPECT for combinator %s := %s', c.name, e)
|
||||
return c
|
||||
return _poly_combinator_effect
|
||||
|
||||
#FUNCTIONS['branch'].expect = s7, (s6, (b1, s5))
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB |
@@ -1,163 +0,0 @@
|
||||
What is it?
|
||||
|
||||
A simple Graphical User Interface for the Joy programming language,
|
||||
written using Pygame to bypass X11 et. al., modeled on the Oberon OS, and
|
||||
intended to be just functional enough to support bootstrapping further Joy
|
||||
development.
|
||||
|
||||
It's basic functionality is more-or-less as a crude text editor along with
|
||||
a simple Joy runtime (interpreter, stack, and dictionary.) It auto- saves
|
||||
any named files (in a versioned home directory) and you can write new Joy
|
||||
primitives in Python and Joy definitions and immediately install and use
|
||||
them, as well as recording them for reuse (after restarts.)
|
||||
|
||||
How it works now.
|
||||
|
||||
The only dependencies are Pygame and Dulwich (a Python Git library.)
|
||||
|
||||
When the main.py script starts it checks for an environment var "JOY_HOME"
|
||||
which should point to a directory where you want the system to store the
|
||||
files ("resources") it will edit and save, this directory defaults to
|
||||
'~/.joypy'. The first time you run it, it will create some default files
|
||||
as content. Right click on see_resources to open a viewer with the list
|
||||
of resources (files), copy a name to the stack and right click on
|
||||
open_resource_at_good_location to open a viewer on that resource.
|
||||
|
||||
Right now the screen size defaults to windowed 1024x768, but if you pass
|
||||
the '-f' option to the main.py script the UI will take up the full screen
|
||||
at the highest available resolution. The window is divided into two (or
|
||||
three in fullscreen) vertical "tracks", and the number and width of the
|
||||
tracks are fixed at start up. (Feel free to edit the values in main.py to
|
||||
play around with different track configurations.) Each track gets divided
|
||||
horizontally into zero or more "viewers" (like windows in a windowed GUI,
|
||||
cf. Chapter 4 of "Project Oberon") for a kind of tiled layout.
|
||||
|
||||
Currently, there are only two kinds of (interesting) viewers: TextViewers
|
||||
and StackViewer. The TextViewers are crude text editors. They provide
|
||||
just enough functionality to let the user write text and code (Python and
|
||||
Joy) and execute Joy functions. One important thing they do is
|
||||
automatically save their content after changes. No more lost work.
|
||||
|
||||
The StackViewer is a specialized TextViewer that shows the contents of the
|
||||
Joy stack one line per stack item. It's a very handy visual aid to keep
|
||||
track of what's going on. There's also a log.txt file that gets written
|
||||
to when commands are executed, and so records the log of user actions and
|
||||
system events. It tends to fill up quickly so there's a reset_log command
|
||||
that clears it out.
|
||||
|
||||
Viewers have "grow" and "close" in their menu bars. These are buttons.
|
||||
When you right-click on grow a viewer a copy is created that covers that
|
||||
viewer's entire track. If you grow a viewer that already takes up its
|
||||
whole track then a copy is created that takes up an additional track, up
|
||||
to the whole screen. Closing a viewer just deletes that viewer, and when
|
||||
a track has no more viewers, it is deleted and that exposes any previous
|
||||
tracks and viewers that were hidden.
|
||||
|
||||
(Note: if you ever close all the viewers and are sitting at a blank screen
|
||||
with nowhere to type and execute commands, press the Pause/Break key.
|
||||
This will open a new "trap" viewer which you can then use to recover.)
|
||||
|
||||
Copies of a viewer all share the same model and update their display as it
|
||||
changes. (If you have two viewers open on the same named resource and edit
|
||||
one you'll see the other update as you type.)
|
||||
|
||||
UI Guide
|
||||
|
||||
left mouse sets cursor in text, in menu bar resizes viewer interactively
|
||||
(this is a little buggy in that you can move the mouse quickly and get
|
||||
outside the menu, leaving the viewer in the "resizing" state. Until I fix
|
||||
this, the workaround is to just grab the menu bar again and wiggle it a
|
||||
few pixels and let go. This will reset the machinery.)
|
||||
|
||||
Right mouse executes Joy command (functions), and you can drag with the
|
||||
right button to highlight (well, underline) commands. Words that aren't
|
||||
names of Joy commands won't be underlined. Release the button to execute
|
||||
the command.
|
||||
|
||||
The middle mouse button (usually a wheel these days) scrolls the text but
|
||||
you can also click and drag any viewer with it to move that viewer to
|
||||
another track or to a different location in the same track. There's no
|
||||
direct visual feedback for this (yet) but that dosen't seem to impair its
|
||||
usefulness.
|
||||
|
||||
F1, F2 - set selection begin and end markers (crude but usable.)
|
||||
|
||||
F3 - copy selected text to the top of the stack.
|
||||
|
||||
Shift-F3 - as copy then run "parse" command on the string.
|
||||
|
||||
F4 - cut selected text to the top of the stack.
|
||||
|
||||
Shift-F4 - as cut then run "pop" (delete selection.)
|
||||
|
||||
Joy
|
||||
|
||||
Pretty much all of the rest of the functionality of the system is provided
|
||||
by executing Joy commands (aka functions, aka "words" in Forth) by right-
|
||||
clicking on their names in any text.
|
||||
|
||||
To get help on a Joy function select the name of the function in a
|
||||
TextViewer using F1 and F2, then press shift-F3 to parse the selection.
|
||||
The function (really its Symbol) will appear on the stack in brackets (a
|
||||
"quoted program" such as "[pop]".) Then right-click on the word help in
|
||||
any TextViewer (if it's not already there, just type it in somewhere.)
|
||||
This will print the docstring or definition of the word (function) to
|
||||
stdout. At some point I'll write a thing to send that to the log.txt file
|
||||
instead, but for now look for output in the terminal.
|
||||
|
||||
I have pre-defined some system-specific commands, like see_stack to open a
|
||||
StackViewer, and I should really go and add docstrings to those (so they
|
||||
work with the help command.)
|
||||
|
||||
... inscribe and evaluate for making new Joy and Python, respectively,
|
||||
commands...
|
||||
|
||||
----
|
||||
|
||||
|
||||
Still to do:
|
||||
* Return key can orphan a line at the bottom of a viewer.
|
||||
* Calculator buttons on the numpad?
|
||||
* System query for most recent selection
|
||||
* Home/End keys
|
||||
* Vertical scrolling w/ scrollbar?
|
||||
* Shift-scroll changes viewer height?
|
||||
* Horizontal scrolling w/ keys
|
||||
* Horizontal scrolling w/ scrollbar?
|
||||
* Pgup/down keys?
|
||||
* Tab key?
|
||||
* When moving viewers sometimes a command gets executed from the underlying
|
||||
viewer. This shouldn't happen.
|
||||
|
||||
Done:
|
||||
- Redirect stdout to "print" to the log.
|
||||
- Initial contents for JOY_HOME.
|
||||
- Pause/Break to open a trap viewer (in case you close them all.)
|
||||
- "shutdown" signal to tell PT to commit outstanding changes.
|
||||
- Local library auto-loaded at start-time
|
||||
- library.py, primitives in Python
|
||||
- definitions.txt
|
||||
- Can name and persist a viewer on an unstored string(list).
|
||||
- Inscribe function
|
||||
- Reverse video, well, grey background, menu bars
|
||||
- PT scans JOY_HOME for resource lists
|
||||
- Capture and display tracebacks
|
||||
- StackViewer
|
||||
- Update log when stack changes
|
||||
- Open a resource list
|
||||
- Open a viewer on a (unstored) string
|
||||
- Selecting text
|
||||
- Copy and Cut
|
||||
- Paste
|
||||
- Menu text, commands and name or title
|
||||
- "print" to e.g. log
|
||||
- Command evaluation
|
||||
- Joy integration
|
||||
- Persistance of data
|
||||
- Content change notification
|
||||
- Vertical scrolling w/ keys
|
||||
- Vertical scrolling w/ mouse wheel
|
||||
- Enter/return key
|
||||
- Arrow keys wrap at line ends
|
||||
- Backspace/delete wrap at line ends
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import joy.vui.main
|
||||
|
||||
|
||||
joy.vui.main.main(*joy.vui.main.init())
|
||||
-282
@@ -1,282 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Core
|
||||
=====================
|
||||
|
||||
The core module defines a bunch of system-wide "constants" (some colors
|
||||
and PyGame event groups), the message classes for Oberon-style message
|
||||
passing, a "world" class that holds the main context for the system, and
|
||||
a mainloop class that manages the, uh, main loop (the PyGame event queue.)
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from builtins import object, str, range
|
||||
from sys import stderr
|
||||
from traceback import format_exc
|
||||
import pygame
|
||||
from joy.joy import run
|
||||
from joy.utils.stack import stack_to_string
|
||||
|
||||
|
||||
COMMITTER = 'Joy <auto-commit@example.com>'
|
||||
|
||||
|
||||
BLACK = FOREGROUND = 0, 0, 0
|
||||
GREY = 127, 127, 127
|
||||
WHITE = BACKGROUND = 255, 255, 255
|
||||
BLUE = 100, 100, 255
|
||||
GREEN = 70, 200, 70
|
||||
|
||||
|
||||
MOUSE_EVENTS = frozenset({
|
||||
pygame.MOUSEMOTION,
|
||||
pygame.MOUSEBUTTONDOWN,
|
||||
pygame.MOUSEBUTTONUP
|
||||
})
|
||||
'PyGame mouse events.'
|
||||
|
||||
ARROW_KEYS = frozenset({
|
||||
pygame.K_UP,
|
||||
pygame.K_DOWN,
|
||||
pygame.K_LEFT,
|
||||
pygame.K_RIGHT
|
||||
})
|
||||
'PyGame arrow key events.'
|
||||
|
||||
|
||||
TASK_EVENTS = tuple(range(pygame.USEREVENT, pygame.NUMEVENTS))
|
||||
'Keep track of all possible task events.'
|
||||
|
||||
AVAILABLE_TASK_EVENTS = set(TASK_EVENTS)
|
||||
'Task IDs that have not been assigned to a task.'
|
||||
|
||||
ALLOWED_EVENTS = [pygame.QUIT, pygame.KEYUP, pygame.KEYDOWN]
|
||||
ALLOWED_EVENTS.extend(MOUSE_EVENTS)
|
||||
ALLOWED_EVENTS.extend(TASK_EVENTS)
|
||||
'Event "mask" for PyGame event queue, we are only interested in these event types.'
|
||||
|
||||
|
||||
ERROR = -1
|
||||
PENDING = 0
|
||||
SUCCESS = 1
|
||||
# 'Message status codes... dunno if this is a good idea or not...
|
||||
|
||||
|
||||
class Message(object):
|
||||
'''Message base class. Contains ``sender`` field.'''
|
||||
def __init__(self, sender):
|
||||
self.sender = sender
|
||||
|
||||
|
||||
class CommandMessage(Message):
|
||||
'''For commands, adds ``command`` field.'''
|
||||
def __init__(self, sender, command):
|
||||
Message.__init__(self, sender)
|
||||
self.command = command
|
||||
|
||||
|
||||
class ModifyMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``subject`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, subject, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.subject = subject
|
||||
self.details = details
|
||||
|
||||
|
||||
class OpenMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``name``, content_id``,
|
||||
``status``, and ``traceback`` fields.
|
||||
'''
|
||||
def __init__(self, sender, name):
|
||||
Message.__init__(self, sender)
|
||||
self.name = name
|
||||
self.content_id = self.thing = None
|
||||
self.status = PENDING
|
||||
self.traceback = None
|
||||
|
||||
|
||||
class PersistMessage(Message):
|
||||
'''
|
||||
For when resources are modified, adds ``content_id`` and ``details``
|
||||
fields.
|
||||
'''
|
||||
def __init__(self, sender, content_id, **details):
|
||||
Message.__init__(self, sender)
|
||||
self.content_id = content_id
|
||||
self.details = details
|
||||
|
||||
|
||||
class ShutdownMessage(Message):
|
||||
'''Signals that the system is shutting down.'''
|
||||
|
||||
|
||||
# Joy Interpreter & Context
|
||||
|
||||
|
||||
class World(object):
|
||||
'''
|
||||
This object contains the system context, the stack, dictionary, a
|
||||
reference to the display broadcast method, and the log.
|
||||
'''
|
||||
|
||||
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
|
||||
self.stack_holder = stack_holder
|
||||
self.dictionary = dictionary
|
||||
self.notify = notify
|
||||
self.stack_id = stack_id
|
||||
self.log = log.lines
|
||||
self.log_id = log.content_id
|
||||
|
||||
def handle(self, message):
|
||||
'''
|
||||
Deal with updates to the stack and commands.
|
||||
'''
|
||||
if (isinstance(message, ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._log_lines('', '%s <-' % self.format_stack())
|
||||
if not isinstance(message, CommandMessage):
|
||||
return
|
||||
c, s, d = message.command, self.stack_holder[0], self.dictionary
|
||||
self._log_lines('', '-> %s' % (c,))
|
||||
self.stack_holder[0], _, self.dictionary = run(c, s, d)
|
||||
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
|
||||
self.notify(mm)
|
||||
|
||||
def _log_lines(self, *lines):
|
||||
self.log.extend(lines)
|
||||
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
|
||||
|
||||
def format_stack(self):
|
||||
try:
|
||||
return stack_to_string(self.stack_holder[0])
|
||||
except:
|
||||
print(format_exc(), file=stderr)
|
||||
return str(self.stack_holder[0])
|
||||
|
||||
|
||||
def push(sender, item, notify, stack_name='stack.pickle'):
|
||||
'''
|
||||
Helper function to push an item onto the system stack with message.
|
||||
'''
|
||||
om = OpenMessage(sender, stack_name)
|
||||
notify(om)
|
||||
if om.status == SUCCESS:
|
||||
om.thing[0] = item, om.thing[0]
|
||||
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
|
||||
return om.status
|
||||
|
||||
|
||||
def open_viewer_on_string(sender, content, notify):
|
||||
'''
|
||||
Helper function to open a text viewer on a string.
|
||||
Typically used to show tracebacks.
|
||||
'''
|
||||
push(sender, content, notify)
|
||||
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
|
||||
|
||||
|
||||
# main loop
|
||||
|
||||
|
||||
class TheLoop(object):
|
||||
'''
|
||||
The main loop manages tasks and the PyGame event queue
|
||||
and framerate clock.
|
||||
'''
|
||||
|
||||
FRAME_RATE = 24
|
||||
|
||||
def __init__(self, display, clock):
|
||||
self.display = display
|
||||
self.clock = clock
|
||||
self.tasks = {}
|
||||
self.running = False
|
||||
|
||||
def install_task(self, F, milliseconds):
|
||||
'''
|
||||
Install a task to run every so many milliseconds.
|
||||
'''
|
||||
try:
|
||||
task_event_id = AVAILABLE_TASK_EVENTS.pop()
|
||||
except KeyError:
|
||||
raise RuntimeError('out of task ids')
|
||||
self.tasks[task_event_id] = F
|
||||
pygame.time.set_timer(task_event_id, milliseconds)
|
||||
return task_event_id
|
||||
|
||||
def remove_task(self, task_event_id):
|
||||
'''
|
||||
Remove an installed task.
|
||||
'''
|
||||
assert task_event_id in self.tasks, repr(task_event_id)
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
del self.tasks[task_event_id]
|
||||
AVAILABLE_TASK_EVENTS.add(task_event_id)
|
||||
|
||||
def __del__(self):
|
||||
# Best effort to cancel all running tasks.
|
||||
for task_event_id in self.tasks:
|
||||
pygame.time.set_timer(task_event_id, 0)
|
||||
|
||||
def run_task(self, task_event_id):
|
||||
'''
|
||||
Give a task its time to shine.
|
||||
'''
|
||||
task = self.tasks[task_event_id]
|
||||
try:
|
||||
task()
|
||||
except:
|
||||
traceback = format_exc()
|
||||
self.remove_task(task_event_id)
|
||||
print(traceback, file=stderr)
|
||||
print('TASK removed due to ERROR', task, file=stderr)
|
||||
open_viewer_on_string(self, traceback, self.display.broadcast)
|
||||
|
||||
def loop(self):
|
||||
'''
|
||||
The actual main loop machinery.
|
||||
|
||||
Maintain a ``running`` flag, pump the PyGame event queue and
|
||||
handle the events (dispatching to the display), tick the clock.
|
||||
|
||||
When the loop is exited (by clicking the window close button or
|
||||
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
|
||||
'''
|
||||
self.running = True
|
||||
while self.running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
self.running = False
|
||||
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
|
||||
self.running = False
|
||||
elif event.type in self.tasks:
|
||||
self.run_task(event.type)
|
||||
else:
|
||||
self.display.dispatch_event(event)
|
||||
pygame.display.update()
|
||||
self.clock.tick(self.FRAME_RATE)
|
||||
self.display.broadcast(ShutdownMessage(self))
|
||||
@@ -1,19 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
import sys, traceback
|
||||
|
||||
# To enable "hot" reloading in the IDLE shell.
|
||||
for name in 'core main display viewer text_viewer stack_viewer persist_task'.split():
|
||||
try:
|
||||
del sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
from . import main
|
||||
|
||||
try:
|
||||
A = A # (screen, clock, pt), three things that we DON'T want to recreate
|
||||
# each time we restart main().
|
||||
except NameError:
|
||||
A = main.init()
|
||||
|
||||
d = main.main(*A)
|
||||
@@ -1,17 +0,0 @@
|
||||
see_stack == good_viewer_location open_stack
|
||||
see_resources == list_resources good_viewer_location open_viewer
|
||||
open_resource_at_good_location == good_viewer_location open_resource
|
||||
see_log == "log.txt" open_resource_at_good_location
|
||||
see_definitions == "definitions.txt" open_resource_at_good_location
|
||||
round_to_cents == 100 * ++ floor 100 /
|
||||
reset_log == "del log.lines[1:] ; log.at_line = 0" evaluate
|
||||
see_menu == "menu.txt" good_viewer_location open_resource
|
||||
|
||||
# Ordered Binary Tree datastructure functions.
|
||||
BTree-new == swap [[] []] cons cons
|
||||
_BTree-P == over [popop popop first] nullary
|
||||
_BTree-T> == [cons cons dipdd] cons cons cons infra
|
||||
_BTree-T< == [cons cons dipd] cons cons cons infra
|
||||
_BTree-E == pop swap roll< rest rest cons cons
|
||||
_BTree-recur == _BTree-P [_BTree-T>] [_BTree-E] [_BTree-T<] cmp
|
||||
BTree-add == [popop not] [[pop] dipd BTree-new] [] [_BTree-recur] genrec
|
||||
@@ -1,206 +0,0 @@
|
||||
'''
|
||||
This file is execfile()'d with a namespace containing:
|
||||
|
||||
D - the Joy dictionary
|
||||
d - the Display object
|
||||
pt - the PersistTask object
|
||||
log - the log.txt viewer
|
||||
loop - the TheLoop main loop object
|
||||
stack_holder - the Python list object that holds the Joy stack tuple
|
||||
world - the Joy environment
|
||||
|
||||
'''
|
||||
from joy.library import (
|
||||
DefinitionWrapper,
|
||||
FunctionWrapper,
|
||||
SimpleFunctionWrapper,
|
||||
)
|
||||
from joy.utils.stack import list_to_stack, concat
|
||||
from joy.vui import core, text_viewer, stack_viewer
|
||||
|
||||
|
||||
def install(command): D[command.name] = command
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def list_resources(stack):
|
||||
'''
|
||||
Put a string on the stack with the names of all the known resources
|
||||
one-per-line.
|
||||
'''
|
||||
return '\n'.join(pt.scan()), stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def open_stack(stack):
|
||||
'''
|
||||
Given a coordinate pair [x y] (in pixels) open a StackViewer there.
|
||||
'''
|
||||
(x, (y, _)), stack = stack
|
||||
V = d.open_viewer(x, y, stack_viewer.StackViewer)
|
||||
V.draw()
|
||||
return stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def open_resource(stack):
|
||||
'''
|
||||
Given a coordinate pair [x y] (in pixels) and the name of a resource
|
||||
(from list_resources command) open a viewer on that resource at that
|
||||
location.
|
||||
'''
|
||||
((x, (y, _)), (name, stack)) = stack
|
||||
om = core.OpenMessage(world, name)
|
||||
d.broadcast(om)
|
||||
if om.status == core.SUCCESS:
|
||||
V = d.open_viewer(x, y, text_viewer.TextViewer)
|
||||
V.content_id, V.lines = om.content_id, om.thing
|
||||
V.draw()
|
||||
return stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def name_viewer(stack):
|
||||
'''
|
||||
Given a string name on the stack, if the currently focused viewer is
|
||||
anonymous, name the viewer and persist it in the resource store under
|
||||
that name.
|
||||
'''
|
||||
name, stack = stack
|
||||
assert isinstance(name, str), repr(name)
|
||||
if d.focused_viewer and not d.focused_viewer.content_id:
|
||||
d.focused_viewer.content_id = name
|
||||
pm = core.PersistMessage(world, name, thing=d.focused_viewer.lines)
|
||||
d.broadcast(pm)
|
||||
d.focused_viewer.draw_menu()
|
||||
return stack
|
||||
|
||||
|
||||
##@install
|
||||
##@SimpleFunctionWrapper
|
||||
##def persist_viewer(stack):
|
||||
## if self.focused_viewer:
|
||||
##
|
||||
## self.focused_viewer.content_id = name
|
||||
## self.focused_viewer.draw_menu()
|
||||
## return stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def inscribe(stack):
|
||||
'''
|
||||
Create a new Joy function definition in the Joy dictionary. A
|
||||
definition is given as a string with a name followed by a double
|
||||
equal sign then one or more Joy functions, the body. for example:
|
||||
|
||||
sqr == dup mul
|
||||
|
||||
If you want the definition to persist over restarts, enter it into
|
||||
the definitions.txt resource.
|
||||
'''
|
||||
definition, stack = stack
|
||||
DefinitionWrapper.add_def(definition, D)
|
||||
return stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def open_viewer(stack):
|
||||
'''
|
||||
Given a coordinate pair [x y] (in pixels) and a string, open a new
|
||||
unnamed viewer on that string at that location.
|
||||
'''
|
||||
((x, (y, _)), (content, stack)) = stack
|
||||
V = d.open_viewer(x, y, text_viewer.TextViewer)
|
||||
V.lines = content.splitlines()
|
||||
V.draw()
|
||||
return stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def good_viewer_location(stack):
|
||||
'''
|
||||
Leave a coordinate pair [x y] (in pixels) on the stack that would
|
||||
be a good location at which to open a new viewer. (The heuristic
|
||||
employed is to take up the bottom half of the currently open viewer
|
||||
with the greatest area.)
|
||||
'''
|
||||
viewers = list(d.iter_viewers())
|
||||
if viewers:
|
||||
viewers.sort(key=lambda (V, x, y): V.w * V.h)
|
||||
V, x, y = viewers[-1]
|
||||
coords = (x + 1, (y + V.h / 2, ()))
|
||||
else:
|
||||
coords = (0, (0, ()))
|
||||
return coords, stack
|
||||
|
||||
|
||||
@install
|
||||
@FunctionWrapper
|
||||
def cmp_(stack, expression, dictionary):
|
||||
'''
|
||||
The cmp combinator takes two values and three quoted programs on the
|
||||
stack and runs one of the three depending on the results of comparing
|
||||
the two values:
|
||||
|
||||
a b [G] [E] [L] cmp
|
||||
------------------------- a > b
|
||||
G
|
||||
|
||||
a b [G] [E] [L] cmp
|
||||
------------------------- a = b
|
||||
E
|
||||
|
||||
a b [G] [E] [L] cmp
|
||||
------------------------- a < b
|
||||
L
|
||||
|
||||
'''
|
||||
L, (E, (G, (b, (a, stack)))) = stack
|
||||
expression = concat(G if a > b else L if a < b else E, expression)
|
||||
return stack, expression, dictionary
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def list_viewers(stack):
|
||||
'''
|
||||
Put a string on the stack with some information about the currently
|
||||
open viewers, one-per-line. This is kind of a demo function, rather
|
||||
than something really useful.
|
||||
'''
|
||||
lines = []
|
||||
for x, T in d.tracks:
|
||||
#lines.append('x: %i, w: %i, %r' % (x, T.w, T))
|
||||
for y, V in T.viewers:
|
||||
lines.append('x: %i y: %i h: %i %r %r' % (x, y, V.h, V.content_id, V))
|
||||
return '\n'.join(lines), stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def splitlines(stack):
|
||||
'''
|
||||
Given a string on the stack replace it with a list of the lines in
|
||||
the string.
|
||||
'''
|
||||
text, stack = stack
|
||||
assert isinstance(text, str), repr(text)
|
||||
return list_to_stack(text.splitlines()), stack
|
||||
|
||||
|
||||
@install
|
||||
@SimpleFunctionWrapper
|
||||
def hiya(stack):
|
||||
'''
|
||||
Demo function to insert "Hi World!" into the current viewer, if any.
|
||||
'''
|
||||
if d.focused_viewer:
|
||||
d.focused_viewer.insert('Hi World!')
|
||||
return stack
|
||||
@@ -1 +0,0 @@
|
||||
Joypy log
|
||||
@@ -1,51 +0,0 @@
|
||||
name_viewer
|
||||
list_resources
|
||||
open_resource_at_good_location
|
||||
good_viewer_location
|
||||
open_viewer
|
||||
see_stack
|
||||
see_resources
|
||||
see_definitions
|
||||
see_log
|
||||
reset_log
|
||||
|
||||
inscribe
|
||||
evaluate
|
||||
|
||||
pop clear dup swap
|
||||
|
||||
add sub mul div truediv modulus divmod
|
||||
pm ++ -- sum product pow sqr sqrt
|
||||
< <= = >= > <>
|
||||
& << >>
|
||||
|
||||
i dupdip
|
||||
|
||||
!= % & * *fraction *fraction0 + ++ - -- / < << <= <> = > >= >> ? ^
|
||||
abs add anamorphism and app1 app2 app3 at average
|
||||
b binary branch
|
||||
choice clear cleave concat cons
|
||||
dinfrirst dip dipd dipdd disenstacken div divmod down_to_zero drop
|
||||
dudipd dup dupd dupdip
|
||||
enstacken eq
|
||||
first flatten floor floordiv
|
||||
gcd ge genrec getitem grand_reset gt
|
||||
help
|
||||
i id ifte infra inscribe
|
||||
key_bindings
|
||||
le least_fraction loop lshift lt
|
||||
map max min mod modulus mouse_bindings mul
|
||||
ne neg not nullary
|
||||
of or over
|
||||
pam parse pick pm pop popd popdd popop pow pred primrec product
|
||||
quoted
|
||||
range range_to_zero rem remainder remove reset_log rest reverse
|
||||
roll< roll> rolldown rollup rshift run
|
||||
second select sharing show_log shunt size sort sqr sqrt stack step
|
||||
step_zero sub succ sum swaack swap swoncat swons
|
||||
take ternary third times truediv truthy tuck
|
||||
unary uncons unique unit unquoted unstack
|
||||
void
|
||||
warranty while words
|
||||
x xor
|
||||
zip
|
||||
@@ -1,85 +0,0 @@
|
||||
What is it?
|
||||
|
||||
A simple Graphical User Interface for the Joy programming language,
|
||||
written using Pygame to bypass X11 et. al., modeled on the Oberon OS, and
|
||||
intended to be just functional enough to support bootstrapping further Joy
|
||||
development.
|
||||
|
||||
It's basic functionality is more-or-less as a crude text editor along with
|
||||
a simple Joy runtime (interpreter, stack, and dictionary.) It auto- saves
|
||||
any named files (in a versioned home directory) and you can write new Joy
|
||||
primitives in Python and Joy definitions and immediately install and use
|
||||
them, as well as recording them for reuse (after restarts.)
|
||||
|
||||
Currently, there are only two kinds of (interesting) viewers: TextViewers
|
||||
and StackViewer. The TextViewers are crude text editors. They provide
|
||||
just enough functionality to let the user write text and code (Python and
|
||||
Joy) and execute Joy functions. One important thing they do is
|
||||
automatically save their content after changes. No more lost work.
|
||||
|
||||
The StackViewer is a specialized TextViewer that shows the contents of the
|
||||
Joy stack one line per stack item. It's a very handy visual aid to keep
|
||||
track of what's going on. There's also a log.txt file that gets written
|
||||
to when commands are executed, and so records the log of user actions and
|
||||
system events. It tends to fill up quickly so there's a reset_log command
|
||||
that clears it out.
|
||||
|
||||
Viewers have "grow" and "close" in their menu bars. These are buttons.
|
||||
When you right-click on grow a viewer a copy is created that covers that
|
||||
viewer's entire track. If you grow a viewer that already takes up its
|
||||
whole track then a copy is created that takes up an additional track, up
|
||||
to the whole screen. Closing a viewer just deletes that viewer, and when
|
||||
a track has no more viewers, it is deleted and that exposes any previous
|
||||
tracks and viewers that were hidden.
|
||||
|
||||
(Note: if you ever close all the viewers and are sitting at a blank screen
|
||||
with nowhere to type and execute commands, press the Pause/Break key.
|
||||
This will open a new "trap" viewer which you can then use to recover.)
|
||||
|
||||
Copies of a viewer all share the same model and update their display as it
|
||||
changes. (If you have two viewers open on the same named resource and edit
|
||||
one you'll see the other update as you type.)
|
||||
|
||||
UI Guide
|
||||
|
||||
left mouse sets cursor in text, in menu bar resizes viewer interactively
|
||||
(this is a little buggy in that you can move the mouse quickly and get
|
||||
outside the menu, leaving the viewer in the "resizing" state. Until I fix
|
||||
this, the workaround is to just grab the menu bar again and wiggle it a
|
||||
few pixels and let go. This will reset the machinery.)
|
||||
|
||||
Right mouse executes Joy command (functions), and you can drag with the
|
||||
right button to highlight (well, underline) commands. Words that aren't
|
||||
names of Joy commands won't be underlined. Release the button to execute
|
||||
the command.
|
||||
|
||||
The middle mouse button (usually a wheel these days) scrolls the text but
|
||||
you can also click and drag any viewer with it to move that viewer to
|
||||
another track or to a different location in the same track. There's no
|
||||
direct visual feedback for this (yet) but that dosen't seem to impair its
|
||||
usefulness.
|
||||
|
||||
F1, F2 - set selection begin and end markers (crude but usable.)
|
||||
|
||||
F3 - copy selected text to the top of the stack.
|
||||
|
||||
Shift-F3 - as copy then run "parse" command on the string.
|
||||
|
||||
F4 - cut selected text to the top of the stack.
|
||||
|
||||
Shift-F4 - as cut then run "pop" (delete selection.)
|
||||
|
||||
Joy
|
||||
|
||||
Pretty much all of the rest of the functionality of the system is provided
|
||||
by executing Joy commands (aka functions, aka "words" in Forth) by right-
|
||||
clicking on their names in any text.
|
||||
|
||||
To get help on a Joy function select the name of the function in a
|
||||
TextViewer using F1 and F2, then press shift-F3 to parse the selection.
|
||||
The function (really its Symbol) will appear on the stack in brackets (a
|
||||
"quoted program" such as "[pop]".) Then right-click on the word help in
|
||||
any TextViewer (if it's not already there, just type it in somewhere.)
|
||||
This will print the docstring or definition of the word (function) to
|
||||
stdout. At some point I'll write a thing to send that to the log.txt file
|
||||
instead, but for now look for output in the terminal.
|
||||
@@ -1 +0,0 @@
|
||||
(t.
|
||||
@@ -1,510 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Display
|
||||
=================
|
||||
|
||||
This module implements a simple visual display system modeled on Oberon.
|
||||
|
||||
Refer to Chapter 4 of the Project Oberon book for more information.
|
||||
|
||||
There is a Display object that manages a pygame surface and N vertical
|
||||
tracks each of which manages zero or more viewers.
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from builtins import next, object
|
||||
from past.utils import old_div
|
||||
from copy import copy
|
||||
from sys import stderr
|
||||
from traceback import format_exc
|
||||
import pygame
|
||||
from .core import (
|
||||
open_viewer_on_string,
|
||||
GREY,
|
||||
MOUSE_EVENTS,
|
||||
)
|
||||
from .viewer import Viewer
|
||||
from joy.vui import text_viewer
|
||||
|
||||
|
||||
class Display(object):
|
||||
'''
|
||||
Manage tracks and viewers on a screen (Pygame surface.)
|
||||
|
||||
The size and number of tracks are defined by passing in at least two
|
||||
ratios, e.g. Display(screen, 1, 4, 4) would create three tracks, one
|
||||
small one on the left and two larger ones of the same size, each four
|
||||
times wider than the left one.
|
||||
|
||||
All tracks take up the whole height of the display screen. Tracks
|
||||
manage zero or more Viewers. When you "grow" a viewer a new track is
|
||||
created that overlays or hides one or two existing tracks, and when
|
||||
the last viewer in an overlay track is closed the track closes too
|
||||
and reveals the hidden tracks (and their viewers, if any.)
|
||||
|
||||
In order to facilitate command underlining while mouse dragging the
|
||||
lookup parameter must be a function that accepts a string and returns
|
||||
a Boolean indicating whether that string is a valid Joy function name.
|
||||
Typically you pass in the __contains__ method of the Joy dict. This
|
||||
is a case of breaking "loose coupling" to gain efficiency, as otherwise
|
||||
we would have to e.g. send some sort of lookup message to the
|
||||
World context object, going through the whole Display.broadcast()
|
||||
machinery, etc. Not something you want to do on each MOUSEMOTION
|
||||
event.
|
||||
'''
|
||||
|
||||
def __init__(self, screen, lookup, *track_ratios):
|
||||
self.screen = screen
|
||||
self.w, self.h = screen.get_width(), screen.get_height()
|
||||
self.lookup = lookup
|
||||
self.focused_viewer = None
|
||||
self.tracks = [] # (x, track)
|
||||
self.handlers = [] # Non-viewers that should receive messages.
|
||||
# Create the tracks.
|
||||
if not track_ratios: track_ratios = 1, 4
|
||||
x, total = 0, sum(track_ratios)
|
||||
for ratio in track_ratios[:-1]:
|
||||
track_width = old_div(self.w * ratio, total)
|
||||
assert track_width >= 10 # minimum width 10 pixels
|
||||
self._open_track(x, track_width)
|
||||
x += track_width
|
||||
self._open_track(x, self.w - x)
|
||||
|
||||
def _open_track(self, x, w):
|
||||
'''Helper function to create the pygame surface and Track.'''
|
||||
track_surface = self.screen.subsurface((x, 0, w, self.h))
|
||||
self.tracks.append((x, Track(track_surface)))
|
||||
|
||||
def open_viewer(self, x, y, class_):
|
||||
'''
|
||||
Open a viewer of class_ at the x, y location on the display,
|
||||
return the viewer.
|
||||
'''
|
||||
track = self._track_at(x)[0]
|
||||
V = track.open_viewer(y, class_)
|
||||
V.focus(self)
|
||||
return V
|
||||
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer.'''
|
||||
for x, track in self.tracks:
|
||||
if track.close_viewer(viewer):
|
||||
if not track.viewers and track.hiding:
|
||||
i = self.tracks.index((x, track))
|
||||
self.tracks[i:i + 1] = track.hiding
|
||||
assert sorted(self.tracks) == self.tracks
|
||||
for _, exposed_track in track.hiding:
|
||||
exposed_track.redraw()
|
||||
if viewer is self.focused_viewer:
|
||||
self.focused_viewer = None
|
||||
break
|
||||
|
||||
def change_viewer(self, viewer, y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
if track.change_viewer(viewer, y, relative):
|
||||
break
|
||||
|
||||
def grow_viewer(self, viewer):
|
||||
'''
|
||||
Cause the viewer to take up its whole track or, if it does
|
||||
already, take up another track, up to the whole screen.
|
||||
|
||||
This is the inverse of closing a viewer. "Growing" a viewer
|
||||
actually creates a new copy and a new track to hold it. The old
|
||||
tracks and viewers are retained, and they get restored when the
|
||||
covering track closes, which happens automatically when the last
|
||||
viewer in the covering track is closed.
|
||||
'''
|
||||
for x, track in self.tracks:
|
||||
for _, V in track.viewers:
|
||||
if V is viewer:
|
||||
return self._grow_viewer(x, track, viewer)
|
||||
|
||||
def _grow_viewer(self, x, track, viewer):
|
||||
'''Helper function to "grow" a viewer.'''
|
||||
new_viewer = None
|
||||
|
||||
if viewer.h < self.h:
|
||||
# replace the track with a new track that contains
|
||||
# a copy of the viewer at full height.
|
||||
new_track = Track(track.surface) # Reuse it, why not?
|
||||
new_viewer = copy(viewer)
|
||||
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = [(x, track)]
|
||||
self.tracks[self.tracks.index((x, track))] = x, new_track
|
||||
|
||||
elif viewer.w < self.w:
|
||||
# replace two tracks
|
||||
i = self.tracks.index((x, track))
|
||||
try: # prefer the one on the right
|
||||
xx, xtrack = self.tracks[i + 1]
|
||||
except IndexError:
|
||||
i -= 1 # okay, the one on the left
|
||||
xx, xtrack = self.tracks[i]
|
||||
hiding = [(xx, xtrack), (x, track)]
|
||||
else:
|
||||
hiding = [(x, track), (xx, xtrack)]
|
||||
# We know there has to be at least one other track because it
|
||||
# there weren't then that implies that the one track takes up
|
||||
# the whole display screen (the only way you can get just one
|
||||
# track is by growing a viewer to cover the whole screen.)
|
||||
# Ergo, viewer.w == self.w, so this branch doesn't run.
|
||||
new_x = min(x, xx)
|
||||
new_w = track.w + xtrack.w
|
||||
r = new_x, 0, new_w, self.h
|
||||
new_track = Track(self.screen.subsurface(r))
|
||||
new_viewer = copy(viewer)
|
||||
r = 0, 0, new_w, self.h
|
||||
new_viewer.resurface(new_track.surface.subsurface(r))
|
||||
new_track.viewers.append((0, new_viewer))
|
||||
new_track.hiding = hiding
|
||||
self.tracks[i:i + 2] = [(new_x, new_track)]
|
||||
new_viewer.draw()
|
||||
|
||||
return new_viewer
|
||||
|
||||
def _move_viewer(self, to, rel_y, viewer, _x, y):
|
||||
'''
|
||||
Helper function to move (really copy) a viewer to a new location.
|
||||
'''
|
||||
h = to.split(rel_y)
|
||||
new_viewer = copy(viewer)
|
||||
if not isinstance(to, Track):
|
||||
to = next(T for _, T in self.tracks
|
||||
for _, V in T.viewers
|
||||
if V is to)
|
||||
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
|
||||
to.viewers.append((y, new_viewer))
|
||||
to.viewers.sort() # bisect.insort() would be overkill here.
|
||||
new_viewer.draw()
|
||||
self.close_viewer(viewer)
|
||||
|
||||
def _track_at(self, x):
|
||||
'''
|
||||
Return the track at x along with the track-relative x coordinate,
|
||||
raise ValueError if x is off-screen.
|
||||
'''
|
||||
for track_x, track in self.tracks:
|
||||
if x < track_x + track.w:
|
||||
return track, x - track_x
|
||||
raise ValueError('x outside display: %r' % (x,))
|
||||
|
||||
def at(self, x, y):
|
||||
'''
|
||||
Return the viewer (which can be a Track) at the x, y location,
|
||||
along with the relative-to-viewer-surface x and y coordinates.
|
||||
If there is no viewer at the location the Track will be returned
|
||||
instead.
|
||||
'''
|
||||
track, x = self._track_at(x)
|
||||
viewer, y = track.viewer_at(y)
|
||||
return viewer, x, y
|
||||
|
||||
def iter_viewers(self):
|
||||
'''
|
||||
Iterate through all viewers yielding (viewer, x, y) three-tuples.
|
||||
The x and y coordinates are screen pixels of the top-left corner
|
||||
of the viewer.
|
||||
'''
|
||||
for x, T in self.tracks:
|
||||
for y, V in T.viewers:
|
||||
yield V, x, y
|
||||
|
||||
def done_resizing(self):
|
||||
'''
|
||||
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
|
||||
update the display when done resizing a viewer.
|
||||
'''
|
||||
for _, track in self.tracks: # This should be done by a Message?
|
||||
if track.resizing_viewer:
|
||||
track.resizing_viewer.draw()
|
||||
track.resizing_viewer = None
|
||||
break
|
||||
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers (except the sender) and all
|
||||
registered handlers.
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.broadcast(message)
|
||||
for handler in self.handlers:
|
||||
handler(message)
|
||||
|
||||
def redraw(self):
|
||||
'''
|
||||
Redraw all tracks (which will redraw all viewers.)
|
||||
'''
|
||||
for _, track in self.tracks:
|
||||
track.redraw()
|
||||
|
||||
def focus(self, viewer):
|
||||
'''
|
||||
Set system focus to a given viewer (or no viewer if a track.)
|
||||
'''
|
||||
if isinstance(viewer, Track):
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = None
|
||||
elif viewer is not self.focused_viewer:
|
||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||
self.focused_viewer = viewer
|
||||
viewer.focus(self)
|
||||
|
||||
def dispatch_event(self, event):
|
||||
'''
|
||||
Display event handling.
|
||||
'''
|
||||
try:
|
||||
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
|
||||
self._keyboard_event(event)
|
||||
elif event.type in MOUSE_EVENTS:
|
||||
self._mouse_event(event)
|
||||
else:
|
||||
print((
|
||||
'received event %s Use pygame.event.set_allowed().'
|
||||
% pygame.event.event_name(event.type)
|
||||
), file=stderr)
|
||||
# Catch all exceptions and open a viewer.
|
||||
except:
|
||||
err = format_exc()
|
||||
print(err, file=stderr) # To be safe just print it right away.
|
||||
open_viewer_on_string(self, err, self.broadcast)
|
||||
|
||||
def _keyboard_event(self, event):
|
||||
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
|
||||
# At least on my keyboard the break/pause key sends K_PAUSE.
|
||||
# The main use of this is to open a TextViewer if you
|
||||
# accidentally close all the viewers, so you can recover.
|
||||
raise KeyboardInterrupt('break')
|
||||
if not self.focused_viewer:
|
||||
return
|
||||
if event.type == pygame.KEYUP:
|
||||
self.focused_viewer.key_up(self, event.key, event.mod)
|
||||
elif event.type == pygame.KEYDOWN:
|
||||
self.focused_viewer.key_down(
|
||||
self, event.unicode, event.key, event.mod)
|
||||
# This is not UnicodeType. TODO does this need to be fixed?
|
||||
# self, event.str, event.key, event.mod)
|
||||
|
||||
def _mouse_event(self, event):
|
||||
V, x, y = self.at(*event.pos)
|
||||
|
||||
if event.type == pygame.MOUSEMOTION:
|
||||
if not isinstance(V, Track):
|
||||
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
|
||||
|
||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
self.focus(V)
|
||||
V.mouse_down(self, x, y, event.button)
|
||||
|
||||
else:
|
||||
assert event.type == pygame.MOUSEBUTTONUP
|
||||
|
||||
# Check for moving viewer.
|
||||
if (event.button == 2
|
||||
and self.focused_viewer
|
||||
and V is not self.focused_viewer
|
||||
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
|
||||
):
|
||||
self._move_viewer(V, y, self.focused_viewer, *event.pos)
|
||||
|
||||
else:
|
||||
V.mouse_up(self, x, y, event.button)
|
||||
|
||||
def init_text(self, pt, x, y, filename):
|
||||
'''
|
||||
Open and return a ``TextViewer`` on a given file (which must be present
|
||||
in the ``JOYHOME`` directory.)
|
||||
'''
|
||||
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
|
||||
viewer.content_id, viewer.lines = pt.open(filename)
|
||||
viewer.draw()
|
||||
return viewer
|
||||
|
||||
|
||||
class Track(Viewer):
|
||||
'''
|
||||
Manage a vertical strip of the display, and the viewers on it.
|
||||
'''
|
||||
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.viewers = [] # (y, viewer)
|
||||
self.hiding = None
|
||||
self.resizing_viewer = None
|
||||
self.draw()
|
||||
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the Track at the y coordinate and return the height
|
||||
available for a new viewer. Tracks manage a vertical strip of
|
||||
the display screen so they don't resize their surface when split.
|
||||
'''
|
||||
h = self.viewers[0][0] if self.viewers else self.h
|
||||
assert h > y
|
||||
return h - y
|
||||
|
||||
def draw(self, rect=None):
|
||||
'''Draw the track onto its surface, clearing all content.
|
||||
|
||||
If rect is passed only draw to that area. This supports e.g.
|
||||
closing a viewer that then exposes part of the track.
|
||||
'''
|
||||
self.surface.fill(GREY, rect=rect)
|
||||
|
||||
def viewer_at(self, y):
|
||||
'''
|
||||
Return the viewer at y along with the viewer-relative y coordinate,
|
||||
if there's no viewer at y return this track and y.
|
||||
'''
|
||||
for viewer_y, viewer in self.viewers:
|
||||
if viewer_y < y <= viewer_y + viewer.h:
|
||||
return viewer, y - viewer_y
|
||||
return self, y
|
||||
|
||||
def open_viewer(self, y, class_):
|
||||
'''Open and return a viewer of class at y.'''
|
||||
# Todo: if y coincides with some other viewer's y replace it.
|
||||
viewer, viewer_y = self.viewer_at(y)
|
||||
h = viewer.split(viewer_y)
|
||||
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
|
||||
new_viewer.draw()
|
||||
self.viewers.append((y, new_viewer))
|
||||
self.viewers.sort() # Could use bisect module but how many
|
||||
# viewers will you ever have?
|
||||
return new_viewer
|
||||
|
||||
def close_viewer(self, viewer):
|
||||
'''Close the viewer, reuse the freed space.'''
|
||||
for y, V in self.viewers:
|
||||
if V is viewer:
|
||||
self._close_viewer(y, V)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _close_viewer(self, y, viewer):
|
||||
'''Helper function to do the actual closing.'''
|
||||
i = self.viewers.index((y, viewer))
|
||||
del self.viewers[i]
|
||||
if i: # The previous viewer gets the space.
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
self._grow_by(previous_viewer, previous_y, viewer.h)
|
||||
else: # This track gets the space.
|
||||
self.draw((0, y, self.w, viewer.surface.get_height()))
|
||||
viewer.close()
|
||||
|
||||
def _grow_by(self, viewer, y, h):
|
||||
'''Grow a viewer (located at y) by height h.
|
||||
|
||||
This might seem like it should be a method of the viewer, but
|
||||
the viewer knows nothing of its own y location on the screen nor
|
||||
the parent track's surface (to make a new subsurface) so it has
|
||||
to be a method of the track, which has both.
|
||||
'''
|
||||
h = viewer.surface.get_height() + h
|
||||
try:
|
||||
surface = self.surface.subsurface((0, y, self.w, h))
|
||||
except ValueError: # subsurface rectangle outside surface area
|
||||
pass
|
||||
else:
|
||||
viewer.resurface(surface)
|
||||
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
|
||||
viewer.draw()
|
||||
|
||||
def change_viewer(self, viewer, new_y, relative=False):
|
||||
'''
|
||||
Adjust the top of the viewer to a new y within the boundaries of
|
||||
its neighbors.
|
||||
|
||||
If relative is False new_y should be in screen coords, else new_y
|
||||
should be relative to the top of the viewer.
|
||||
'''
|
||||
for old_y, V in self.viewers:
|
||||
if V is viewer:
|
||||
if relative: new_y += old_y
|
||||
if new_y != old_y: self._change_viewer(new_y, old_y, V)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _change_viewer(self, new_y, old_y, viewer):
|
||||
new_y = max(0, min(self.h, new_y))
|
||||
i = self.viewers.index((old_y, viewer))
|
||||
if new_y < old_y: # Enlarge self, shrink upper neighbor.
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
if new_y - previous_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
previous_viewer.resizing = 1
|
||||
h = previous_viewer.split(new_y - previous_y)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
h = old_y - new_y
|
||||
self._grow_by(viewer, new_y, h)
|
||||
|
||||
else: # Shink self, enlarge upper neighbor.
|
||||
# Enforce invariant.
|
||||
try:
|
||||
h, _ = self.viewers[i + 1]
|
||||
except IndexError: # No next viewer.
|
||||
h = self.h
|
||||
if h - new_y < self.MINIMUM_HEIGHT:
|
||||
return
|
||||
|
||||
# Change the viewer and adjust the upper viewer or track.
|
||||
h = new_y - old_y
|
||||
self._grow_by(viewer, new_y, -h) # grow by negative height!
|
||||
if i:
|
||||
previous_y, previous_viewer = self.viewers[i - 1]
|
||||
previous_viewer.resizing = 1
|
||||
self._grow_by(previous_viewer, previous_y, h)
|
||||
previous_viewer.resizing = 0
|
||||
self.resizing_viewer = previous_viewer
|
||||
else:
|
||||
self.draw((0, old_y, self.w, h))
|
||||
|
||||
self.viewers[i] = new_y, viewer
|
||||
# self.viewers.sort() # Not necessary, invariant holds.
|
||||
assert sorted(self.viewers) == self.viewers
|
||||
|
||||
def broadcast(self, message):
|
||||
'''
|
||||
Broadcast a message to all viewers on this track (except the sender.)
|
||||
'''
|
||||
for _, viewer in self.viewers:
|
||||
if viewer is not message.sender:
|
||||
viewer.handle(message)
|
||||
|
||||
def redraw(self):
|
||||
'''Redraw the track and all of its viewers.'''
|
||||
self.draw()
|
||||
for _, viewer in self.viewers:
|
||||
viewer.draw()
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import print_function
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
from io import StringIO
|
||||
import base64, zlib
|
||||
|
||||
|
||||
def create(fn='Iosevka12.BMP'):
|
||||
with open(fn, 'rb') as f:
|
||||
data = f.read()
|
||||
return base64.encodestring(zlib.compress(data))
|
||||
|
||||
|
||||
data = StringIO(zlib.decompress(base64.decodestring('''\
|
||||
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
|
||||
lnalXc/9SsNb2vUirzS8pV0v8gJv/w/2vRht''')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(create())
|
||||
@@ -1,278 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
Utility module to help with setting up the initial contents of the
|
||||
JOY_HOME directory.
|
||||
|
||||
These contents are kept in this Python module as a base64-encoded zip
|
||||
file, so you can just do, e.g.:
|
||||
|
||||
import init_joy_home
|
||||
init_joy_home.initialize(JOY_HOME)
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from future import standard_library
|
||||
standard_library.install_aliases()
|
||||
import base64, os, io, zipfile
|
||||
|
||||
|
||||
def initialize(joy_home):
|
||||
Z.extractall(joy_home)
|
||||
|
||||
|
||||
def create_data(from_dir='./default_joy_home'):
|
||||
f = io.StringIO()
|
||||
z = zipfile.ZipFile(f, mode='w')
|
||||
for fn in os.listdir(from_dir):
|
||||
from_fn = os.path.join(from_dir, fn)
|
||||
z.write(from_fn, fn)
|
||||
z.close()
|
||||
return base64.encodestring(f.getvalue())
|
||||
|
||||
|
||||
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
||||
UEsDBBQAAAAAAORmeE794BlRfgMAAH4DAAAPAAAAZGVmaW5pdGlvbnMudHh0c2VlX3N0YWNrID09
|
||||
IGdvb2Rfdmlld2VyX2xvY2F0aW9uIG9wZW5fc3RhY2sNCnNlZV9yZXNvdXJjZXMgPT0gbGlzdF9y
|
||||
ZXNvdXJjZXMgZ29vZF92aWV3ZXJfbG9jYXRpb24gb3Blbl92aWV3ZXINCm9wZW5fcmVzb3VyY2Vf
|
||||
YXRfZ29vZF9sb2NhdGlvbiA9PSBnb29kX3ZpZXdlcl9sb2NhdGlvbiBvcGVuX3Jlc291cmNlDQpz
|
||||
ZWVfbG9nID09ICJsb2cudHh0IiBvcGVuX3Jlc291cmNlX2F0X2dvb2RfbG9jYXRpb24NCnNlZV9k
|
||||
ZWZpbml0aW9ucyA9PSAiZGVmaW5pdGlvbnMudHh0IiBvcGVuX3Jlc291cmNlX2F0X2dvb2RfbG9j
|
||||
YXRpb24NCnJvdW5kX3RvX2NlbnRzID09IDEwMCAqICsrIGZsb29yIDEwMCAvDQpyZXNldF9sb2cg
|
||||
PT0gImRlbCBsb2cubGluZXNbMTpdIDsgbG9nLmF0X2xpbmUgPSAwIiBldmFsdWF0ZQ0Kc2VlX21l
|
||||
bnUgPT0gIm1lbnUudHh0IiBnb29kX3ZpZXdlcl9sb2NhdGlvbiBvcGVuX3Jlc291cmNlDQoNCiMg
|
||||
T3JkZXJlZCBCaW5hcnkgVHJlZSBkYXRhc3RydWN0dXJlIGZ1bmN0aW9ucy4NCkJUcmVlLW5ldyA9
|
||||
PSBzd2FwIFtbXSBbXV0gY29ucyBjb25zDQogX0JUcmVlLVAgPT0gb3ZlciBbcG9wb3AgcG9wb3Ag
|
||||
Zmlyc3RdIG51bGxhcnkNCiBfQlRyZWUtVD4gPT0gW2NvbnMgY29ucyBkaXBkZF0gY29ucyBjb25z
|
||||
IGNvbnMgaW5mcmENCiBfQlRyZWUtVDwgPT0gW2NvbnMgY29ucyBkaXBkXSBjb25zIGNvbnMgY29u
|
||||
cyBpbmZyYQ0KIF9CVHJlZS1FID09IHBvcCBzd2FwIHJvbGw8IHJlc3QgcmVzdCBjb25zIGNvbnMN
|
||||
CiBfQlRyZWUtcmVjdXIgPT0gX0JUcmVlLVAgW19CVHJlZS1UPl0gW19CVHJlZS1FXSBbX0JUcmVl
|
||||
LVQ8XSBjbXANCkJUcmVlLWFkZCA9PSBbcG9wb3Agbm90XSBbW3BvcF0gZGlwZCBCVHJlZS1uZXdd
|
||||
IFtdIFtfQlRyZWUtcmVjdXJdIGdlbnJlYw0KUEsDBBQAAAAAACFrpk7/HHjxGBYAABgWAAAKAAAA
|
||||
bGlicmFyeS5weScnJw0KVGhpcyBmaWxlIGlzIGV4ZWNmaWxlKCknZCB3aXRoIGEgbmFtZXNwYWNl
|
||||
IGNvbnRhaW5pbmc6DQoNCiAgRCAtIHRoZSBKb3kgZGljdGlvbmFyeQ0KICBkIC0gdGhlIERpc3Bs
|
||||
YXkgb2JqZWN0DQogIHB0IC0gdGhlIFBlcnNpc3RUYXNrIG9iamVjdA0KICBsb2cgLSB0aGUgbG9n
|
||||
LnR4dCB2aWV3ZXINCiAgbG9vcCAtIHRoZSBUaGVMb29wIG1haW4gbG9vcCBvYmplY3QNCiAgc3Rh
|
||||
Y2tfaG9sZGVyIC0gdGhlIFB5dGhvbiBsaXN0IG9iamVjdCB0aGF0IGhvbGRzIHRoZSBKb3kgc3Rh
|
||||
Y2sgdHVwbGUNCiAgd29ybGQgLSB0aGUgSm95IGVudmlyb25tZW50DQoNCicnJw0KZnJvbSBqb3ku
|
||||
bGlicmFyeSBpbXBvcnQgKA0KICAgIERlZmluaXRpb25XcmFwcGVyLA0KICAgIEZ1bmN0aW9uV3Jh
|
||||
cHBlciwNCiAgICBTaW1wbGVGdW5jdGlvbldyYXBwZXIsDQogICAgKQ0KZnJvbSBqb3kudXRpbHMu
|
||||
c3RhY2sgaW1wb3J0IGxpc3RfdG9fc3RhY2ssIGNvbmNhdA0KZnJvbSBqb3kudnVpIGltcG9ydCBj
|
||||
b3JlLCB0ZXh0X3ZpZXdlciwgc3RhY2tfdmlld2VyDQoNCg0KZGVmIGluc3RhbGwoY29tbWFuZCk6
|
||||
IERbY29tbWFuZC5uYW1lXSA9IGNvbW1hbmQNCg0KDQpAaW5zdGFsbA0KQFNpbXBsZUZ1bmN0aW9u
|
||||
V3JhcHBlcg0KZGVmIGxpc3RfcmVzb3VyY2VzKHN0YWNrKToNCiAgICAnJycNCiAgICBQdXQgYSBz
|
||||
dHJpbmcgb24gdGhlIHN0YWNrIHdpdGggdGhlIG5hbWVzIG9mIGFsbCB0aGUga25vd24gcmVzb3Vy
|
||||
Y2VzDQogICAgb25lLXBlci1saW5lLg0KICAgICcnJw0KICAgIHJldHVybiAnXG4nLmpvaW4ocHQu
|
||||
c2NhbigpKSwgc3RhY2sNCg0KDQpAaW5zdGFsbA0KQFNpbXBsZUZ1bmN0aW9uV3JhcHBlcg0KZGVm
|
||||
IG9wZW5fc3RhY2soc3RhY2spOg0KICAgICcnJw0KICAgIEdpdmVuIGEgY29vcmRpbmF0ZSBwYWly
|
||||
IFt4IHldIChpbiBwaXhlbHMpIG9wZW4gYSBTdGFja1ZpZXdlciB0aGVyZS4NCiAgICAnJycNCiAg
|
||||
ICAoeCwgKHksIF8pKSwgc3RhY2sgPSBzdGFjaw0KICAgIFYgPSBkLm9wZW5fdmlld2VyKHgsIHks
|
||||
IHN0YWNrX3ZpZXdlci5TdGFja1ZpZXdlcikNCiAgICBWLmRyYXcoKQ0KICAgIHJldHVybiBzdGFj
|
||||
aw0KDQoNCkBpbnN0YWxsDQpAU2ltcGxlRnVuY3Rpb25XcmFwcGVyDQpkZWYgb3Blbl9yZXNvdXJj
|
||||
ZShzdGFjayk6DQogICAgJycnDQogICAgR2l2ZW4gYSBjb29yZGluYXRlIHBhaXIgW3ggeV0gKGlu
|
||||
IHBpeGVscykgYW5kIHRoZSBuYW1lIG9mIGEgcmVzb3VyY2UNCiAgICAoZnJvbSBsaXN0X3Jlc291
|
||||
cmNlcyBjb21tYW5kKSBvcGVuIGEgdmlld2VyIG9uIHRoYXQgcmVzb3VyY2UgYXQgdGhhdA0KICAg
|
||||
IGxvY2F0aW9uLg0KICAgICcnJw0KICAgICgoeCwgKHksIF8pKSwgKG5hbWUsIHN0YWNrKSkgPSBz
|
||||
dGFjaw0KICAgIG9tID0gY29yZS5PcGVuTWVzc2FnZSh3b3JsZCwgbmFtZSkNCiAgICBkLmJyb2Fk
|
||||
Y2FzdChvbSkNCiAgICBpZiBvbS5zdGF0dXMgPT0gY29yZS5TVUNDRVNTOg0KICAgICAgICBWID0g
|
||||
ZC5vcGVuX3ZpZXdlcih4LCB5LCB0ZXh0X3ZpZXdlci5UZXh0Vmlld2VyKQ0KICAgICAgICBWLmNv
|
||||
bnRlbnRfaWQsIFYubGluZXMgPSBvbS5jb250ZW50X2lkLCBvbS50aGluZw0KICAgICAgICBWLmRy
|
||||
YXcoKQ0KICAgIHJldHVybiBzdGFjaw0KDQoNCkBpbnN0YWxsDQpAU2ltcGxlRnVuY3Rpb25XcmFw
|
||||
cGVyDQpkZWYgbmFtZV92aWV3ZXIoc3RhY2spOg0KICAgICcnJw0KICAgIEdpdmVuIGEgc3RyaW5n
|
||||
IG5hbWUgb24gdGhlIHN0YWNrLCBpZiB0aGUgY3VycmVudGx5IGZvY3VzZWQgdmlld2VyIGlzDQog
|
||||
ICAgYW5vbnltb3VzLCBuYW1lIHRoZSB2aWV3ZXIgYW5kIHBlcnNpc3QgaXQgaW4gdGhlIHJlc291
|
||||
cmNlIHN0b3JlIHVuZGVyDQogICAgdGhhdCBuYW1lLg0KICAgICcnJw0KICAgIG5hbWUsIHN0YWNr
|
||||
ID0gc3RhY2sNCiAgICBhc3NlcnQgaXNpbnN0YW5jZShuYW1lLCBzdHIpLCByZXByKG5hbWUpDQog
|
||||
ICAgaWYgZC5mb2N1c2VkX3ZpZXdlciBhbmQgbm90IGQuZm9jdXNlZF92aWV3ZXIuY29udGVudF9p
|
||||
ZDoNCiAgICAgICAgZC5mb2N1c2VkX3ZpZXdlci5jb250ZW50X2lkID0gbmFtZQ0KICAgICAgICBw
|
||||
bSA9IGNvcmUuUGVyc2lzdE1lc3NhZ2Uod29ybGQsIG5hbWUsIHRoaW5nPWQuZm9jdXNlZF92aWV3
|
||||
ZXIubGluZXMpDQogICAgICAgIGQuYnJvYWRjYXN0KHBtKQ0KICAgICAgICBkLmZvY3VzZWRfdmll
|
||||
d2VyLmRyYXdfbWVudSgpDQogICAgcmV0dXJuIHN0YWNrDQoNCg0KIyNAaW5zdGFsbA0KIyNAU2lt
|
||||
cGxlRnVuY3Rpb25XcmFwcGVyDQojI2RlZiBwZXJzaXN0X3ZpZXdlcihzdGFjayk6DQojIyAgICBp
|
||||
ZiBzZWxmLmZvY3VzZWRfdmlld2VyOg0KIyMgICAgICAgIA0KIyMgICAgICAgIHNlbGYuZm9jdXNl
|
||||
ZF92aWV3ZXIuY29udGVudF9pZCA9IG5hbWUNCiMjICAgICAgICBzZWxmLmZvY3VzZWRfdmlld2Vy
|
||||
LmRyYXdfbWVudSgpDQojIyAgICByZXR1cm4gc3RhY2sNCg0KDQpAaW5zdGFsbA0KQFNpbXBsZUZ1
|
||||
bmN0aW9uV3JhcHBlcg0KZGVmIGluc2NyaWJlKHN0YWNrKToNCiAgICAnJycNCiAgICBDcmVhdGUg
|
||||
YSBuZXcgSm95IGZ1bmN0aW9uIGRlZmluaXRpb24gaW4gdGhlIEpveSBkaWN0aW9uYXJ5LiAgQQ0K
|
||||
ICAgIGRlZmluaXRpb24gaXMgZ2l2ZW4gYXMgYSBzdHJpbmcgd2l0aCBhIG5hbWUgZm9sbG93ZWQg
|
||||
YnkgYSBkb3VibGUNCiAgICBlcXVhbCBzaWduIHRoZW4gb25lIG9yIG1vcmUgSm95IGZ1bmN0aW9u
|
||||
cywgdGhlIGJvZHkuIGZvciBleGFtcGxlOg0KDQogICAgICAgIHNxciA9PSBkdXAgbXVsDQoNCiAg
|
||||
ICBJZiB5b3Ugd2FudCB0aGUgZGVmaW5pdGlvbiB0byBwZXJzaXN0IG92ZXIgcmVzdGFydHMsIGVu
|
||||
dGVyIGl0IGludG8NCiAgICB0aGUgZGVmaW5pdGlvbnMudHh0IHJlc291cmNlLg0KICAgICcnJw0K
|
||||
ICAgIGRlZmluaXRpb24sIHN0YWNrID0gc3RhY2sNCiAgICBEZWZpbml0aW9uV3JhcHBlci5hZGRf
|
||||
ZGVmKGRlZmluaXRpb24sIEQpDQogICAgcmV0dXJuIHN0YWNrDQoNCg0KQGluc3RhbGwNCkBTaW1w
|
||||
bGVGdW5jdGlvbldyYXBwZXINCmRlZiBvcGVuX3ZpZXdlcihzdGFjayk6DQogICAgJycnDQogICAg
|
||||
R2l2ZW4gYSBjb29yZGluYXRlIHBhaXIgW3ggeV0gKGluIHBpeGVscykgYW5kIGEgc3RyaW5nLCBv
|
||||
cGVuIGEgbmV3DQogICAgdW5uYW1lZCB2aWV3ZXIgb24gdGhhdCBzdHJpbmcgYXQgdGhhdCBsb2Nh
|
||||
dGlvbi4NCiAgICAnJycNCiAgICAoKHgsICh5LCBfKSksIChjb250ZW50LCBzdGFjaykpID0gc3Rh
|
||||
Y2sNCiAgICBWID0gZC5vcGVuX3ZpZXdlcih4LCB5LCB0ZXh0X3ZpZXdlci5UZXh0Vmlld2VyKQ0K
|
||||
ICAgIFYubGluZXMgPSBjb250ZW50LnNwbGl0bGluZXMoKQ0KICAgIFYuZHJhdygpDQogICAgcmV0
|
||||
dXJuIHN0YWNrDQoNCg0KQGluc3RhbGwNCkBTaW1wbGVGdW5jdGlvbldyYXBwZXINCmRlZiBnb29k
|
||||
X3ZpZXdlcl9sb2NhdGlvbihzdGFjayk6DQogICAgJycnDQogICAgTGVhdmUgYSBjb29yZGluYXRl
|
||||
IHBhaXIgW3ggeV0gKGluIHBpeGVscykgb24gdGhlIHN0YWNrIHRoYXQgd291bGQNCiAgICBiZSBh
|
||||
IGdvb2QgbG9jYXRpb24gYXQgd2hpY2ggdG8gb3BlbiBhIG5ldyB2aWV3ZXIuICAoVGhlIGhldXJp
|
||||
c3RpYw0KICAgIGVtcGxveWVkIGlzIHRvIHRha2UgdXAgdGhlIGJvdHRvbSBoYWxmIG9mIHRoZSBj
|
||||
dXJyZW50bHkgb3BlbiB2aWV3ZXINCiAgICB3aXRoIHRoZSBncmVhdGVzdCBhcmVhLikNCiAgICAn
|
||||
JycNCiAgICB2aWV3ZXJzID0gbGlzdChkLml0ZXJfdmlld2VycygpKQ0KICAgIGlmIHZpZXdlcnM6
|
||||
DQogICAgICAgIHZpZXdlcnMuc29ydChrZXk9bGFtYmRhIChWLCB4LCB5KTogVi53ICogVi5oKQ0K
|
||||
ICAgICAgICBWLCB4LCB5ID0gdmlld2Vyc1stMV0NCiAgICAgICAgY29vcmRzID0gKHggKyAxLCAo
|
||||
eSArIFYuaCAvIDIsICgpKSkNCiAgICBlbHNlOg0KICAgICAgICBjb29yZHMgPSAoMCwgKDAsICgp
|
||||
KSkNCiAgICByZXR1cm4gY29vcmRzLCBzdGFjaw0KDQoNCkBpbnN0YWxsDQpARnVuY3Rpb25XcmFw
|
||||
cGVyDQpkZWYgY21wXyhzdGFjaywgZXhwcmVzc2lvbiwgZGljdGlvbmFyeSk6DQogICAgJycnDQog
|
||||
ICAgVGhlIGNtcCBjb21iaW5hdG9yIHRha2VzIHR3byB2YWx1ZXMgYW5kIHRocmVlIHF1b3RlZCBw
|
||||
cm9ncmFtcyBvbiB0aGUNCiAgICBzdGFjayBhbmQgcnVucyBvbmUgb2YgdGhlIHRocmVlIGRlcGVu
|
||||
ZGluZyBvbiB0aGUgcmVzdWx0cyBvZiBjb21wYXJpbmcNCiAgICB0aGUgdHdvIHZhbHVlczoNCg0K
|
||||
ICAgICAgICAgICBhIGIgW0ddIFtFXSBbTF0gY21wDQogICAgICAgIC0tLS0tLS0tLS0tLS0tLS0t
|
||||
LS0tLS0tLS0gYSA+IGINCiAgICAgICAgICAgICAgICBHDQoNCiAgICAgICAgICAgYSBiIFtHXSBb
|
||||
RV0gW0xdIGNtcA0KICAgICAgICAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIGEgPSBiDQogICAg
|
||||
ICAgICAgICAgICAgICAgIEUNCg0KICAgICAgICAgICBhIGIgW0ddIFtFXSBbTF0gY21wDQogICAg
|
||||
ICAgIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gYSA8IGINCiAgICAgICAgICAgICAgICAgICAg
|
||||
ICAgIEwNCg0KICAgICcnJw0KICAgIEwsIChFLCAoRywgKGIsIChhLCBzdGFjaykpKSkgPSBzdGFj
|
||||
aw0KICAgIGV4cHJlc3Npb24gPSBjb25jYXQoRyBpZiBhID4gYiBlbHNlIEwgaWYgYSA8IGIgZWxz
|
||||
ZSBFLCBleHByZXNzaW9uKQ0KICAgIHJldHVybiBzdGFjaywgZXhwcmVzc2lvbiwgZGljdGlvbmFy
|
||||
eQ0KDQoNCkBpbnN0YWxsDQpAU2ltcGxlRnVuY3Rpb25XcmFwcGVyDQpkZWYgbGlzdF92aWV3ZXJz
|
||||
KHN0YWNrKToNCiAgICAnJycNCiAgICBQdXQgYSBzdHJpbmcgb24gdGhlIHN0YWNrIHdpdGggc29t
|
||||
ZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgY3VycmVudGx5DQogICAgb3BlbiB2aWV3ZXJzLCBvbmUt
|
||||
cGVyLWxpbmUuICBUaGlzIGlzIGtpbmQgb2YgYSBkZW1vIGZ1bmN0aW9uLCByYXRoZXINCiAgICB0
|
||||
aGFuIHNvbWV0aGluZyByZWFsbHkgdXNlZnVsLg0KICAgICcnJw0KICAgIGxpbmVzID0gW10NCiAg
|
||||
ICBmb3IgeCwgVCBpbiBkLnRyYWNrczoNCiAgICAgICAgI2xpbmVzLmFwcGVuZCgneDogJWksIHc6
|
||||
ICVpLCAlcicgJSAoeCwgVC53LCBUKSkNCiAgICAgICAgZm9yIHksIFYgaW4gVC52aWV3ZXJzOg0K
|
||||
ICAgICAgICAgICAgbGluZXMuYXBwZW5kKCd4OiAlaSB5OiAlaSBoOiAlaSAlciAlcicgJSAoeCwg
|
||||
eSwgVi5oLCBWLmNvbnRlbnRfaWQsIFYpKQ0KICAgIHJldHVybiAnXG4nLmpvaW4obGluZXMpLCBz
|
||||
dGFjaw0KDQoNCkBpbnN0YWxsDQpAU2ltcGxlRnVuY3Rpb25XcmFwcGVyDQpkZWYgc3BsaXRsaW5l
|
||||
cyhzdGFjayk6DQogICAgJycnDQogICAgR2l2ZW4gYSBzdHJpbmcgb24gdGhlIHN0YWNrIHJlcGxh
|
||||
Y2UgaXQgd2l0aCBhIGxpc3Qgb2YgdGhlIGxpbmVzIGluDQogICAgdGhlIHN0cmluZy4NCiAgICAn
|
||||
JycNCiAgICB0ZXh0LCBzdGFjayA9IHN0YWNrDQogICAgYXNzZXJ0IGlzaW5zdGFuY2UodGV4dCwg
|
||||
c3RyKSwgcmVwcih0ZXh0KQ0KICAgIHJldHVybiBsaXN0X3RvX3N0YWNrKHRleHQuc3BsaXRsaW5l
|
||||
cygpKSwgc3RhY2sNCg0KDQpAaW5zdGFsbA0KQFNpbXBsZUZ1bmN0aW9uV3JhcHBlcg0KZGVmIGhp
|
||||
eWEoc3RhY2spOg0KICAgICcnJw0KICAgIERlbW8gZnVuY3Rpb24gdG8gaW5zZXJ0ICJIaSBXb3Js
|
||||
ZCEiIGludG8gdGhlIGN1cnJlbnQgdmlld2VyLCBpZiBhbnkuDQogICAgJycnDQogICAgaWYgZC5m
|
||||
b2N1c2VkX3ZpZXdlcjoNCiAgICAgICAgZC5mb2N1c2VkX3ZpZXdlci5pbnNlcnQoJ0hpIFdvcmxk
|
||||
IScpDQogICAgcmV0dXJuIHN0YWNrDQpQSwMEFAAAAAAA5GZ4TkXs5NYLAAAACwAAAAcAAABsb2cu
|
||||
dHh0Sm95cHkgbG9nDQpQSwMEFAAAAAAA5GZ4Tmf2u80CBQAAAgUAAAgAAABtZW51LnR4dCAgbmFt
|
||||
ZV92aWV3ZXINCiAgbGlzdF9yZXNvdXJjZXMNCiAgb3Blbl9yZXNvdXJjZV9hdF9nb29kX2xvY2F0
|
||||
aW9uDQogIGdvb2Rfdmlld2VyX2xvY2F0aW9uDQogIG9wZW5fdmlld2VyDQogIHNlZV9zdGFjaw0K
|
||||
ICBzZWVfcmVzb3VyY2VzDQogIHNlZV9kZWZpbml0aW9ucw0KICBzZWVfbG9nDQogIHJlc2V0X2xv
|
||||
Zw0KDQogIGluc2NyaWJlDQogIGV2YWx1YXRlDQoNCiAgcG9wIGNsZWFyICAgIGR1cCBzd2FwDQoN
|
||||
CiAgYWRkIHN1YiBtdWwgZGl2IHRydWVkaXYgbW9kdWx1cyBkaXZtb2QNCiAgcG0gKysgLS0gc3Vt
|
||||
IHByb2R1Y3QgcG93IHNxciBzcXJ0DQogIDwgPD0gPSA+PSA+IDw+DQogICYgPDwgPj4NCg0KICBp
|
||||
IGR1cGRpcA0KDQohPSAlICYgKiAqZnJhY3Rpb24gKmZyYWN0aW9uMCArICsrIC0gLS0gLyA8IDw8
|
||||
IDw9IDw+ID0gPiA+PSA+PiA/IF4NCmFicyBhZGQgYW5hbW9ycGhpc20gYW5kIGFwcDEgYXBwMiBh
|
||||
cHAzIGF0IGF2ZXJhZ2UNCmIgYmluYXJ5IGJyYW5jaA0KY2hvaWNlIGNsZWFyIGNsZWF2ZSBjb25j
|
||||
YXQgY29ucw0KZGluZnJpcnN0IGRpcCBkaXBkIGRpcGRkIGRpc2Vuc3RhY2tlbiBkaXYgZGl2bW9k
|
||||
IGRvd25fdG9femVybyBkcm9wDQpkdWRpcGQgZHVwIGR1cGQgZHVwZGlwDQplbnN0YWNrZW4gZXEN
|
||||
CmZpcnN0IGZsYXR0ZW4gZmxvb3IgZmxvb3JkaXYNCmdjZCBnZSBnZW5yZWMgZ2V0aXRlbSBncmFu
|
||||
ZF9yZXNldCBndA0KaGVscA0KaSBpZCBpZnRlIGluZnJhIGluc2NyaWJlDQprZXlfYmluZGluZ3MN
|
||||
CmxlIGxlYXN0X2ZyYWN0aW9uIGxvb3AgbHNoaWZ0IGx0DQptYXAgbWF4IG1pbiBtb2QgbW9kdWx1
|
||||
cyBtb3VzZV9iaW5kaW5ncyBtdWwNCm5lIG5lZyBub3QgbnVsbGFyeQ0Kb2Ygb3Igb3Zlcg0KcGFt
|
||||
IHBhcnNlIHBpY2sgcG0gcG9wIHBvcGQgcG9wZGQgcG9wb3AgcG93IHByZWQgcHJpbXJlYyBwcm9k
|
||||
dWN0DQpxdW90ZWQNCnJhbmdlIHJhbmdlX3RvX3plcm8gcmVtIHJlbWFpbmRlciByZW1vdmUgcmVz
|
||||
ZXRfbG9nIHJlc3QgcmV2ZXJzZQ0Kcm9sbDwgcm9sbD4gcm9sbGRvd24gcm9sbHVwIHJzaGlmdCBy
|
||||
dW4NCnNlY29uZCBzZWxlY3Qgc2hhcmluZyBzaG93X2xvZyBzaHVudCBzaXplIHNvcnQgc3FyIHNx
|
||||
cnQgc3RhY2sgc3RlcA0Kc3RlcF96ZXJvIHN1YiBzdWNjIHN1bSBzd2FhY2sgc3dhcCBzd29uY2F0
|
||||
IHN3b25zDQp0YWtlIHRlcm5hcnkgdGhpcmQgdGltZXMgdHJ1ZWRpdiB0cnV0aHkgdHVjaw0KdW5h
|
||||
cnkgdW5jb25zIHVuaXF1ZSB1bml0IHVucXVvdGVkIHVuc3RhY2sNCnZvaWQNCndhcnJhbnR5IHdo
|
||||
aWxlIHdvcmRzDQp4IHhvcg0KemlwDQpQSwMEFAAAAAAA5GZ4TgCrPcaOEAAAjhAAAAsAAABzY3Jh
|
||||
dGNoLnR4dFdoYXQgaXMgaXQ/DQoNCkEgc2ltcGxlIEdyYXBoaWNhbCBVc2VyIEludGVyZmFjZSBm
|
||||
b3IgdGhlIEpveSBwcm9ncmFtbWluZyBsYW5ndWFnZSwNCndyaXR0ZW4gdXNpbmcgUHlnYW1lIHRv
|
||||
IGJ5cGFzcyBYMTEgZXQuIGFsLiwgbW9kZWxlZCBvbiB0aGUgT2Jlcm9uIE9TLCBhbmQNCmludGVu
|
||||
ZGVkIHRvIGJlIGp1c3QgZnVuY3Rpb25hbCBlbm91Z2ggdG8gc3VwcG9ydCBib290c3RyYXBwaW5n
|
||||
IGZ1cnRoZXIgSm95DQpkZXZlbG9wbWVudC4NCg0KSXQncyBiYXNpYyBmdW5jdGlvbmFsaXR5IGlz
|
||||
IG1vcmUtb3ItbGVzcyBhcyBhIGNydWRlIHRleHQgZWRpdG9yIGFsb25nIHdpdGgNCmEgc2ltcGxl
|
||||
IEpveSBydW50aW1lIChpbnRlcnByZXRlciwgc3RhY2ssIGFuZCBkaWN0aW9uYXJ5LikgIEl0IGF1
|
||||
dG8tIHNhdmVzDQphbnkgbmFtZWQgZmlsZXMgKGluIGEgdmVyc2lvbmVkIGhvbWUgZGlyZWN0b3J5
|
||||
KSBhbmQgeW91IGNhbiB3cml0ZSBuZXcgSm95DQpwcmltaXRpdmVzIGluIFB5dGhvbiBhbmQgSm95
|
||||
IGRlZmluaXRpb25zIGFuZCBpbW1lZGlhdGVseSBpbnN0YWxsIGFuZCB1c2UNCnRoZW0sIGFzIHdl
|
||||
bGwgYXMgcmVjb3JkaW5nIHRoZW0gZm9yIHJldXNlIChhZnRlciByZXN0YXJ0cy4pDQoNCkN1cnJl
|
||||
bnRseSwgdGhlcmUgYXJlIG9ubHkgdHdvIGtpbmRzIG9mIChpbnRlcmVzdGluZykgdmlld2Vyczog
|
||||
VGV4dFZpZXdlcnMNCmFuZCBTdGFja1ZpZXdlci4gVGhlIFRleHRWaWV3ZXJzIGFyZSBjcnVkZSB0
|
||||
ZXh0IGVkaXRvcnMuICBUaGV5IHByb3ZpZGUNCmp1c3QgZW5vdWdoIGZ1bmN0aW9uYWxpdHkgdG8g
|
||||
bGV0IHRoZSB1c2VyIHdyaXRlIHRleHQgYW5kIGNvZGUgKFB5dGhvbiBhbmQNCkpveSkgYW5kIGV4
|
||||
ZWN1dGUgSm95IGZ1bmN0aW9ucy4gIE9uZSBpbXBvcnRhbnQgdGhpbmcgdGhleSBkbyBpcw0KYXV0
|
||||
b21hdGljYWxseSBzYXZlIHRoZWlyIGNvbnRlbnQgYWZ0ZXIgY2hhbmdlcy4gIE5vIG1vcmUgbG9z
|
||||
dCB3b3JrLg0KDQpUaGUgU3RhY2tWaWV3ZXIgaXMgYSBzcGVjaWFsaXplZCBUZXh0Vmlld2VyIHRo
|
||||
YXQgc2hvd3MgdGhlIGNvbnRlbnRzIG9mIHRoZQ0KSm95IHN0YWNrIG9uZSBsaW5lIHBlciBzdGFj
|
||||
ayBpdGVtLiAgSXQncyBhIHZlcnkgaGFuZHkgdmlzdWFsIGFpZCB0byBrZWVwDQp0cmFjayBvZiB3
|
||||
aGF0J3MgZ29pbmcgb24uICBUaGVyZSdzIGFsc28gYSBsb2cudHh0IGZpbGUgdGhhdCBnZXRzIHdy
|
||||
aXR0ZW4NCnRvIHdoZW4gY29tbWFuZHMgYXJlIGV4ZWN1dGVkLCBhbmQgc28gcmVjb3JkcyB0aGUg
|
||||
bG9nIG9mIHVzZXIgYWN0aW9ucyBhbmQNCnN5c3RlbSBldmVudHMuICBJdCB0ZW5kcyB0byBmaWxs
|
||||
IHVwIHF1aWNrbHkgc28gdGhlcmUncyBhIHJlc2V0X2xvZyBjb21tYW5kDQp0aGF0IGNsZWFycyBp
|
||||
dCBvdXQuDQoNClZpZXdlcnMgaGF2ZSAiZ3JvdyIgYW5kICJjbG9zZSIgaW4gdGhlaXIgbWVudSBi
|
||||
YXJzLiAgVGhlc2UgYXJlIGJ1dHRvbnMuDQpXaGVuIHlvdSByaWdodC1jbGljayBvbiBncm93IGEg
|
||||
dmlld2VyIGEgY29weSBpcyBjcmVhdGVkIHRoYXQgY292ZXJzIHRoYXQNCnZpZXdlcidzIGVudGly
|
||||
ZSB0cmFjay4gIElmIHlvdSBncm93IGEgdmlld2VyIHRoYXQgYWxyZWFkeSB0YWtlcyB1cCBpdHMN
|
||||
Cndob2xlIHRyYWNrIHRoZW4gYSBjb3B5IGlzIGNyZWF0ZWQgdGhhdCB0YWtlcyB1cCBhbiBhZGRp
|
||||
dGlvbmFsIHRyYWNrLCB1cA0KdG8gdGhlIHdob2xlIHNjcmVlbi4gIENsb3NpbmcgYSB2aWV3ZXIg
|
||||
anVzdCBkZWxldGVzIHRoYXQgdmlld2VyLCBhbmQgd2hlbg0KYSB0cmFjayBoYXMgbm8gbW9yZSB2
|
||||
aWV3ZXJzLCBpdCBpcyBkZWxldGVkIGFuZCB0aGF0IGV4cG9zZXMgYW55IHByZXZpb3VzDQp0cmFj
|
||||
a3MgYW5kIHZpZXdlcnMgdGhhdCB3ZXJlIGhpZGRlbi4NCg0KKE5vdGU6IGlmIHlvdSBldmVyIGNs
|
||||
b3NlIGFsbCB0aGUgdmlld2VycyBhbmQgYXJlIHNpdHRpbmcgYXQgYSBibGFuayBzY3JlZW4NCndp
|
||||
dGggIG5vd2hlcmUgdG8gdHlwZSBhbmQgZXhlY3V0ZSBjb21tYW5kcywgcHJlc3MgdGhlIFBhdXNl
|
||||
L0JyZWFrIGtleS4NClRoaXMgd2lsbCBvcGVuIGEgbmV3ICJ0cmFwIiB2aWV3ZXIgd2hpY2ggeW91
|
||||
IGNhbiB0aGVuIHVzZSB0byByZWNvdmVyLikNCg0KQ29waWVzIG9mIGEgdmlld2VyIGFsbCBzaGFy
|
||||
ZSB0aGUgc2FtZSBtb2RlbCBhbmQgdXBkYXRlIHRoZWlyIGRpc3BsYXkgYXMgaXQNCmNoYW5nZXMu
|
||||
IChJZiB5b3UgaGF2ZSB0d28gdmlld2VycyBvcGVuIG9uIHRoZSBzYW1lIG5hbWVkIHJlc291cmNl
|
||||
IGFuZCBlZGl0DQpvbmUgeW91J2xsIHNlZSB0aGUgb3RoZXIgdXBkYXRlIGFzIHlvdSB0eXBlLikN
|
||||
Cg0KVUkgR3VpZGUNCg0KbGVmdCBtb3VzZSBzZXRzIGN1cnNvciBpbiB0ZXh0LCBpbiBtZW51IGJh
|
||||
ciByZXNpemVzIHZpZXdlciBpbnRlcmFjdGl2ZWx5DQoodGhpcyBpcyBhIGxpdHRsZSBidWdneSBp
|
||||
biB0aGF0IHlvdSBjYW4gbW92ZSB0aGUgbW91c2UgcXVpY2tseSBhbmQgZ2V0DQpvdXRzaWRlIHRo
|
||||
ZSBtZW51LCBsZWF2aW5nIHRoZSB2aWV3ZXIgaW4gdGhlICJyZXNpemluZyIgc3RhdGUuIFVudGls
|
||||
IEkgZml4DQp0aGlzLCB0aGUgd29ya2Fyb3VuZCBpcyB0byBqdXN0IGdyYWIgdGhlIG1lbnUgYmFy
|
||||
IGFnYWluIGFuZCB3aWdnbGUgaXQgYQ0KZmV3IHBpeGVscyBhbmQgbGV0IGdvLiAgVGhpcyB3aWxs
|
||||
IHJlc2V0IHRoZSBtYWNoaW5lcnkuKQ0KDQpSaWdodCBtb3VzZSBleGVjdXRlcyBKb3kgY29tbWFu
|
||||
ZCAoZnVuY3Rpb25zKSwgYW5kIHlvdSBjYW4gZHJhZyB3aXRoIHRoZQ0KcmlnaHQgYnV0dG9uIHRv
|
||||
IGhpZ2hsaWdodCAod2VsbCwgdW5kZXJsaW5lKSBjb21tYW5kcy4gIFdvcmRzIHRoYXQgYXJlbid0
|
||||
DQpuYW1lcyBvZiBKb3kgY29tbWFuZHMgd29uJ3QgYmUgdW5kZXJsaW5lZC4gIFJlbGVhc2UgdGhl
|
||||
IGJ1dHRvbiB0byBleGVjdXRlDQp0aGUgY29tbWFuZC4NCg0KVGhlIG1pZGRsZSBtb3VzZSBidXR0
|
||||
b24gKHVzdWFsbHkgYSB3aGVlbCB0aGVzZSBkYXlzKSBzY3JvbGxzIHRoZSB0ZXh0IGJ1dA0KeW91
|
||||
IGNhbiBhbHNvIGNsaWNrIGFuZCBkcmFnIGFueSB2aWV3ZXIgd2l0aCBpdCB0byBtb3ZlIHRoYXQg
|
||||
dmlld2VyIHRvDQphbm90aGVyIHRyYWNrIG9yIHRvIGEgZGlmZmVyZW50IGxvY2F0aW9uIGluIHRo
|
||||
ZSBzYW1lIHRyYWNrLiAgVGhlcmUncyBubw0KZGlyZWN0IHZpc3VhbCBmZWVkYmFjayBmb3IgdGhp
|
||||
cyAoeWV0KSBidXQgdGhhdCBkb3Nlbid0IHNlZW0gdG8gaW1wYWlyIGl0cw0KdXNlZnVsbmVzcy4N
|
||||
Cg0KRjEsIEYyIC0gc2V0IHNlbGVjdGlvbiBiZWdpbiBhbmQgZW5kIG1hcmtlcnMgKGNydWRlIGJ1
|
||||
dCB1c2FibGUuKQ0KDQpGMyAtIGNvcHkgc2VsZWN0ZWQgdGV4dCB0byB0aGUgdG9wIG9mIHRoZSBz
|
||||
dGFjay4NCg0KU2hpZnQtRjMgLSBhcyBjb3B5IHRoZW4gcnVuICJwYXJzZSIgY29tbWFuZCBvbiB0
|
||||
aGUgc3RyaW5nLg0KDQpGNCAtIGN1dCBzZWxlY3RlZCB0ZXh0IHRvIHRoZSB0b3Agb2YgdGhlIHN0
|
||||
YWNrLg0KDQpTaGlmdC1GNCAtIGFzIGN1dCB0aGVuIHJ1biAicG9wIiAoZGVsZXRlIHNlbGVjdGlv
|
||||
bi4pDQoNCkpveQ0KDQpQcmV0dHkgbXVjaCBhbGwgb2YgdGhlIHJlc3Qgb2YgdGhlIGZ1bmN0aW9u
|
||||
YWxpdHkgb2YgdGhlIHN5c3RlbSBpcyBwcm92aWRlZA0KYnkgZXhlY3V0aW5nIEpveSBjb21tYW5k
|
||||
cyAoYWthIGZ1bmN0aW9ucywgYWthICJ3b3JkcyIgaW4gRm9ydGgpIGJ5IHJpZ2h0LQ0KY2xpY2tp
|
||||
bmcgb24gdGhlaXIgbmFtZXMgaW4gYW55IHRleHQuDQoNClRvIGdldCBoZWxwIG9uIGEgSm95IGZ1
|
||||
bmN0aW9uIHNlbGVjdCB0aGUgbmFtZSBvZiB0aGUgZnVuY3Rpb24gaW4gYQ0KVGV4dFZpZXdlciB1
|
||||
c2luZyBGMSBhbmQgRjIsIHRoZW4gcHJlc3Mgc2hpZnQtRjMgdG8gcGFyc2UgdGhlIHNlbGVjdGlv
|
||||
bi4NClRoZSBmdW5jdGlvbiAocmVhbGx5IGl0cyBTeW1ib2wpIHdpbGwgYXBwZWFyIG9uIHRoZSBz
|
||||
dGFjayBpbiBicmFja2V0cyAoYQ0KInF1b3RlZCBwcm9ncmFtIiBzdWNoIGFzICJbcG9wXSIuKSAg
|
||||
VGhlbiByaWdodC1jbGljayBvbiB0aGUgd29yZCBoZWxwIGluDQphbnkgVGV4dFZpZXdlciAoaWYg
|
||||
aXQncyBub3QgYWxyZWFkeSB0aGVyZSwganVzdCB0eXBlIGl0IGluIHNvbWV3aGVyZS4pDQpUaGlz
|
||||
IHdpbGwgcHJpbnQgdGhlIGRvY3N0cmluZyBvciBkZWZpbml0aW9uIG9mIHRoZSB3b3JkIChmdW5j
|
||||
dGlvbikgdG8NCnN0ZG91dC4gIEF0IHNvbWUgcG9pbnQgSSdsbCB3cml0ZSBhIHRoaW5nIHRvIHNl
|
||||
bmQgdGhhdCB0byB0aGUgbG9nLnR4dCBmaWxlDQppbnN0ZWFkLCBidXQgZm9yIG5vdyBsb29rIGZv
|
||||
ciBvdXRwdXQgaW4gdGhlIHRlcm1pbmFsLg0KUEsDBBQAAAAAAORmeE53f5peAwAAAAMAAAAMAAAA
|
||||
c3RhY2sucGlja2xlKHQuUEsBAhQAFAAAAAAA5GZ4Tv3gGVF+AwAAfgMAAA8AAAAAAAAAAAAAALaB
|
||||
AAAAAGRlZmluaXRpb25zLnR4dFBLAQIUABQAAAAAACFrpk7/HHjxGBYAABgWAAAKAAAAAAAAAAAA
|
||||
AAC2gasDAABsaWJyYXJ5LnB5UEsBAhQAFAAAAAAA5GZ4TkXs5NYLAAAACwAAAAcAAAAAAAAAAAAA
|
||||
ALaB6xkAAGxvZy50eHRQSwECFAAUAAAAAADkZnhOZ/a7zQIFAAACBQAACAAAAAAAAAAAAAAAtoEb
|
||||
GgAAbWVudS50eHRQSwECFAAUAAAAAADkZnhOAKs9xo4QAACOEAAACwAAAAAAAAAAAAAAtoFDHwAA
|
||||
c2NyYXRjaC50eHRQSwECFAAUAAAAAADkZnhOd3+aXgMAAAADAAAADAAAAAAAAAAAAAAAtoH6LwAA
|
||||
c3RhY2sucGlja2xlUEsFBgAAAAAGAAYAUwEAACcwAAAAAA==''')))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(create_data())
|
||||
-179
@@ -1,179 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Main Module
|
||||
======================================
|
||||
|
||||
Pulls everything together.
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from past.builtins import execfile
|
||||
from builtins import object
|
||||
from past.utils import old_div
|
||||
import os, sys, traceback
|
||||
import pygame
|
||||
from joy.library import initialize, DefinitionWrapper, SimpleFunctionWrapper
|
||||
from joy.vui import core, display, persist_task
|
||||
|
||||
|
||||
FULLSCREEN = '-f' in sys.argv
|
||||
|
||||
|
||||
JOY_HOME = os.environ.get('JOY_HOME')
|
||||
if JOY_HOME is None:
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
if not os.path.isabs(JOY_HOME):
|
||||
raise ValueError('what directory?')
|
||||
|
||||
|
||||
def load_definitions(pt, dictionary):
|
||||
'''Load definitions from ``definitions.txt``.'''
|
||||
lines = pt.open('definitions.txt')[1]
|
||||
for line in lines:
|
||||
if '==' in line:
|
||||
DefinitionWrapper.add_def(line, dictionary)
|
||||
|
||||
|
||||
def load_primitives(home, name_space):
|
||||
'''Load primitives from ``library.py``.'''
|
||||
fn = os.path.join(home, 'library.py')
|
||||
if os.path.exists(fn):
|
||||
execfile(fn, name_space)
|
||||
|
||||
|
||||
def init():
|
||||
'''
|
||||
Initialize the system.
|
||||
|
||||
* Init PyGame
|
||||
* Create main window
|
||||
* Start the PyGame clock
|
||||
* Set the event mask
|
||||
* Create the PersistTask
|
||||
|
||||
'''
|
||||
print('Initializing Pygame...')
|
||||
pygame.init()
|
||||
print('Creating window...')
|
||||
if FULLSCREEN:
|
||||
screen = pygame.display.set_mode()
|
||||
else:
|
||||
screen = pygame.display.set_mode((1024, 768))
|
||||
clock = pygame.time.Clock()
|
||||
pygame.event.set_allowed(None)
|
||||
pygame.event.set_allowed(core.ALLOWED_EVENTS)
|
||||
pt = persist_task.PersistTask(JOY_HOME)
|
||||
return screen, clock, pt
|
||||
|
||||
|
||||
def init_context(screen, clock, pt):
|
||||
'''
|
||||
More initialization
|
||||
|
||||
* Create the Joy dictionary
|
||||
* Create the Display
|
||||
* Open the log, menu, and scratch text viewers, and the stack pickle
|
||||
* Start the main loop
|
||||
* Create the World object
|
||||
* Register PersistTask and World message handlers with the Display
|
||||
* Load user function definitions.
|
||||
|
||||
'''
|
||||
D = initialize()
|
||||
d = display.Display(
|
||||
screen,
|
||||
D.__contains__,
|
||||
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
|
||||
)
|
||||
log = d.init_text(pt, 0, 0, 'log.txt')
|
||||
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.txt')
|
||||
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
|
||||
loop = core.TheLoop(d, clock)
|
||||
stack_id, stack_holder = pt.open('stack.pickle')
|
||||
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
|
||||
loop.install_task(pt.task_run, 10000) # save files every ten seconds
|
||||
d.handlers.append(pt.handle)
|
||||
d.handlers.append(world.handle)
|
||||
load_definitions(pt, D)
|
||||
return locals()
|
||||
|
||||
|
||||
def error_guard(loop, n=10):
|
||||
'''
|
||||
Run a loop function, retry for ``n`` exceptions.
|
||||
Prints tracebacks on ``sys.stderr``.
|
||||
'''
|
||||
error_count = 0
|
||||
while error_count < n:
|
||||
try:
|
||||
loop()
|
||||
break
|
||||
except:
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
error_count += 1
|
||||
|
||||
|
||||
class FileFaker(object):
|
||||
'''Pretends to be a file object but writes to log instead.'''
|
||||
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
|
||||
def write(self, text):
|
||||
'''Write text to log.'''
|
||||
self.log.append(text)
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
|
||||
def main(screen, clock, pt):
|
||||
'''
|
||||
Main function.
|
||||
|
||||
* Call ``init_context()``
|
||||
* Load primitives
|
||||
* Create an ``evaluate`` function that lets you just eval some Python code
|
||||
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
|
||||
* Start the main loop.
|
||||
'''
|
||||
name_space = init_context(screen, clock, pt)
|
||||
load_primitives(pt.home, name_space.copy())
|
||||
|
||||
@SimpleFunctionWrapper
|
||||
def evaluate(stack):
|
||||
'''Evaluate the Python code text on the top of the stack.'''
|
||||
code, stack = stack
|
||||
exec(code, name_space.copy())
|
||||
return stack
|
||||
|
||||
name_space['D']['evaluate'] = evaluate
|
||||
|
||||
|
||||
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
|
||||
try:
|
||||
error_guard(name_space['loop'].loop)
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
|
||||
return name_space['d']
|
||||
@@ -1,274 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Persist Task
|
||||
===========================
|
||||
|
||||
This module deals with persisting the "resources" (text files and the
|
||||
stack) to the git repo in the ``JOY_HOME`` directory.
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from builtins import object
|
||||
import os, pickle, traceback
|
||||
from collections import Counter
|
||||
from dulwich.errors import NotGitRepository
|
||||
from dulwich.repo import Repo
|
||||
from joy.vui import core, init_joy_home
|
||||
|
||||
|
||||
def open_repo(repo_dir=None, initialize=False):
|
||||
'''
|
||||
Open, or create, and return a Dulwich git repo object for the given
|
||||
directory. If the dir path doesn't exist it will be created. If it
|
||||
does exist but isn't a repo the result depends on the ``initialize``
|
||||
argument. If it is ``False`` (the default) a ``NotGitRepository``
|
||||
exception is raised, otherwise ``git init`` is effected in the dir.
|
||||
'''
|
||||
if not os.path.exists(repo_dir):
|
||||
os.makedirs(repo_dir, 0o700)
|
||||
return init_repo(repo_dir)
|
||||
try:
|
||||
return Repo(repo_dir)
|
||||
except NotGitRepository:
|
||||
if initialize:
|
||||
return init_repo(repo_dir)
|
||||
raise
|
||||
|
||||
|
||||
def init_repo(repo_dir):
|
||||
'''
|
||||
Initialize a git repository in the directory. Stage and commit all
|
||||
files (toplevel, not those in subdirectories if any) in the dir.
|
||||
'''
|
||||
repo = Repo.init(repo_dir)
|
||||
init_joy_home.initialize(repo_dir)
|
||||
repo.stage([
|
||||
fn
|
||||
for fn in os.listdir(repo_dir)
|
||||
if os.path.isfile(os.path.join(repo_dir, fn))
|
||||
])
|
||||
repo.do_commit('Initial commit.', committer=core.COMMITTER)
|
||||
return repo
|
||||
|
||||
|
||||
def make_repo_relative_path_maker(repo):
|
||||
'''
|
||||
Helper function to return a function that returns a path given a path,
|
||||
that's relative to the repository.
|
||||
'''
|
||||
c = repo.controldir()
|
||||
def repo_relative_path(path):
|
||||
return os.path.relpath(path, os.path.commonprefix((c, path)))
|
||||
return repo_relative_path
|
||||
|
||||
|
||||
class Resource(object):
|
||||
'''
|
||||
Handle the content of a text files as a list of lines, deal with
|
||||
saving it and staging the changes to a repo.
|
||||
'''
|
||||
|
||||
def __init__(self, filename, repo_relative_filename, thing=None):
|
||||
self.filename = filename
|
||||
self.repo_relative_filename = repo_relative_filename
|
||||
self.thing = thing or self._from_file(open(filename))
|
||||
|
||||
def _from_file(self, f):
|
||||
return f.read().splitlines()
|
||||
|
||||
def _to_file(self, f):
|
||||
for line in self.thing:
|
||||
print(line, file=f)
|
||||
|
||||
def persist(self, repo):
|
||||
'''
|
||||
Save the lines to the file and stage the file in the repo.
|
||||
'''
|
||||
with open(self.filename, 'w') as f:
|
||||
os.chmod(self.filename, 0o600)
|
||||
self._to_file(f)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
# For goodness's sake, write it to the disk already!
|
||||
repo.stage([self.repo_relative_filename])
|
||||
|
||||
|
||||
class PickledResource(Resource):
|
||||
'''
|
||||
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
|
||||
'''
|
||||
|
||||
def _from_file(self, f):
|
||||
return [pickle.load(f)]
|
||||
|
||||
def _to_file(self, f):
|
||||
pickle.dump(self.thing[0], f, protocol=2)
|
||||
|
||||
|
||||
class PersistTask(object):
|
||||
'''
|
||||
This class deals with saving changes to the git repo.
|
||||
'''
|
||||
|
||||
LIMIT = 10
|
||||
MAX_SAVE = 10
|
||||
|
||||
def __init__(self, home):
|
||||
self.home = home
|
||||
self.repo = open_repo(home)
|
||||
self._r = make_repo_relative_path_maker(self.repo)
|
||||
self.counter = Counter()
|
||||
self.store = {}
|
||||
|
||||
def open(self, name):
|
||||
'''
|
||||
Look up the named file in home and return its content_id and data.
|
||||
'''
|
||||
fn = os.path.join(self.home, name)
|
||||
content_id = name # hash(fn)
|
||||
try:
|
||||
resource = self.store[content_id]
|
||||
except KeyError:
|
||||
R = PickledResource if name.endswith('.pickle') else Resource
|
||||
resource = self.store[content_id] = R(fn, self._r(fn))
|
||||
return content_id, resource.thing
|
||||
|
||||
def handle(self, message):
|
||||
'''
|
||||
Handle messages, dispatch to ``handle_FOO()`` methods.
|
||||
'''
|
||||
if isinstance(message, core.OpenMessage):
|
||||
self.handle_open(message)
|
||||
elif isinstance(message, core.ModifyMessage):
|
||||
self.handle_modify(message)
|
||||
elif isinstance(message, core.PersistMessage):
|
||||
self.handle_persist(message)
|
||||
elif isinstance(message, core.ShutdownMessage):
|
||||
for content_id in self.counter:
|
||||
self.store[content_id].persist(self.repo)
|
||||
self.commit('shutdown')
|
||||
|
||||
def handle_open(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
message.content_id, message.thing = self.open(message.name)
|
||||
except:
|
||||
message.traceback = traceback.format_exc()
|
||||
message.status = core.ERROR
|
||||
else:
|
||||
message.status = core.SUCCESS
|
||||
|
||||
def handle_modify(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
content_id = message.details['content_id']
|
||||
except KeyError:
|
||||
return
|
||||
if not content_id:
|
||||
return
|
||||
self.counter[content_id] += 1
|
||||
if self.counter[content_id] > self.LIMIT:
|
||||
self.persist(content_id)
|
||||
self.commit('due to activity')
|
||||
|
||||
def handle_persist(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
try:
|
||||
resource = self.store[message.content_id]
|
||||
except KeyError:
|
||||
resource = self.handle_persist_new(message)
|
||||
resource.persist(self.repo)
|
||||
self.commit('by request from %r' % (message.sender,))
|
||||
|
||||
def handle_persist_new(self, message):
|
||||
'''
|
||||
Foo.
|
||||
'''
|
||||
name = message.content_id
|
||||
check_filename(name)
|
||||
fn = os.path.join(self.home, name)
|
||||
thing = message.details['thing']
|
||||
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
|
||||
resource = self.store[name] = R(fn, self._r(fn), thing)
|
||||
return resource
|
||||
|
||||
def persist(self, content_id):
|
||||
'''
|
||||
Persist a resource.
|
||||
'''
|
||||
del self.counter[content_id]
|
||||
self.store[content_id].persist(self.repo)
|
||||
|
||||
def task_run(self):
|
||||
'''
|
||||
Stage any outstanding changes.
|
||||
'''
|
||||
if not self.counter:
|
||||
return
|
||||
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
|
||||
self.persist(content_id)
|
||||
self.commit()
|
||||
|
||||
def commit(self, message='auto-commit'):
|
||||
'''
|
||||
Commit.
|
||||
'''
|
||||
return self.repo.do_commit(message, committer=core.COMMITTER)
|
||||
|
||||
def scan(self):
|
||||
'''
|
||||
Return a sorted list of all the files in the home dir.
|
||||
'''
|
||||
return sorted([
|
||||
fn
|
||||
for fn in os.listdir(self.home)
|
||||
if os.path.isfile(os.path.join(self.home, fn))
|
||||
])
|
||||
|
||||
|
||||
def check_filename(name):
|
||||
'''
|
||||
Sanity checks for filename.
|
||||
'''
|
||||
# TODO: improve this...
|
||||
if len(name) > 64:
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
left, dot, right = name.partition('.')
|
||||
if not left.isalnum() or dot and not right.isalnum():
|
||||
raise ValueError('bad name %r' % (name,))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
JOY_HOME = os.path.expanduser('~/.thun')
|
||||
pt = PersistTask(JOY_HOME)
|
||||
content_id, thing = pt.open('stack.pickle')
|
||||
pt.persist(content_id)
|
||||
print(pt.counter)
|
||||
mm = core.ModifyMessage(None, None, content_id=content_id)
|
||||
pt.handle(mm)
|
||||
print(pt.counter)
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Stack Viewer
|
||||
=================
|
||||
|
||||
'''
|
||||
from builtins import map, str
|
||||
from joy.utils.stack import expression_to_string, iter_stack
|
||||
from joy.vui import core, text_viewer
|
||||
|
||||
|
||||
MAX_WIDTH = 64
|
||||
|
||||
|
||||
def fsi(item):
|
||||
'''Format Stack Item'''
|
||||
if isinstance(item, tuple):
|
||||
res = '[%s]' % expression_to_string(item)
|
||||
elif isinstance(item, str):
|
||||
res = '"%s"' % item
|
||||
else:
|
||||
res = str(item)
|
||||
if len(res) > MAX_WIDTH:
|
||||
return res[:MAX_WIDTH - 3] + '...'
|
||||
return res
|
||||
|
||||
|
||||
class StackViewer(text_viewer.TextViewer):
|
||||
|
||||
def __init__(self, surface):
|
||||
super(StackViewer, self).__init__(surface)
|
||||
self.stack_holder = None
|
||||
self.content_id = 'stack viewer'
|
||||
|
||||
def _attach(self, display):
|
||||
if self.stack_holder:
|
||||
return
|
||||
om = core.OpenMessage(self, 'stack.pickle')
|
||||
display.broadcast(om)
|
||||
if om.status != core.SUCCESS:
|
||||
raise RuntimeError('stack unavailable')
|
||||
self.stack_holder = om.thing
|
||||
|
||||
def _update(self):
|
||||
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
|
||||
|
||||
def focus(self, display):
|
||||
self._attach(display)
|
||||
super(StackViewer, self).focus(display)
|
||||
|
||||
def handle(self, message):
|
||||
if (isinstance(message, core.ModifyMessage)
|
||||
and message.subject is self.stack_holder
|
||||
):
|
||||
self._update()
|
||||
self.draw_body()
|
||||
@@ -1,704 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Text Viewer
|
||||
=================
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from builtins import object, range, str, zip
|
||||
from past.builtins import basestring
|
||||
from past.utils import old_div
|
||||
import string
|
||||
import pygame
|
||||
from joy.utils.stack import expression_to_string
|
||||
from joy.vui.core import (
|
||||
ARROW_KEYS,
|
||||
BACKGROUND as BG,
|
||||
FOREGROUND as FG,
|
||||
CommandMessage,
|
||||
ModifyMessage,
|
||||
OpenMessage,
|
||||
SUCCESS,
|
||||
push,
|
||||
)
|
||||
from joy.vui import viewer, font_data
|
||||
#reload(viewer)
|
||||
|
||||
|
||||
MenuViewer = viewer.MenuViewer
|
||||
|
||||
|
||||
SELECTION_COLOR = 235, 255, 0, 32
|
||||
SELECTION_KEYS = {
|
||||
pygame.K_F1,
|
||||
pygame.K_F2,
|
||||
pygame.K_F3,
|
||||
pygame.K_F4,
|
||||
}
|
||||
STACK_CHATTER_KEYS = {
|
||||
pygame.K_F5,
|
||||
pygame.K_F6,
|
||||
pygame.K_F7,
|
||||
pygame.K_F8,
|
||||
}
|
||||
|
||||
|
||||
def _is_command(display, word):
|
||||
return display.lookup(word) or word.isdigit() or all(
|
||||
not s or s.isdigit() for s in word.split('.', 1)
|
||||
) and len(word) > 1
|
||||
|
||||
|
||||
def format_stack_item(content):
|
||||
if isinstance(content, tuple):
|
||||
return '[%s]' % expression_to_string(content)
|
||||
return str(content)
|
||||
|
||||
|
||||
class Font(object):
|
||||
|
||||
IMAGE = pygame.image.load(font_data.data, 'Iosevka12.BMP')
|
||||
LOOKUP = (string.ascii_letters +
|
||||
string.digits +
|
||||
'''@#$&_~|`'"%^=-+*/\\<>[]{}(),.;:!?''')
|
||||
|
||||
def __init__(self, char_w=8, char_h=19, line_h=19):
|
||||
self.char_w = char_w
|
||||
self.char_h = char_h
|
||||
self.line_h = line_h
|
||||
|
||||
def size(self, text):
|
||||
return self.char_w * len(text), self.line_h
|
||||
|
||||
def render(self, text):
|
||||
surface = pygame.Surface(self.size(text))
|
||||
surface.fill(BG)
|
||||
x = 0
|
||||
for ch in text:
|
||||
if not ch.isspace():
|
||||
try:
|
||||
i = self.LOOKUP.index(ch)
|
||||
except ValueError:
|
||||
# render a lil box...
|
||||
r = (x + 1, old_div(self.line_h, 2) - 3,
|
||||
self.char_w - 2, old_div(self.line_h, 2))
|
||||
pygame.draw.rect(surface, FG, r, 1)
|
||||
else:
|
||||
iy, ix = divmod(i, 26)
|
||||
ix *= self.char_w
|
||||
iy *= self.char_h
|
||||
area = ix, iy, self.char_w, self.char_h
|
||||
surface.blit(self.IMAGE, (x, 0), area)
|
||||
x += self.char_w
|
||||
return surface
|
||||
|
||||
def __contains__(self, char):
|
||||
assert len(char) == 1, repr(char)
|
||||
return char in self.LOOKUP
|
||||
|
||||
|
||||
FONT = Font()
|
||||
|
||||
|
||||
class TextViewer(MenuViewer):
|
||||
|
||||
MINIMUM_HEIGHT = FONT.line_h + 3
|
||||
CLOSE_TEXT = FONT.render('close')
|
||||
GROW_TEXT = FONT.render('grow')
|
||||
|
||||
class Cursor(object):
|
||||
|
||||
def __init__(self, viewer):
|
||||
self.v = viewer
|
||||
self.x = self.y = 0
|
||||
self.w, self.h = 2, FONT.line_h
|
||||
self.mem = pygame.Surface((self.w, self.h))
|
||||
self.can_fade = False
|
||||
|
||||
def set_to(self, x, y):
|
||||
self.fade()
|
||||
self.x, self.y = x, y
|
||||
self.draw()
|
||||
|
||||
def draw(self):
|
||||
r = self.x * FONT.char_w, self.screen_y(), self.w, self.h
|
||||
self.mem.blit(self.v.body_surface, (0, 0), r)
|
||||
self.v.body_surface.fill(FG, r)
|
||||
self.can_fade = True
|
||||
|
||||
def fade(self):
|
||||
if self.can_fade:
|
||||
dest = self.x * FONT.char_w, self.screen_y()
|
||||
self.v.body_surface.blit(self.mem, dest)
|
||||
self.can_fade = False
|
||||
|
||||
def screen_y(self, row=None):
|
||||
if row is None: row = self.y
|
||||
return (row - self.v.at_line) * FONT.line_h
|
||||
|
||||
def up(self, _mod):
|
||||
if self.y:
|
||||
self.fade()
|
||||
self.y -= 1
|
||||
self.x = min(self.x, len(self.v.lines[self.y]))
|
||||
self.draw()
|
||||
|
||||
def down(self, _mod):
|
||||
if self.y < len(self.v.lines) - 1:
|
||||
self.fade()
|
||||
self.y += 1
|
||||
self.x = min(self.x, len(self.v.lines[self.y]))
|
||||
self.draw()
|
||||
self._check_scroll()
|
||||
|
||||
def left(self, _mod):
|
||||
if self.x:
|
||||
self.fade()
|
||||
self.x -= 1
|
||||
self.draw()
|
||||
elif self.y:
|
||||
self.fade()
|
||||
self.y -= 1
|
||||
self.x = len(self.v.lines[self.y])
|
||||
self.draw()
|
||||
self._check_scroll()
|
||||
|
||||
def right(self, _mod):
|
||||
if self.x < len(self.v.lines[self.y]):
|
||||
self.fade()
|
||||
self.x += 1
|
||||
self.draw()
|
||||
elif self.y < len(self.v.lines) - 1:
|
||||
self.fade()
|
||||
self.y += 1
|
||||
self.x = 0
|
||||
self.draw()
|
||||
self._check_scroll()
|
||||
|
||||
def _check_scroll(self):
|
||||
if self.y < self.v.at_line:
|
||||
self.v.scroll_down()
|
||||
elif self.y > self.v.at_line + self.v.h_in_lines:
|
||||
self.v.scroll_up()
|
||||
|
||||
def __init__(self, surface):
|
||||
self.cursor = self.Cursor(self)
|
||||
MenuViewer.__init__(self, surface)
|
||||
self.lines = ['']
|
||||
self.content_id = None
|
||||
self.at_line = 0
|
||||
self.bg = BG
|
||||
self.command = self.command_rect = None
|
||||
self._sel_start = self._sel_end = None
|
||||
|
||||
def resurface(self, surface):
|
||||
self.cursor.fade()
|
||||
MenuViewer.resurface(self, surface)
|
||||
|
||||
w, h = self.CLOSE_TEXT.get_size()
|
||||
self.close_rect = pygame.rect.Rect(self.w - 2 - w, 1, w, h)
|
||||
w, h = self.GROW_TEXT.get_size()
|
||||
self.grow_rect = pygame.rect.Rect(1, 1, w, h)
|
||||
|
||||
self.body_surface = surface.subsurface(self.body_rect)
|
||||
self.line_w = old_div(self.body_rect.w, FONT.char_w) + 1
|
||||
self.h_in_lines = old_div(self.body_rect.h, FONT.line_h) - 1
|
||||
self.command_rect = self.command = None
|
||||
self._sel_start = self._sel_end = None
|
||||
|
||||
def handle(self, message):
|
||||
if super(TextViewer, self).handle(message):
|
||||
return
|
||||
if (isinstance(message, ModifyMessage)
|
||||
and message.subject is self.lines
|
||||
):
|
||||
# TODO: check self.at_line
|
||||
self.draw_body()
|
||||
|
||||
# Drawing
|
||||
|
||||
def draw_menu(self):
|
||||
#MenuViewer.draw_menu(self)
|
||||
self.surface.blit(self.GROW_TEXT, (1, 1))
|
||||
self.surface.blit(self.CLOSE_TEXT,
|
||||
(self.w - 2 - self.close_rect.w, 1))
|
||||
if self.content_id:
|
||||
self.surface.blit(FONT.render('| ' + self.content_id),
|
||||
(self.grow_rect.w + FONT.char_w + 3, 1))
|
||||
self.surface.fill( # light grey background
|
||||
(196, 196, 196),
|
||||
(0, 0, self.w - 1, self.MINIMUM_HEIGHT),
|
||||
pygame.BLEND_MULT
|
||||
)
|
||||
|
||||
def draw_body(self):
|
||||
MenuViewer.draw_body(self)
|
||||
ys = range(0, self.body_rect.height, FONT.line_h)
|
||||
ls = self.lines[self.at_line:self.at_line + self.h_in_lines + 2]
|
||||
for y, line in zip(ys, ls):
|
||||
self.draw_line(y, line)
|
||||
|
||||
def draw_line(self, y, line):
|
||||
surface = FONT.render(line[:self.line_w])
|
||||
self.body_surface.blit(surface, (0, y))
|
||||
|
||||
def _redraw_line(self, row):
|
||||
try: line = self.lines[row]
|
||||
except IndexError: line = ' ' * self.line_w
|
||||
else:
|
||||
n = self.line_w - len(line)
|
||||
if n > 0: line = line + ' ' * n
|
||||
self.draw_line(self.cursor.screen_y(row), line)
|
||||
|
||||
# General Functionality
|
||||
|
||||
def focus(self, display):
|
||||
self.cursor.v = self
|
||||
self.cursor.draw()
|
||||
|
||||
def unfocus(self):
|
||||
self.cursor.fade()
|
||||
|
||||
def scroll_up(self):
|
||||
if self.at_line < len(self.lines) - 1:
|
||||
self._fade_command()
|
||||
self._deselect()
|
||||
self._sel_start = self._sel_end = None
|
||||
self.at_line += 1
|
||||
self.body_surface.scroll(0, -FONT.line_h)
|
||||
row = self.h_in_lines + self.at_line
|
||||
self._redraw_line(row)
|
||||
self._redraw_line(row + 1)
|
||||
self.cursor.draw()
|
||||
|
||||
def scroll_down(self):
|
||||
if self.at_line:
|
||||
self._fade_command()
|
||||
self._deselect()
|
||||
self._sel_start = self._sel_end = None
|
||||
self.at_line -= 1
|
||||
self.body_surface.scroll(0, FONT.line_h)
|
||||
self._redraw_line(self.at_line)
|
||||
self.cursor.draw()
|
||||
|
||||
def command_down(self, display, x, y):
|
||||
if self.command_rect and self.command_rect.collidepoint(x, y):
|
||||
return
|
||||
self._fade_command()
|
||||
line, column, _row = self.at(x, y)
|
||||
word_start = line.rfind(' ', 0, column) + 1
|
||||
word_end = line.find(' ', column)
|
||||
if word_end == -1: word_end = len(line)
|
||||
word = line[word_start:word_end]
|
||||
if not _is_command(display, word):
|
||||
return
|
||||
r = self.command_rect = pygame.Rect(
|
||||
word_start * FONT.char_w, # x
|
||||
old_div(y, FONT.line_h) * FONT.line_h, # y
|
||||
len(word) * FONT.char_w, # w
|
||||
FONT.line_h # h
|
||||
)
|
||||
pygame.draw.line(self.body_surface, FG, r.bottomleft, r.bottomright)
|
||||
self.command = word
|
||||
|
||||
def command_up(self, display):
|
||||
if self.command:
|
||||
command = self.command
|
||||
self._fade_command()
|
||||
display.broadcast(CommandMessage(self, command))
|
||||
|
||||
def _fade_command(self):
|
||||
self.command = None
|
||||
r, self.command_rect = self.command_rect, None
|
||||
if r:
|
||||
pygame.draw.line(self.body_surface, BG, r.bottomleft, r.bottomright)
|
||||
|
||||
def at(self, x, y):
|
||||
'''
|
||||
Given screen coordinates return the line, row, and column of the
|
||||
character there.
|
||||
'''
|
||||
row = self.at_line + old_div(y, FONT.line_h)
|
||||
try:
|
||||
line = self.lines[row]
|
||||
except IndexError:
|
||||
row = len(self.lines) - 1
|
||||
line = self.lines[row]
|
||||
column = len(line)
|
||||
else:
|
||||
column = min(old_div(x, FONT.char_w), len(line))
|
||||
return line, column, row
|
||||
|
||||
# Event Processing
|
||||
|
||||
def body_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
_line, column, row = self.at(x, y)
|
||||
self.cursor.set_to(column, row)
|
||||
elif button == 2:
|
||||
if pygame.KMOD_SHIFT & pygame.key.get_mods():
|
||||
self.scroll_up()
|
||||
else:
|
||||
self.scroll_down()
|
||||
elif button == 3:
|
||||
self.command_down(display, x, y)
|
||||
elif button == 4: self.scroll_down()
|
||||
elif button == 5: self.scroll_up()
|
||||
|
||||
def menu_click(self, display, x, y, button):
|
||||
if MenuViewer.menu_click(self, display, x, y, button):
|
||||
return True
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
if MenuViewer.mouse_up(self, display, x, y, button):
|
||||
return True
|
||||
elif button == 3 and self.body_rect.collidepoint(x, y):
|
||||
self.command_up(display)
|
||||
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
|
||||
button0, button1, button2):
|
||||
return True
|
||||
if (button0
|
||||
and display.focused_viewer is self
|
||||
and self.body_rect.collidepoint(x, y)
|
||||
):
|
||||
bx, by = self.body_rect.topleft
|
||||
_line, column, row = self.at(x - bx, y - by)
|
||||
self.cursor.set_to(column, row)
|
||||
elif button2 and self.body_rect.collidepoint(x, y):
|
||||
bx, by = self.body_rect.topleft
|
||||
self.command_down(display, x - bx, y - by)
|
||||
|
||||
def close(self):
|
||||
self._sel_start = self._sel_end = None
|
||||
|
||||
def key_down(self, display, uch, key, mod):
|
||||
|
||||
if key in SELECTION_KEYS:
|
||||
self._selection_key(display, key, mod)
|
||||
return
|
||||
if key in STACK_CHATTER_KEYS:
|
||||
self._stack_chatter_key(display, key, mod)
|
||||
return
|
||||
if key in ARROW_KEYS:
|
||||
self._arrow_key(key, mod)
|
||||
return
|
||||
|
||||
line, i = self.lines[self.cursor.y], self.cursor.x
|
||||
modified = ()
|
||||
if key == pygame.K_RETURN:
|
||||
self._return_key(mod, line, i)
|
||||
modified = True
|
||||
elif key == pygame.K_BACKSPACE:
|
||||
modified = self._backspace_key(mod, line, i)
|
||||
elif key == pygame.K_DELETE:
|
||||
modified = self._delete_key(mod, line, i)
|
||||
elif key == pygame.K_INSERT:
|
||||
modified = self._insert_key(display, mod, line, i)
|
||||
elif uch and uch in FONT or uch == ' ':
|
||||
self._printable_key(uch, mod, line, i)
|
||||
modified = True
|
||||
else:
|
||||
print('%r %i %s' % (uch, key, bin(mod)))
|
||||
|
||||
if modified:
|
||||
# The selection is fragile.
|
||||
self._deselect()
|
||||
self._sel_start = self._sel_end = None
|
||||
message = ModifyMessage(
|
||||
self, self.lines, content_id=self.content_id)
|
||||
display.broadcast(message)
|
||||
|
||||
def _stack_chatter_key(self, display, key, mod):
|
||||
if key == pygame.K_F5:
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
command = 'roll<'
|
||||
else:
|
||||
command = 'swap'
|
||||
elif key == pygame.K_F6:
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
command = 'roll>'
|
||||
else:
|
||||
command = 'dup'
|
||||
elif key == pygame.K_F7:
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
command = 'tuck'
|
||||
else:
|
||||
command = 'over'
|
||||
## elif key == pygame.K_F8:
|
||||
## if mod & pygame.KMOD_SHIFT:
|
||||
## command = ''
|
||||
## else:
|
||||
## command = ''
|
||||
else:
|
||||
return
|
||||
display.broadcast(CommandMessage(self, command))
|
||||
|
||||
# Selection Handling
|
||||
|
||||
def _selection_key(self, display, key, mod):
|
||||
self.cursor.fade()
|
||||
self._deselect()
|
||||
if key == pygame.K_F1: # set sel start
|
||||
self._sel_start = self.cursor.y, self.cursor.x
|
||||
self._update_selection()
|
||||
elif key == pygame.K_F2: # set sel end
|
||||
self._sel_end = self.cursor.y, self.cursor.x
|
||||
self._update_selection()
|
||||
elif key == pygame.K_F3: # copy
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
self._parse_selection(display)
|
||||
else:
|
||||
self._copy_selection(display)
|
||||
self._update_selection()
|
||||
elif key == pygame.K_F4: # cut or delete
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
self._delete_selection(display)
|
||||
else:
|
||||
self._cut_selection(display)
|
||||
self.cursor.draw()
|
||||
|
||||
def _deselect(self):
|
||||
if self._has_selection():
|
||||
srow, erow = self._sel_start[0], self._sel_end[0]
|
||||
# Just erase the whole selection.
|
||||
for r in range(min(srow, erow), max(srow, erow) + 1):
|
||||
self._redraw_line(r)
|
||||
|
||||
def _copy_selection(self, display):
|
||||
if push(self, self._get_selection(), display.broadcast) == SUCCESS:
|
||||
return True
|
||||
## om = OpenMessage(self, 'stack.pickle')
|
||||
## display.broadcast(om)
|
||||
## if om.status == SUCCESS:
|
||||
## selection = self._get_selection()
|
||||
## om.thing[0] = selection, om.thing[0]
|
||||
## display.broadcast(ModifyMessage(
|
||||
## self, om.thing, content_id=om.content_id))
|
||||
|
||||
def _parse_selection(self, display):
|
||||
if self._has_selection():
|
||||
if self._copy_selection(display):
|
||||
display.broadcast(CommandMessage(self, 'parse'))
|
||||
|
||||
def _cut_selection(self, display):
|
||||
if self._has_selection():
|
||||
if self._copy_selection(display):
|
||||
self._delete_selection(display)
|
||||
|
||||
def _delete_selection(self, display):
|
||||
if not self._has_selection():
|
||||
return
|
||||
self.cursor.fade()
|
||||
srow, scolumn, erow, ecolumn = self._selection_coords()
|
||||
if srow == erow:
|
||||
line = self.lines[srow]
|
||||
self.lines[srow] = line[:scolumn] + line[ecolumn:]
|
||||
else:
|
||||
left = self.lines[srow][:scolumn]
|
||||
right = self.lines[erow][ecolumn:]
|
||||
self.lines[srow:erow + 1] = [left + right]
|
||||
self.draw_body()
|
||||
self.cursor.set_to(srow, scolumn)
|
||||
display.broadcast(ModifyMessage(
|
||||
self, self.lines, content_id=self.content_id))
|
||||
|
||||
def _has_selection(self):
|
||||
return (self._sel_start
|
||||
and self._sel_end
|
||||
and self._sel_start != self._sel_end)
|
||||
|
||||
def _get_selection(self):
|
||||
'''Return the current selection if any as a single string.'''
|
||||
if not self._has_selection():
|
||||
return ''
|
||||
srow, scolumn, erow, ecolumn = self._selection_coords()
|
||||
if srow == erow:
|
||||
return str(self.lines[srow][scolumn:ecolumn])
|
||||
lines = []
|
||||
assert srow < erow
|
||||
while srow <= erow:
|
||||
line = self.lines[srow]
|
||||
e = ecolumn if srow == erow else len(line)
|
||||
lines.append(line[scolumn:e])
|
||||
scolumn = 0
|
||||
srow += 1
|
||||
return str('\n'.join(lines))
|
||||
|
||||
def _selection_coords(self):
|
||||
(srow, scolumn), (erow, ecolumn) = (
|
||||
min(self._sel_start, self._sel_end),
|
||||
max(self._sel_start, self._sel_end)
|
||||
)
|
||||
return srow, scolumn, erow, ecolumn
|
||||
|
||||
def _update_selection(self):
|
||||
if self._sel_start is None and self._sel_end:
|
||||
self._sel_start = self._sel_end
|
||||
elif self._sel_end is None and self._sel_start:
|
||||
self._sel_end = self._sel_start
|
||||
assert self._sel_start and self._sel_end
|
||||
if self._sel_start != self._sel_end:
|
||||
for rect in self._iter_selection_rectangles():
|
||||
self.body_surface.fill(
|
||||
SELECTION_COLOR,
|
||||
rect,
|
||||
pygame.BLEND_RGBA_MULT
|
||||
)
|
||||
|
||||
def _iter_selection_rectangles(self, ):
|
||||
srow, scolumn, erow, ecolumn = self._selection_coords()
|
||||
if srow == erow:
|
||||
yield (
|
||||
scolumn * FONT.char_w,
|
||||
self.cursor.screen_y(srow),
|
||||
(ecolumn - scolumn) * FONT.char_w,
|
||||
FONT.line_h
|
||||
)
|
||||
return
|
||||
lines = self.lines[srow:erow + 1]
|
||||
assert len(lines) >= 2
|
||||
first_line = lines[0]
|
||||
yield (
|
||||
scolumn * FONT.char_w,
|
||||
self.cursor.screen_y(srow),
|
||||
(len(first_line) - scolumn) * FONT.char_w,
|
||||
FONT.line_h
|
||||
)
|
||||
yield (
|
||||
0,
|
||||
self.cursor.screen_y(erow),
|
||||
ecolumn * FONT.char_w,
|
||||
FONT.line_h
|
||||
)
|
||||
if len(lines) > 2:
|
||||
for line in lines[1:-1]:
|
||||
srow += 1
|
||||
yield (
|
||||
0,
|
||||
self.cursor.screen_y(srow),
|
||||
len(line) * FONT.char_w,
|
||||
FONT.line_h
|
||||
)
|
||||
|
||||
# Key Handlers
|
||||
|
||||
def _printable_key(self, uch, _mod, line, i):
|
||||
line = line[:i] + uch + line[i:]
|
||||
self.lines[self.cursor.y] = line
|
||||
self.cursor.fade()
|
||||
self.cursor.x += 1
|
||||
self.draw_line(self.cursor.screen_y(), line)
|
||||
self.cursor.draw()
|
||||
|
||||
def _backspace_key(self, _mod, line, i):
|
||||
res = False
|
||||
if i:
|
||||
line = line[:i - 1] + line[i:]
|
||||
self.lines[self.cursor.y] = line
|
||||
self.cursor.fade()
|
||||
self.cursor.x -= 1
|
||||
self.draw_line(self.cursor.screen_y(), line + ' ')
|
||||
self.cursor.draw()
|
||||
res = True
|
||||
elif self.cursor.y:
|
||||
y = self.cursor.y
|
||||
left, right = self.lines[y - 1:y + 1]
|
||||
self.lines[y - 1:y + 1] = [left + right]
|
||||
self.cursor.x = len(left)
|
||||
self.cursor.y -= 1
|
||||
self.draw_body()
|
||||
self.cursor.draw()
|
||||
res = True
|
||||
return res
|
||||
|
||||
def _delete_key(self, _mod, line, i):
|
||||
res = False
|
||||
if i < len(line):
|
||||
line = line[:i] + line[i + 1:]
|
||||
self.lines[self.cursor.y] = line
|
||||
self.cursor.fade()
|
||||
self.draw_line(self.cursor.screen_y(), line + ' ')
|
||||
self.cursor.draw()
|
||||
res = True
|
||||
elif self.cursor.y < len(self.lines) - 1:
|
||||
y = self.cursor.y
|
||||
left, right = self.lines[y:y + 2]
|
||||
self.lines[y:y + 2] = [left + right]
|
||||
self.draw_body()
|
||||
self.cursor.draw()
|
||||
res = True
|
||||
return res
|
||||
|
||||
def _arrow_key(self, key, mod):
|
||||
if key == pygame.K_UP: self.cursor.up(mod)
|
||||
elif key == pygame.K_DOWN: self.cursor.down(mod)
|
||||
elif key == pygame.K_LEFT: self.cursor.left(mod)
|
||||
elif key == pygame.K_RIGHT: self.cursor.right(mod)
|
||||
|
||||
def _return_key(self, _mod, line, i):
|
||||
self.cursor.fade()
|
||||
# Ignore the mods for now.
|
||||
n = self.cursor.y
|
||||
self.lines[n:n + 1] = [line[:i], line[i:]]
|
||||
self.cursor.y += 1
|
||||
self.cursor.x = 0
|
||||
if self.cursor.y > self.at_line + self.h_in_lines:
|
||||
self.scroll_up()
|
||||
else:
|
||||
self.draw_body()
|
||||
self.cursor.draw()
|
||||
|
||||
def _insert_key(self, display, mod, _line, _i):
|
||||
om = OpenMessage(self, 'stack.pickle')
|
||||
display.broadcast(om)
|
||||
if om.status != SUCCESS:
|
||||
return
|
||||
stack = om.thing[0]
|
||||
if stack:
|
||||
content = format_stack_item(stack[0])
|
||||
if self.insert(content):
|
||||
if mod & pygame.KMOD_SHIFT:
|
||||
display.broadcast(CommandMessage(self, 'pop'))
|
||||
return True
|
||||
|
||||
def insert(self, content):
|
||||
assert isinstance(content, basestring), repr(content)
|
||||
if content:
|
||||
self.cursor.fade()
|
||||
row, column = self.cursor.y, self.cursor.x
|
||||
line = self.lines[row]
|
||||
lines = (line[:column] + content + line[column:]).splitlines()
|
||||
self.lines[row:row + 1] = lines
|
||||
self.draw_body()
|
||||
self.cursor.y = row + len(lines) - 1
|
||||
self.cursor.x = len(lines[-1]) - len(line) + column
|
||||
self.cursor.draw()
|
||||
return True
|
||||
|
||||
def append(self, content):
|
||||
self.cursor.fade()
|
||||
self.cursor.y = len(self.lines) - 1
|
||||
self.cursor.x = len(self.lines[self.cursor.y])
|
||||
self.insert(content)
|
||||
@@ -1,249 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2019 Simon Forman
|
||||
#
|
||||
# This file is part of joy.py
|
||||
#
|
||||
# joy.py is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# joy.py is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with joy.py. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
|
||||
Viewer
|
||||
=================
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
from __future__ import division
|
||||
from builtins import chr, object
|
||||
from past.utils import old_div
|
||||
import pygame
|
||||
from joy.vui.core import BACKGROUND, FOREGROUND
|
||||
|
||||
|
||||
class Viewer(object):
|
||||
'''
|
||||
Base Viewer class
|
||||
'''
|
||||
|
||||
MINIMUM_HEIGHT = 11
|
||||
|
||||
def __init__(self, surface):
|
||||
self.resurface(surface)
|
||||
self.last_touch = 0, 0
|
||||
|
||||
def resurface(self, surface):
|
||||
self.w, self.h = surface.get_width(), surface.get_height()
|
||||
self.surface = surface
|
||||
|
||||
def split(self, y):
|
||||
'''
|
||||
Split the viewer at the y coordinate (which is relative to the
|
||||
viewer's surface and must be inside it somewhere) and return the
|
||||
remaining height. The upper part of the viewer remains (and gets
|
||||
redrawn on a new surface) and the lower space is now available
|
||||
for e.g. a new viewer.
|
||||
'''
|
||||
assert y >= self.MINIMUM_HEIGHT
|
||||
new_viewer_h = self.h - y
|
||||
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
|
||||
if y <= self.last_touch[1]: self.last_touch = 0, 0
|
||||
self.draw()
|
||||
return new_viewer_h
|
||||
|
||||
def handle(self, message):
|
||||
assert self is not message.sender
|
||||
pass
|
||||
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
self.surface.fill(BACKGROUND)
|
||||
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
|
||||
# Right-hand side.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
|
||||
# Between header and body.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
|
||||
# Bottom.
|
||||
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
|
||||
|
||||
def close(self):
|
||||
'''Close the viewer and release any resources, etc...'''
|
||||
|
||||
def focus(self, display):
|
||||
pass
|
||||
|
||||
def unfocus(self):
|
||||
pass
|
||||
|
||||
# Event handling.
|
||||
|
||||
def mouse_down(self, display, x, y, button):
|
||||
self.last_touch = x, y
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
pass
|
||||
|
||||
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
|
||||
pass
|
||||
|
||||
def key_up(self, display, key, mod):
|
||||
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
|
||||
def key_down(self, display, uch, key, mod):
|
||||
pass
|
||||
|
||||
|
||||
class MenuViewer(Viewer):
|
||||
|
||||
'''
|
||||
MenuViewer class
|
||||
'''
|
||||
|
||||
MINIMUM_HEIGHT = 26
|
||||
|
||||
def __init__(self, surface):
|
||||
Viewer.__init__(self, surface)
|
||||
self.resizing = 0
|
||||
self.bg = 100, 150, 100
|
||||
|
||||
def resurface(self, surface):
|
||||
Viewer.resurface(self, surface)
|
||||
n = self.MINIMUM_HEIGHT - 2
|
||||
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
|
||||
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
|
||||
self.body_rect = pygame.rect.Rect(
|
||||
0, self.MINIMUM_HEIGHT + 1,
|
||||
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
|
||||
|
||||
def draw(self):
|
||||
'''Draw the viewer onto its surface.'''
|
||||
Viewer.draw(self)
|
||||
if not self.resizing:
|
||||
self.draw_menu()
|
||||
self.draw_body()
|
||||
|
||||
def draw_menu(self):
|
||||
# menu buttons
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
|
||||
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
|
||||
|
||||
def draw_body(self):
|
||||
self.surface.fill(self.bg, self.body_rect)
|
||||
|
||||
def mouse_down(self, display, x, y, button):
|
||||
Viewer.mouse_down(self, display, x, y, button)
|
||||
if y <= self.MINIMUM_HEIGHT:
|
||||
self.menu_click(display, x, y, button)
|
||||
else:
|
||||
bx, by = self.body_rect.topleft
|
||||
self.body_click(display, x - bx, y - by, button)
|
||||
|
||||
def body_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.draw_an_a(x, y)
|
||||
|
||||
def menu_click(self, display, x, y, button):
|
||||
if button == 1:
|
||||
self.resizing = 1
|
||||
elif button == 3:
|
||||
if self.close_rect.collidepoint(x, y):
|
||||
display.close_viewer(self)
|
||||
return True
|
||||
elif self.grow_rect.collidepoint(x, y):
|
||||
display.grow_viewer(self)
|
||||
return True
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
|
||||
if button == 1 and self.resizing:
|
||||
if self.resizing == 2:
|
||||
self.resizing = 0
|
||||
self.draw()
|
||||
display.done_resizing()
|
||||
self.resizing = 0
|
||||
return True
|
||||
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if self.resizing and button0:
|
||||
self.resizing = 2
|
||||
display.change_viewer(self, rel_y, relative=True)
|
||||
return True
|
||||
else:
|
||||
self.resizing = 0
|
||||
#self.draw_an_a(x, y)
|
||||
|
||||
def key_up(self, display, key, mod):
|
||||
if Viewer.key_up(self, display, key, mod):
|
||||
return True
|
||||
|
||||
def draw_an_a(self, x, y):
|
||||
# Draw a crude letter A.
|
||||
lw, lh = 10, 14
|
||||
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
|
||||
except ValueError: return
|
||||
draw_a(surface, blend=1)
|
||||
|
||||
|
||||
class SomeViewer(MenuViewer):
|
||||
|
||||
def __init__(self, surface):
|
||||
MenuViewer.__init__(self, surface)
|
||||
|
||||
def resurface(self, surface):
|
||||
MenuViewer.resurface(self, surface)
|
||||
|
||||
def draw_menu(self):
|
||||
MenuViewer.draw_menu(self)
|
||||
|
||||
def draw_body(self):
|
||||
pass
|
||||
|
||||
def body_click(self, display, x, y, button):
|
||||
pass
|
||||
|
||||
def menu_click(self, display, x, y, button):
|
||||
if MenuViewer.menu_click(self, display, x, y, button):
|
||||
return True
|
||||
|
||||
def mouse_up(self, display, x, y, button):
|
||||
if MenuViewer.mouse_up(self, display, x, y, button):
|
||||
return True
|
||||
|
||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
|
||||
button0, button1, button2):
|
||||
return True
|
||||
|
||||
def key_down(self, display, uch, key, mod):
|
||||
try:
|
||||
print(chr(key), end=' ')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# Note that Oberon book says that if you split at the exact top of a viewer
|
||||
# it should close, and I think this implies the new viewer gets the old
|
||||
# viewer's whole height. I haven't implemented that yet, so the edge-case
|
||||
# in the code is broken by "intent" for now..
|
||||
|
||||
|
||||
def draw_a(surface, color=FOREGROUND, blend=False):
|
||||
w, h = surface.get_width() - 2, surface.get_height() - 2
|
||||
pygame.draw.aalines(surface, color, False, (
|
||||
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
|
||||
), blend)
|
||||
Reference in New Issue
Block a user