Switch to tabs for indentation.
Instead of a mix of 2- and 4-space tabs just use actual tabs. ;-P
This commit is contained in:
parent
2fb610e733
commit
078f29830d
|
|
@ -16,17 +16,17 @@ import base64, os, io, zipfile
|
||||||
|
|
||||||
|
|
||||||
def initialize(joy_home):
|
def initialize(joy_home):
|
||||||
Z.extractall(joy_home)
|
Z.extractall(joy_home)
|
||||||
|
|
||||||
|
|
||||||
def create_data(from_dir='./default_joy_home'):
|
def create_data(from_dir='./default_joy_home'):
|
||||||
f = io.StringIO()
|
f = io.StringIO()
|
||||||
z = zipfile.ZipFile(f, mode='w')
|
z = zipfile.ZipFile(f, mode='w')
|
||||||
for fn in os.listdir(from_dir):
|
for fn in os.listdir(from_dir):
|
||||||
from_fn = os.path.join(from_dir, fn)
|
from_fn = os.path.join(from_dir, fn)
|
||||||
z.write(from_fn, fn)
|
z.write(from_fn, fn)
|
||||||
z.close()
|
z.close()
|
||||||
return base64.encodestring(f.getvalue())
|
return base64.encodestring(f.getvalue())
|
||||||
|
|
||||||
|
|
||||||
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
||||||
|
|
@ -103,4 +103,4 @@ AAAAAAAAAAAAtIH2BgAAZGVmaW5pdGlvbnMudHh0UEsFBgAAAAAFAAUAHgEAAF0OAAAAAA==''')))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(create_data())
|
print(create_data())
|
||||||
|
|
|
||||||
120
joy/gui/main.py
120
joy/gui/main.py
|
|
@ -23,10 +23,10 @@ repo = init_home(JOY_HOME)
|
||||||
|
|
||||||
_log = logging.getLogger(__name__)
|
_log = logging.getLogger(__name__)
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
|
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
|
||||||
filename=os.path.join(JOY_HOME, 'thun.log'),
|
filename=os.path.join(JOY_HOME, 'thun.log'),
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
)
|
)
|
||||||
_log.info('Starting with JOY_HOME=%s', JOY_HOME)
|
_log.info('Starting with JOY_HOME=%s', JOY_HOME)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -39,73 +39,73 @@ from joy.utils.stack import stack_to_string
|
||||||
cp = RawConfigParser()
|
cp = RawConfigParser()
|
||||||
cp.optionxform = str # Don't mess with uppercase.
|
cp.optionxform = str # Don't mess with uppercase.
|
||||||
with open(os.path.join(args.joy_home, 'thun.config')) as f:
|
with open(os.path.join(args.joy_home, 'thun.config')) as f:
|
||||||
cp.readfp(f)
|
cp.readfp(f)
|
||||||
|
|
||||||
|
|
||||||
GLOBAL_COMMANDS = dict(cp.items('key bindings'))
|
GLOBAL_COMMANDS = dict(cp.items('key bindings'))
|
||||||
|
|
||||||
|
|
||||||
def repo_relative_path(path):
|
def repo_relative_path(path):
|
||||||
return os.path.relpath(
|
return os.path.relpath(
|
||||||
path,
|
path,
|
||||||
os.path.commonprefix((repo.controldir(), path))
|
os.path.commonprefix((repo.controldir(), path))
|
||||||
)
|
)
|
||||||
|
|
||||||
def commands():
|
def commands():
|
||||||
# pylint: disable=unused-variable
|
# pylint: disable=unused-variable
|
||||||
|
|
||||||
def key_bindings(*args):
|
def key_bindings(*args):
|
||||||
commands = [ # These are bound in the TextViewerWidget.
|
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.',
|
'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.',
|
||||||
'F3 - Copy selection to stack.',
|
'F3 - Copy selection to stack.',
|
||||||
'Shift-F3 - Cut selection to stack.',
|
'Shift-F3 - Cut selection to stack.',
|
||||||
'F4 - Paste item on top of stack to insertion cursor.',
|
'F4 - Paste item on top of stack to insertion cursor.',
|
||||||
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
|
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
|
||||||
]
|
]
|
||||||
for key, command in GLOBAL_COMMANDS.items():
|
for key, command in GLOBAL_COMMANDS.items():
|
||||||
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
|
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
|
||||||
print('\n'.join([''] + sorted(commands)))
|
print('\n'.join([''] + sorted(commands)))
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def mouse_bindings(*args):
|
def mouse_bindings(*args):
|
||||||
print(dedent('''
|
print(dedent('''
|
||||||
Mouse button chords (to cancel a chord, click the third mouse button.)
|
Mouse button chords (to cancel a chord, click the third mouse button.)
|
||||||
|
|
||||||
Left - Point, sweep selection
|
Left - Point, sweep selection
|
||||||
Left-Middle - Copy the selection, place text on stack
|
Left-Middle - Copy the selection, place text on stack
|
||||||
Left-Right - Run the selection as Joy code
|
Left-Right - Run the selection as Joy code
|
||||||
|
|
||||||
Middle - Paste selection (bypass stack); click and drag to scroll.
|
Middle - Paste selection (bypass stack); click and drag to scroll.
|
||||||
Middle-Left - Paste from top of stack, preserve
|
Middle-Left - Paste from top of stack, preserve
|
||||||
Middle-Right - Paste from top of stack, pop
|
Middle-Right - Paste from top of stack, pop
|
||||||
|
|
||||||
Right - Execute command word under mouse cursor
|
Right - Execute command word under mouse cursor
|
||||||
Right-Left - Print docs of command word under mouse cursor
|
Right-Left - Print docs of command word under mouse cursor
|
||||||
Right-Middle - Lookup word (kinda useless now)
|
Right-Middle - Lookup word (kinda useless now)
|
||||||
'''))
|
'''))
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def reset_log(*args):
|
def reset_log(*args):
|
||||||
log.delete('0.0', tk.END)
|
log.delete('0.0', tk.END)
|
||||||
print(__doc__)
|
print(__doc__)
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def show_log(*args):
|
def show_log(*args):
|
||||||
log_window.wm_deiconify()
|
log_window.wm_deiconify()
|
||||||
log_window.update()
|
log_window.update()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def grand_reset(s, e, d):
|
def grand_reset(s, e, d):
|
||||||
stack = world.load_stack() or ()
|
stack = world.load_stack() or ()
|
||||||
log.reset()
|
log.reset()
|
||||||
t.reset()
|
t.reset()
|
||||||
return stack, e, d
|
return stack, e, d
|
||||||
|
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
STACK_FN = os.path.join(JOY_HOME, 'stack.pickle')
|
STACK_FN = os.path.join(JOY_HOME, 'stack.pickle')
|
||||||
|
|
@ -125,19 +125,19 @@ FONT = get_font('Iosevka', size=14) # Requires Tk root already set up.
|
||||||
log.init('Log', LOG_FN, repo_relative_path(LOG_FN), repo, FONT)
|
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)
|
t.init('Joy - ' + JOY_HOME, JOY_FN, repo_relative_path(JOY_FN), repo, FONT)
|
||||||
for event, command in GLOBAL_COMMANDS.items():
|
for event, command in GLOBAL_COMMANDS.items():
|
||||||
callback = lambda _, _command=command: world.interpret(_command)
|
callback = lambda _, _command=command: world.interpret(_command)
|
||||||
t.bind(event, callback)
|
t.bind(event, callback)
|
||||||
log.bind(event, callback)
|
log.bind(event, callback)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout, old_stdout = FileFaker(log), sys.stdout
|
sys.stdout, old_stdout = FileFaker(log), sys.stdout
|
||||||
try:
|
try:
|
||||||
t.mainloop()
|
t.mainloop()
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = old_stdout
|
sys.stdout = old_stdout
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -25,187 +25,187 @@ nothing = lambda event: None
|
||||||
|
|
||||||
|
|
||||||
class MouseBindingsMixin(object):
|
class MouseBindingsMixin(object):
|
||||||
"""TextViewerWidget mixin class to provide mouse bindings."""
|
"""TextViewerWidget mixin class to provide mouse bindings."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
#Remember our mouse button state
|
#Remember our mouse button state
|
||||||
self.B1_DOWN = False
|
self.B1_DOWN = False
|
||||||
self.B2_DOWN = False
|
self.B2_DOWN = False
|
||||||
self.B3_DOWN = False
|
self.B3_DOWN = False
|
||||||
|
|
||||||
#Remember our pending action.
|
#Remember our pending action.
|
||||||
self.dothis = nothing
|
self.dothis = nothing
|
||||||
|
|
||||||
#We'll need to remember whether or not we've been moving B2.
|
#We'll need to remember whether or not we've been moving B2.
|
||||||
self.beenMovingB2 = False
|
self.beenMovingB2 = False
|
||||||
|
|
||||||
#Unbind the events we're interested in.
|
#Unbind the events we're interested in.
|
||||||
for sequence in (
|
for sequence in (
|
||||||
"<Button-1>", "<B1-Motion>", "<ButtonRelease-1>",
|
"<Button-1>", "<B1-Motion>", "<ButtonRelease-1>",
|
||||||
"<Button-2>", "<B2-Motion>", "<ButtonRelease-2>",
|
"<Button-2>", "<B2-Motion>", "<ButtonRelease-2>",
|
||||||
"<Button-3>", "<B3-Motion>", "<ButtonRelease-3>",
|
"<Button-3>", "<B3-Motion>", "<ButtonRelease-3>",
|
||||||
"<B1-Leave>", "<B2-Leave>", "<B3-Leave>", "<Any-Leave>", "<Leave>"
|
"<B1-Leave>", "<B2-Leave>", "<B3-Leave>", "<Any-Leave>", "<Leave>"
|
||||||
):
|
):
|
||||||
self.unbind(sequence)
|
self.unbind(sequence)
|
||||||
self.unbind_all(sequence)
|
self.unbind_all(sequence)
|
||||||
|
|
||||||
self.event_delete('<<PasteSelection>>') #I forgot what this was for! :-P D'oh!
|
self.event_delete('<<PasteSelection>>') #I forgot what this was for! :-P D'oh!
|
||||||
|
|
||||||
#Bind our event handlers to their events.
|
#Bind our event handlers to their events.
|
||||||
self.bind("<Button-1>", self.B1d)
|
self.bind("<Button-1>", self.B1d)
|
||||||
self.bind("<B1-Motion>", self.B1m)
|
self.bind("<B1-Motion>", self.B1m)
|
||||||
self.bind("<ButtonRelease-1>", self.B1r)
|
self.bind("<ButtonRelease-1>", self.B1r)
|
||||||
|
|
||||||
self.bind("<Button-2>", self.B2d)
|
self.bind("<Button-2>", self.B2d)
|
||||||
self.bind("<B2-Motion>", self.B2m)
|
self.bind("<B2-Motion>", self.B2m)
|
||||||
self.bind("<ButtonRelease-2>", self.B2r)
|
self.bind("<ButtonRelease-2>", self.B2r)
|
||||||
|
|
||||||
self.bind("<Button-3>", self.B3d)
|
self.bind("<Button-3>", self.B3d)
|
||||||
self.bind("<B3-Motion>", self.B3m)
|
self.bind("<B3-Motion>", self.B3m)
|
||||||
self.bind("<ButtonRelease-3>", self.B3r)
|
self.bind("<ButtonRelease-3>", self.B3r)
|
||||||
|
|
||||||
self.bind("<Any-Leave>", self.leave)
|
self.bind("<Any-Leave>", self.leave)
|
||||||
self.bind("<Motion>", self.scan_command)
|
self.bind("<Motion>", self.scan_command)
|
||||||
|
|
||||||
def B1d(self, event):
|
def B1d(self, event):
|
||||||
'''button one pressed'''
|
'''button one pressed'''
|
||||||
self.B1_DOWN = True
|
self.B1_DOWN = True
|
||||||
|
|
||||||
if self.B2_DOWN:
|
if self.B2_DOWN:
|
||||||
|
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
|
|
||||||
if self.B3_DOWN :
|
if self.B3_DOWN :
|
||||||
self.dothis = self.cancel
|
self.dothis = self.cancel
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#copy TOS to the mouse (instead of system selection.)
|
#copy TOS to the mouse (instead of system selection.)
|
||||||
self.dothis = self.copyto #middle-left-interclick
|
self.dothis = self.copyto #middle-left-interclick
|
||||||
|
|
||||||
elif self.B3_DOWN :
|
elif self.B3_DOWN :
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
self.dothis = self.opendoc #right-left-interclick
|
self.dothis = self.opendoc #right-left-interclick
|
||||||
|
|
||||||
else:
|
else:
|
||||||
##button 1 down, set insertion and begin selection.
|
##button 1 down, set insertion and begin selection.
|
||||||
##Actually, do nothing. Tk Text widget defaults take care of it.
|
##Actually, do nothing. Tk Text widget defaults take care of it.
|
||||||
self.dothis = nothing
|
self.dothis = nothing
|
||||||
return
|
return
|
||||||
|
|
||||||
#Prevent further event handling by returning "break".
|
#Prevent further event handling by returning "break".
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B2d(self, event):
|
def B2d(self, event):
|
||||||
'''button two pressed'''
|
'''button two pressed'''
|
||||||
self.B2_DOWN = 1
|
self.B2_DOWN = 1
|
||||||
|
|
||||||
if self.B1_DOWN :
|
if self.B1_DOWN :
|
||||||
|
|
||||||
if self.B3_DOWN :
|
if self.B3_DOWN :
|
||||||
self.dothis = self.cancel
|
self.dothis = self.cancel
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#left-middle-interclick - copy selection to stack
|
#left-middle-interclick - copy selection to stack
|
||||||
self.dothis = self.copy_selection_to_stack
|
self.dothis = self.copy_selection_to_stack
|
||||||
|
|
||||||
elif self.B3_DOWN :
|
elif self.B3_DOWN :
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
self.dothis = self.lookup #right-middle-interclick - lookup
|
self.dothis = self.lookup #right-middle-interclick - lookup
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#middle-click - paste X selection to mouse pointer
|
#middle-click - paste X selection to mouse pointer
|
||||||
self.set_insertion_point(event)
|
self.set_insertion_point(event)
|
||||||
self.dothis = self.paste_X_selection_to_mouse_pointer
|
self.dothis = self.paste_X_selection_to_mouse_pointer
|
||||||
return
|
return
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B3d(self, event):
|
def B3d(self, event):
|
||||||
'''button three pressed'''
|
'''button three pressed'''
|
||||||
self.B3_DOWN = 1
|
self.B3_DOWN = 1
|
||||||
|
|
||||||
if self.B1_DOWN :
|
if self.B1_DOWN :
|
||||||
|
|
||||||
if self.B2_DOWN :
|
if self.B2_DOWN :
|
||||||
self.dothis = self.cancel
|
self.dothis = self.cancel
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#left-right-interclick - run selection
|
#left-right-interclick - run selection
|
||||||
self.dothis = self.run_selection
|
self.dothis = self.run_selection
|
||||||
|
|
||||||
elif self.B2_DOWN :
|
elif self.B2_DOWN :
|
||||||
#middle-right-interclick - Pop/Cut from TOS to insertion cursor
|
#middle-right-interclick - Pop/Cut from TOS to insertion cursor
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
self.dothis = self.pastecut
|
self.dothis = self.pastecut
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#right-click
|
#right-click
|
||||||
self.CommandFirstDown(event)
|
self.CommandFirstDown(event)
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B1m(self, event):
|
def B1m(self, event):
|
||||||
'''button one moved'''
|
'''button one moved'''
|
||||||
if self.B2_DOWN or self.B3_DOWN:
|
if self.B2_DOWN or self.B3_DOWN:
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B2m(self, event):
|
def B2m(self, event):
|
||||||
'''button two moved'''
|
'''button two moved'''
|
||||||
if self.dothis == self.paste_X_selection_to_mouse_pointer and \
|
if self.dothis == self.paste_X_selection_to_mouse_pointer and \
|
||||||
not (self.B1_DOWN or self.B3_DOWN):
|
not (self.B1_DOWN or self.B3_DOWN):
|
||||||
|
|
||||||
self.beenMovingB2 = True
|
self.beenMovingB2 = True
|
||||||
return
|
return
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B3m(self, event):
|
def B3m(self, event):
|
||||||
'''button three moved'''
|
'''button three moved'''
|
||||||
if self.dothis == self.do_command and \
|
if self.dothis == self.do_command and \
|
||||||
not (self.B1_DOWN or self.B2_DOWN):
|
not (self.B1_DOWN or self.B2_DOWN):
|
||||||
|
|
||||||
self.update_command_word(event)
|
self.update_command_word(event)
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def scan_command(self, event):
|
def scan_command(self, event):
|
||||||
self.update_command_word(event)
|
self.update_command_word(event)
|
||||||
|
|
||||||
def B1r(self, event):
|
def B1r(self, event):
|
||||||
'''button one released'''
|
'''button one released'''
|
||||||
self.B1_DOWN = False
|
self.B1_DOWN = False
|
||||||
|
|
||||||
if not (self.B2_DOWN or self.B3_DOWN):
|
if not (self.B2_DOWN or self.B3_DOWN):
|
||||||
self.dothis(event)
|
self.dothis(event)
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B2r(self, event):
|
def B2r(self, event):
|
||||||
'''button two released'''
|
'''button two released'''
|
||||||
self.B2_DOWN = False
|
self.B2_DOWN = False
|
||||||
|
|
||||||
if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2):
|
if not (self.B1_DOWN or self.B3_DOWN or self.beenMovingB2):
|
||||||
self.dothis(event)
|
self.dothis(event)
|
||||||
|
|
||||||
self.beenMovingB2 = False
|
self.beenMovingB2 = False
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def B3r(self, event):
|
def B3r(self, event):
|
||||||
'''button three released'''
|
'''button three released'''
|
||||||
self.B3_DOWN = False
|
self.B3_DOWN = False
|
||||||
|
|
||||||
if not (self.B1_DOWN or self.B2_DOWN) :
|
if not (self.B1_DOWN or self.B2_DOWN) :
|
||||||
self.dothis(event)
|
self.dothis(event)
|
||||||
|
|
||||||
return "break"
|
return "break"
|
||||||
|
|
||||||
def InsertFirstDown(self, event):
|
def InsertFirstDown(self, event):
|
||||||
self.focus()
|
self.focus()
|
||||||
self.dothis = nothing
|
self.dothis = nothing
|
||||||
self.set_insertion_point(event)
|
self.set_insertion_point(event)
|
||||||
|
|
||||||
def CommandFirstDown(self, event):
|
def CommandFirstDown(self, event):
|
||||||
self.dothis = self.do_command
|
self.dothis = self.do_command
|
||||||
self.update_command_word(event)
|
self.update_command_word(event)
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,12 @@ A Graphical User Interface for a dialect of Joy in Python.
|
||||||
|
|
||||||
The GUI
|
The GUI
|
||||||
|
|
||||||
History
|
History
|
||||||
Structure
|
Structure
|
||||||
Commands
|
Commands
|
||||||
Mouse Chords
|
Mouse Chords
|
||||||
Keyboard
|
Keyboard
|
||||||
Output from Joy
|
Output from Joy
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
@ -40,11 +40,11 @@ standard_library.install_aliases()
|
||||||
from builtins import str, map, object
|
from builtins import str, map, object
|
||||||
from past.builtins import basestring
|
from past.builtins import basestring
|
||||||
try:
|
try:
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter.font import families, Font
|
from tkinter.font import families, Font
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import Tkinter as tk
|
import Tkinter as tk
|
||||||
from tkFont import families, Font
|
from tkFont import families, Font
|
||||||
|
|
||||||
from re import compile as regular_expression
|
from re import compile as regular_expression
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
@ -58,17 +58,17 @@ from .world import World
|
||||||
|
|
||||||
|
|
||||||
def make_gui(dictionary):
|
def make_gui(dictionary):
|
||||||
t = TextViewerWidget(World(dictionary=dictionary))
|
t = TextViewerWidget(World(dictionary=dictionary))
|
||||||
t['font'] = get_font()
|
t['font'] = get_font()
|
||||||
t._root().title('Joy')
|
t._root().title('Joy')
|
||||||
t.pack(expand=True, fill=tk.BOTH)
|
t.pack(expand=True, fill=tk.BOTH)
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
def get_font(family='EB Garamond', size=14):
|
def get_font(family='EB Garamond', size=14):
|
||||||
if family not in families():
|
if family not in families():
|
||||||
family = 'Times'
|
family = 'Times'
|
||||||
return Font(family=family, size=size)
|
return Font(family=family, size=size)
|
||||||
|
|
||||||
|
|
||||||
#: Define mapping between Tkinter events and functions or methods. The
|
#: Define mapping between Tkinter events and functions or methods. The
|
||||||
|
|
@ -77,85 +77,85 @@ def get_font(family='EB Garamond', size=14):
|
||||||
#: must return the actual callable to which to bind the event sequence.
|
#: must return the actual callable to which to bind the event sequence.
|
||||||
TEXT_BINDINGS = {
|
TEXT_BINDINGS = {
|
||||||
|
|
||||||
#I want to ensure that these keyboard shortcuts work.
|
#I want to ensure that these keyboard shortcuts work.
|
||||||
'<Control-Return>': lambda tv: tv._control_enter,
|
'<Control-Return>': lambda tv: tv._control_enter,
|
||||||
'<Control-v>': lambda tv: tv._paste,
|
'<Control-v>': lambda tv: tv._paste,
|
||||||
'<Control-V>': lambda tv: tv._paste,
|
'<Control-V>': lambda tv: tv._paste,
|
||||||
'<F3>': lambda tv: tv.copy_selection_to_stack,
|
'<F3>': lambda tv: tv.copy_selection_to_stack,
|
||||||
'<F4>': lambda tv: tv.copyto,
|
'<F4>': lambda tv: tv.copyto,
|
||||||
'<Shift-F3>': lambda tv: tv.cut,
|
'<Shift-F3>': lambda tv: tv.cut,
|
||||||
'<Shift-F4>': lambda tv: tv.pastecut,
|
'<Shift-F4>': lambda tv: tv.pastecut,
|
||||||
'<Shift-Insert>': lambda tv: tv._paste,
|
'<Shift-Insert>': lambda tv: tv._paste,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class SavingMixin(object):
|
class SavingMixin(object):
|
||||||
|
|
||||||
def __init__(self, saver=None, filename=None, save_delay=2000):
|
def __init__(self, saver=None, filename=None, save_delay=2000):
|
||||||
self.saver = self._saver if saver is None else saver
|
self.saver = self._saver if saver is None else saver
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self._save_delay = save_delay
|
self._save_delay = save_delay
|
||||||
self.tk.call(self._w, 'edit', 'modified', 0)
|
self.tk.call(self._w, 'edit', 'modified', 0)
|
||||||
self.bind('<<Modified>>', self._beenModified)
|
self.bind('<<Modified>>', self._beenModified)
|
||||||
self._resetting_modified_flag = False
|
self._resetting_modified_flag = False
|
||||||
self._save = None
|
self._save = None
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
'''
|
'''
|
||||||
Call _saveFunc() after a certain amount of idle time.
|
Call _saveFunc() after a certain amount of idle time.
|
||||||
|
|
||||||
Called by _beenModified().
|
Called by _beenModified().
|
||||||
'''
|
'''
|
||||||
self._cancelSave()
|
self._cancelSave()
|
||||||
if self.saver:
|
if self.saver:
|
||||||
self._saveAfter(self._save_delay)
|
self._saveAfter(self._save_delay)
|
||||||
|
|
||||||
def _saveAfter(self, delay):
|
def _saveAfter(self, delay):
|
||||||
'''
|
'''
|
||||||
Trigger a cancel-able call to _saveFunc() after delay milliseconds.
|
Trigger a cancel-able call to _saveFunc() after delay milliseconds.
|
||||||
'''
|
'''
|
||||||
self._save = self.after(delay, self._saveFunc)
|
self._save = self.after(delay, self._saveFunc)
|
||||||
|
|
||||||
def _saveFunc(self):
|
def _saveFunc(self):
|
||||||
self._save = None
|
self._save = None
|
||||||
self.saver(self._get_contents())
|
self.saver(self._get_contents())
|
||||||
|
|
||||||
def _saver(self, text):
|
def _saver(self, text):
|
||||||
if not self.filename:
|
if not self.filename:
|
||||||
return
|
return
|
||||||
with open(self.filename, 'wb') as f:
|
with open(self.filename, 'wb') as f:
|
||||||
os.chmod(self.filename, 0o600)
|
os.chmod(self.filename, 0o600)
|
||||||
f.write(text.encode('UTF_8'))
|
f.write(text.encode('UTF_8'))
|
||||||
f.flush()
|
f.flush()
|
||||||
os.fsync(f.fileno())
|
os.fsync(f.fileno())
|
||||||
if hasattr(self, 'repo'):
|
if hasattr(self, 'repo'):
|
||||||
self.repo.stage([self.repo_relative_filename])
|
self.repo.stage([self.repo_relative_filename])
|
||||||
self.world.save()
|
self.world.save()
|
||||||
|
|
||||||
def _cancelSave(self):
|
def _cancelSave(self):
|
||||||
if self._save is not None:
|
if self._save is not None:
|
||||||
self.after_cancel(self._save)
|
self.after_cancel(self._save)
|
||||||
self._save = None
|
self._save = None
|
||||||
|
|
||||||
def _get_contents(self):
|
def _get_contents(self):
|
||||||
self['state'] = 'disabled'
|
self['state'] = 'disabled'
|
||||||
try:
|
try:
|
||||||
return self.get('0.0', 'end')[:-1]
|
return self.get('0.0', 'end')[:-1]
|
||||||
finally:
|
finally:
|
||||||
self['state'] = 'normal'
|
self['state'] = 'normal'
|
||||||
|
|
||||||
def _beenModified(self, event):
|
def _beenModified(self, event):
|
||||||
if self._resetting_modified_flag:
|
if self._resetting_modified_flag:
|
||||||
return
|
return
|
||||||
self._clearModifiedFlag()
|
self._clearModifiedFlag()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def _clearModifiedFlag(self):
|
def _clearModifiedFlag(self):
|
||||||
self._resetting_modified_flag = True
|
self._resetting_modified_flag = True
|
||||||
try:
|
try:
|
||||||
self.tk.call(self._w, 'edit', 'modified', 0)
|
self.tk.call(self._w, 'edit', 'modified', 0)
|
||||||
finally:
|
finally:
|
||||||
self._resetting_modified_flag = False
|
self._resetting_modified_flag = False
|
||||||
|
|
||||||
## tags = self._saveTags()
|
## tags = self._saveTags()
|
||||||
## chunks = self.DUMP()
|
## chunks = self.DUMP()
|
||||||
|
|
@ -163,309 +163,309 @@ class SavingMixin(object):
|
||||||
|
|
||||||
|
|
||||||
class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
|
class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
|
||||||
"""
|
"""
|
||||||
This class is a Tkinter Text with special mousebindings to make
|
This class is a Tkinter Text with special mousebindings to make
|
||||||
it act as a Xerblin Text Viewer.
|
it act as a Xerblin Text Viewer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#This is a regular expression for finding commands in the text.
|
#This is a regular expression for finding commands in the text.
|
||||||
command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+')
|
command_re = regular_expression(r'[-a-zA-Z0-9_\\~/.:!@#$%&*?=+<>]+')
|
||||||
|
|
||||||
#These are the config tags for command text when it's highlighted.
|
#These are the config tags for command text when it's highlighted.
|
||||||
command_tags = dict(
|
command_tags = dict(
|
||||||
#underline = 1,
|
#underline = 1,
|
||||||
#bgstipple = "gray50",
|
#bgstipple = "gray50",
|
||||||
borderwidth = 2,
|
borderwidth = 2,
|
||||||
relief=tk.RIDGE,
|
relief=tk.RIDGE,
|
||||||
foreground = "green"
|
foreground = "green"
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, world, master=None, **kw):
|
def __init__(self, world, master=None, **kw):
|
||||||
|
|
||||||
self.world = world
|
self.world = world
|
||||||
if self.world.text_widget is None:
|
if self.world.text_widget is None:
|
||||||
self.world.text_widget = self
|
self.world.text_widget = self
|
||||||
|
|
||||||
#Turn on undo, but don't override a passed-in setting.
|
#Turn on undo, but don't override a passed-in setting.
|
||||||
kw.setdefault('undo', True)
|
kw.setdefault('undo', True)
|
||||||
|
|
||||||
# kw.setdefault('bg', 'white')
|
# kw.setdefault('bg', 'white')
|
||||||
kw.setdefault('wrap', 'word')
|
kw.setdefault('wrap', 'word')
|
||||||
kw.setdefault('font', 'arial 12')
|
kw.setdefault('font', 'arial 12')
|
||||||
|
|
||||||
text_bindings = kw.pop('text_bindings', TEXT_BINDINGS)
|
text_bindings = kw.pop('text_bindings', TEXT_BINDINGS)
|
||||||
|
|
||||||
#Create ourselves as a Tkinter Text
|
#Create ourselves as a Tkinter Text
|
||||||
tk.Text.__init__(self, master, **kw)
|
tk.Text.__init__(self, master, **kw)
|
||||||
|
|
||||||
#Initialize our mouse mixin.
|
#Initialize our mouse mixin.
|
||||||
MouseBindingsMixin.__init__(self)
|
MouseBindingsMixin.__init__(self)
|
||||||
|
|
||||||
#Initialize our saver mixin.
|
#Initialize our saver mixin.
|
||||||
SavingMixin.__init__(self)
|
SavingMixin.__init__(self)
|
||||||
|
|
||||||
#Add tag config for command highlighting.
|
#Add tag config for command highlighting.
|
||||||
self.tag_config('command', **self.command_tags)
|
self.tag_config('command', **self.command_tags)
|
||||||
self.tag_config('bzzt', foreground = "orange")
|
self.tag_config('bzzt', foreground = "orange")
|
||||||
self.tag_config('huh', foreground = "grey")
|
self.tag_config('huh', foreground = "grey")
|
||||||
self.tag_config('number', foreground = "blue")
|
self.tag_config('number', foreground = "blue")
|
||||||
|
|
||||||
#Create us a command instance variable
|
#Create us a command instance variable
|
||||||
self.command = ''
|
self.command = ''
|
||||||
|
|
||||||
#Activate event bindings. Modify text_bindings in your config
|
#Activate event bindings. Modify text_bindings in your config
|
||||||
#file to affect the key bindings and whatnot here.
|
#file to affect the key bindings and whatnot here.
|
||||||
for event_sequence, callback_finder in text_bindings.items():
|
for event_sequence, callback_finder in text_bindings.items():
|
||||||
callback = callback_finder(self)
|
callback = callback_finder(self)
|
||||||
self.bind(event_sequence, callback)
|
self.bind(event_sequence, callback)
|
||||||
|
|
||||||
## T.protocol("WM_DELETE_WINDOW", self.on_close)
|
## T.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||||
|
|
||||||
def find_command_in_line(self, line, index):
|
def find_command_in_line(self, line, index):
|
||||||
'''
|
'''
|
||||||
Return the command at index in line and its begin and end indices.
|
Return the command at index in line and its begin and end indices.
|
||||||
find_command_in_line(line, index) => command, begin, end
|
find_command_in_line(line, index) => command, begin, end
|
||||||
'''
|
'''
|
||||||
for match in self.command_re.finditer(line):
|
for match in self.command_re.finditer(line):
|
||||||
b, e = match.span()
|
b, e = match.span()
|
||||||
if b <= index <= e:
|
if b <= index <= e:
|
||||||
return match.group(), b, e
|
return match.group(), b, e
|
||||||
|
|
||||||
def paste_X_selection_to_mouse_pointer(self, event):
|
def paste_X_selection_to_mouse_pointer(self, event):
|
||||||
'''Paste the X selection to the mouse pointer.'''
|
'''Paste the X selection to the mouse pointer.'''
|
||||||
try:
|
try:
|
||||||
text = self.selection_get()
|
text = self.selection_get()
|
||||||
except tk.TclError:
|
except tk.TclError:
|
||||||
return 'break'
|
return 'break'
|
||||||
self.insert_it(text)
|
self.insert_it(text)
|
||||||
|
|
||||||
def update_command_word(self, event):
|
def update_command_word(self, event):
|
||||||
'''Highlight the command under the mouse.'''
|
'''Highlight the command under the mouse.'''
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
self.command = ''
|
self.command = ''
|
||||||
index = '@%d,%d' % (event.x, event.y)
|
index = '@%d,%d' % (event.x, event.y)
|
||||||
linestart = self.index(index + 'linestart')
|
linestart = self.index(index + 'linestart')
|
||||||
lineend = self.index(index + 'lineend')
|
lineend = self.index(index + 'lineend')
|
||||||
line = self.get(linestart, lineend)
|
line = self.get(linestart, lineend)
|
||||||
row, offset = self._get_index(index)
|
row, offset = self._get_index(index)
|
||||||
|
|
||||||
if offset >= len(line) or line[offset].isspace():
|
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
|
# The mouse is off the end of the line or on a space so there's no
|
||||||
# command, we're done.
|
# command, we're done.
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = self.find_command_in_line(line, offset)
|
cmd = self.find_command_in_line(line, offset)
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd, b, e = cmd
|
cmd, b, e = cmd
|
||||||
if is_numerical(cmd):
|
if is_numerical(cmd):
|
||||||
extra_tags = 'number',
|
extra_tags = 'number',
|
||||||
elif self.world.has(cmd):
|
elif self.world.has(cmd):
|
||||||
check = self.world.check(cmd)
|
check = self.world.check(cmd)
|
||||||
if check: extra_tags = ()
|
if check: extra_tags = ()
|
||||||
elif check is None: extra_tags = 'huh',
|
elif check is None: extra_tags = 'huh',
|
||||||
else: extra_tags = 'bzzt',
|
else: extra_tags = 'bzzt',
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
self.command = cmd
|
self.command = cmd
|
||||||
self.highlight_command(
|
self.highlight_command(
|
||||||
'%d.%d' % (row, b),
|
'%d.%d' % (row, b),
|
||||||
'%d.%d' % (row, e),
|
'%d.%d' % (row, e),
|
||||||
*extra_tags)
|
*extra_tags)
|
||||||
|
|
||||||
def highlight_command(self, from_, to, *extra_tags):
|
def highlight_command(self, from_, to, *extra_tags):
|
||||||
'''Apply command style from from_ to to.'''
|
'''Apply command style from from_ to to.'''
|
||||||
cmdstart = self.index(from_)
|
cmdstart = self.index(from_)
|
||||||
cmdend = self.index(to)
|
cmdend = self.index(to)
|
||||||
self.tag_add('command', cmdstart, cmdend)
|
self.tag_add('command', cmdstart, cmdend)
|
||||||
for tag in extra_tags:
|
for tag in extra_tags:
|
||||||
self.tag_add(tag, cmdstart, cmdend)
|
self.tag_add(tag, cmdstart, cmdend)
|
||||||
|
|
||||||
def do_command(self, event):
|
def do_command(self, event):
|
||||||
'''Do the currently highlighted command.'''
|
'''Do the currently highlighted command.'''
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
if self.command:
|
if self.command:
|
||||||
self.run_command(self.command)
|
self.run_command(self.command)
|
||||||
|
|
||||||
def _control_enter(self, event):
|
def _control_enter(self, event):
|
||||||
select_indices = self.tag_ranges(tk.SEL)
|
select_indices = self.tag_ranges(tk.SEL)
|
||||||
if select_indices:
|
if select_indices:
|
||||||
command = self.get(select_indices[0], select_indices[1])
|
command = self.get(select_indices[0], select_indices[1])
|
||||||
else:
|
else:
|
||||||
linestart = self.index(tk.INSERT + ' linestart')
|
linestart = self.index(tk.INSERT + ' linestart')
|
||||||
lineend = self.index(tk.INSERT + ' lineend')
|
lineend = self.index(tk.INSERT + ' lineend')
|
||||||
command = self.get(linestart, lineend)
|
command = self.get(linestart, lineend)
|
||||||
if command and not command.isspace():
|
if command and not command.isspace():
|
||||||
self.run_command(command)
|
self.run_command(command)
|
||||||
return 'break'
|
return 'break'
|
||||||
|
|
||||||
def run_command(self, command):
|
def run_command(self, command):
|
||||||
'''Given a string run it on the stack, report errors.'''
|
'''Given a string run it on the stack, report errors.'''
|
||||||
try:
|
try:
|
||||||
self.world.interpret(command)
|
self.world.interpret(command)
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
self.popupTB(format_exc().rstrip())
|
self.popupTB(format_exc().rstrip())
|
||||||
|
|
||||||
def unhighlight_command(self):
|
def unhighlight_command(self):
|
||||||
'''Remove any command highlighting.'''
|
'''Remove any command highlighting.'''
|
||||||
self.tag_remove('number', 1.0, tk.END)
|
self.tag_remove('number', 1.0, tk.END)
|
||||||
self.tag_remove('huh', 1.0, tk.END)
|
self.tag_remove('huh', 1.0, tk.END)
|
||||||
self.tag_remove('bzzt', 1.0, tk.END)
|
self.tag_remove('bzzt', 1.0, tk.END)
|
||||||
self.tag_remove('command', 1.0, tk.END)
|
self.tag_remove('command', 1.0, tk.END)
|
||||||
|
|
||||||
def set_insertion_point(self, event):
|
def set_insertion_point(self, event):
|
||||||
'''Set the insertion cursor to the current mouse location.'''
|
'''Set the insertion cursor to the current mouse location.'''
|
||||||
self.focus()
|
self.focus()
|
||||||
self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y))
|
self.mark_set(tk.INSERT, '@%d,%d' % (event.x, event.y))
|
||||||
|
|
||||||
def copy_selection_to_stack(self, event):
|
def copy_selection_to_stack(self, event):
|
||||||
'''Copy selection to stack.'''
|
'''Copy selection to stack.'''
|
||||||
select_indices = self.tag_ranges(tk.SEL)
|
select_indices = self.tag_ranges(tk.SEL)
|
||||||
if select_indices:
|
if select_indices:
|
||||||
s = self.get(select_indices[0], select_indices[1])
|
s = self.get(select_indices[0], select_indices[1])
|
||||||
self.world.push(s)
|
self.world.push(s)
|
||||||
|
|
||||||
def cut(self, event):
|
def cut(self, event):
|
||||||
'''Cut selection to stack.'''
|
'''Cut selection to stack.'''
|
||||||
self.copy_selection_to_stack(event)
|
self.copy_selection_to_stack(event)
|
||||||
# Let the pre-existing machinery take care of cutting the selection.
|
# Let the pre-existing machinery take care of cutting the selection.
|
||||||
self.event_generate("<<Cut>>")
|
self.event_generate("<<Cut>>")
|
||||||
|
|
||||||
def copyto(self, event):
|
def copyto(self, event):
|
||||||
'''Actually "paste" from TOS'''
|
'''Actually "paste" from TOS'''
|
||||||
s = self.world.peek()
|
s = self.world.peek()
|
||||||
if s is not None:
|
if s is not None:
|
||||||
self.insert_it(s)
|
self.insert_it(s)
|
||||||
|
|
||||||
def insert_it(self, s):
|
def insert_it(self, s):
|
||||||
if not isinstance(s, basestring):
|
if not isinstance(s, basestring):
|
||||||
s = stack_to_string(s)
|
s = stack_to_string(s)
|
||||||
|
|
||||||
# When pasting from the mouse we have to remove the current selection
|
# When pasting from the mouse we have to remove the current selection
|
||||||
# to prevent destroying it by the paste operation.
|
# to prevent destroying it by the paste operation.
|
||||||
select_indices = self.tag_ranges(tk.SEL)
|
select_indices = self.tag_ranges(tk.SEL)
|
||||||
if select_indices:
|
if select_indices:
|
||||||
# Set two marks to remember the selection.
|
# Set two marks to remember the selection.
|
||||||
self.mark_set('_sel_start', select_indices[0])
|
self.mark_set('_sel_start', select_indices[0])
|
||||||
self.mark_set('_sel_end', select_indices[1])
|
self.mark_set('_sel_end', select_indices[1])
|
||||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||||
|
|
||||||
self.insert(tk.INSERT, s)
|
self.insert(tk.INSERT, s)
|
||||||
|
|
||||||
if select_indices:
|
if select_indices:
|
||||||
self.tag_add(tk.SEL, '_sel_start', '_sel_end')
|
self.tag_add(tk.SEL, '_sel_start', '_sel_end')
|
||||||
self.mark_unset('_sel_start')
|
self.mark_unset('_sel_start')
|
||||||
self.mark_unset('_sel_end')
|
self.mark_unset('_sel_end')
|
||||||
|
|
||||||
def run_selection(self, event):
|
def run_selection(self, event):
|
||||||
'''Run the current selection if any on the stack.'''
|
'''Run the current selection if any on the stack.'''
|
||||||
select_indices = self.tag_ranges(tk.SEL)
|
select_indices = self.tag_ranges(tk.SEL)
|
||||||
if select_indices:
|
if select_indices:
|
||||||
selection = self.get(select_indices[0], select_indices[1])
|
selection = self.get(select_indices[0], select_indices[1])
|
||||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||||
self.run_command(selection)
|
self.run_command(selection)
|
||||||
|
|
||||||
def pastecut(self, event):
|
def pastecut(self, event):
|
||||||
'''Cut the TOS item to the mouse.'''
|
'''Cut the TOS item to the mouse.'''
|
||||||
self.copyto(event)
|
self.copyto(event)
|
||||||
self.world.pop()
|
self.world.pop()
|
||||||
|
|
||||||
def opendoc(self, event):
|
def opendoc(self, event):
|
||||||
'''OpenDoc the current command.'''
|
'''OpenDoc the current command.'''
|
||||||
if self.command:
|
if self.command:
|
||||||
self.world.do_opendoc(self.command)
|
self.world.do_opendoc(self.command)
|
||||||
|
|
||||||
def lookup(self, event):
|
def lookup(self, event):
|
||||||
'''Look up the current command.'''
|
'''Look up the current command.'''
|
||||||
if self.command:
|
if self.command:
|
||||||
self.world.do_lookup(self.command)
|
self.world.do_lookup(self.command)
|
||||||
|
|
||||||
def cancel(self, event):
|
def cancel(self, event):
|
||||||
'''Cancel whatever we're doing.'''
|
'''Cancel whatever we're doing.'''
|
||||||
self.leave(None)
|
self.leave(None)
|
||||||
self.tag_remove(tk.SEL, 1.0, tk.END)
|
self.tag_remove(tk.SEL, 1.0, tk.END)
|
||||||
self._sel_anchor = '0.0'
|
self._sel_anchor = '0.0'
|
||||||
self.mark_unset(tk.INSERT)
|
self.mark_unset(tk.INSERT)
|
||||||
|
|
||||||
def leave(self, event):
|
def leave(self, event):
|
||||||
'''Called when mouse leaves the Text window.'''
|
'''Called when mouse leaves the Text window.'''
|
||||||
self.unhighlight_command()
|
self.unhighlight_command()
|
||||||
self.command = ''
|
self.command = ''
|
||||||
|
|
||||||
def _get_index(self, index):
|
def _get_index(self, index):
|
||||||
'''Get the index in (int, int) form of index.'''
|
'''Get the index in (int, int) form of index.'''
|
||||||
return tuple(map(int, self.index(index).split('.')))
|
return tuple(map(int, self.index(index).split('.')))
|
||||||
|
|
||||||
def _paste(self, event):
|
def _paste(self, event):
|
||||||
'''Paste the system selection to the current selection, replacing it.'''
|
'''Paste the system selection to the current selection, replacing it.'''
|
||||||
|
|
||||||
# If we're "key" pasting, we have to move the insertion point
|
# If we're "key" pasting, we have to move the insertion point
|
||||||
# to the selection so the pasted text gets inserted at the
|
# to the selection so the pasted text gets inserted at the
|
||||||
# location of the deleted selection.
|
# location of the deleted selection.
|
||||||
|
|
||||||
select_indices = self.tag_ranges(tk.SEL)
|
select_indices = self.tag_ranges(tk.SEL)
|
||||||
if select_indices:
|
if select_indices:
|
||||||
# Mark the location of the current insertion cursor
|
# Mark the location of the current insertion cursor
|
||||||
self.mark_set('tmark', tk.INSERT)
|
self.mark_set('tmark', tk.INSERT)
|
||||||
# Put the insertion cursor at the selection
|
# Put the insertion cursor at the selection
|
||||||
self.mark_set(tk.INSERT, select_indices[1])
|
self.mark_set(tk.INSERT, select_indices[1])
|
||||||
|
|
||||||
# Paste to the current selection, or if none, to the insertion cursor.
|
# Paste to the current selection, or if none, to the insertion cursor.
|
||||||
self.event_generate("<<Paste>>")
|
self.event_generate("<<Paste>>")
|
||||||
|
|
||||||
# If we mess with the insertion cursor above, fix it now.
|
# If we mess with the insertion cursor above, fix it now.
|
||||||
if select_indices:
|
if select_indices:
|
||||||
# Put the insertion cursor back where it was.
|
# Put the insertion cursor back where it was.
|
||||||
self.mark_set(tk.INSERT, 'tmark')
|
self.mark_set(tk.INSERT, 'tmark')
|
||||||
# And get rid of our unneeded mark.
|
# And get rid of our unneeded mark.
|
||||||
self.mark_unset('tmark')
|
self.mark_unset('tmark')
|
||||||
|
|
||||||
return 'break'
|
return 'break'
|
||||||
|
|
||||||
def init(self, title, filename, repo_relative_filename, repo, font):
|
def init(self, title, filename, repo_relative_filename, repo, font):
|
||||||
self.winfo_toplevel().title(title)
|
self.winfo_toplevel().title(title)
|
||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
with open(filename) as f:
|
with open(filename) as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
self.insert(tk.END, data)
|
self.insert(tk.END, data)
|
||||||
# Prevent this from triggering a git commit.
|
# Prevent this from triggering a git commit.
|
||||||
self.update()
|
self.update()
|
||||||
self._cancelSave()
|
self._cancelSave()
|
||||||
self.pack(expand=True, fill=tk.BOTH)
|
self.pack(expand=True, fill=tk.BOTH)
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.repo_relative_filename = repo_relative_filename
|
self.repo_relative_filename = repo_relative_filename
|
||||||
self.repo = repo
|
self.repo = repo
|
||||||
self['font'] = font # See below.
|
self['font'] = font # See below.
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
with open(self.filename) as f:
|
with open(self.filename) as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
if data:
|
if data:
|
||||||
self.delete('0.0', tk.END)
|
self.delete('0.0', tk.END)
|
||||||
self.insert(tk.END, data)
|
self.insert(tk.END, data)
|
||||||
|
|
||||||
def popupTB(self, tb):
|
def popupTB(self, tb):
|
||||||
top = tk.Toplevel()
|
top = tk.Toplevel()
|
||||||
T = TextViewerWidget(
|
T = TextViewerWidget(
|
||||||
self.world,
|
self.world,
|
||||||
top,
|
top,
|
||||||
width=max(len(s) for s in tb.splitlines()) + 3,
|
width=max(len(s) for s in tb.splitlines()) + 3,
|
||||||
)
|
)
|
||||||
|
|
||||||
T['background'] = 'darkgrey'
|
T['background'] = 'darkgrey'
|
||||||
T['foreground'] = 'darkblue'
|
T['foreground'] = 'darkblue'
|
||||||
T.tag_config('err', foreground='yellow')
|
T.tag_config('err', foreground='yellow')
|
||||||
|
|
||||||
T.insert(tk.END, tb)
|
T.insert(tk.END, tb)
|
||||||
last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0'
|
last_line = str(int(T.index(tk.END).split('.')[0]) - 1) + '.0'
|
||||||
T.tag_add('err', last_line, tk.END)
|
T.tag_add('err', last_line, tk.END)
|
||||||
T['state'] = tk.DISABLED
|
T['state'] = tk.DISABLED
|
||||||
|
|
||||||
top.title(T.get(last_line, tk.END).strip())
|
top.title(T.get(last_line, tk.END).strip())
|
||||||
|
|
||||||
T.pack(expand=1, fill=tk.BOTH)
|
T.pack(expand=1, fill=tk.BOTH)
|
||||||
T.see(tk.END)
|
T.see(tk.END)
|
||||||
|
|
|
||||||
110
joy/gui/utils.py
110
joy/gui/utils.py
|
|
@ -13,85 +13,85 @@ DEFAULT_JOY_HOME = expanduser(join('~', '.joypy'))
|
||||||
|
|
||||||
|
|
||||||
def is_numerical(s):
|
def is_numerical(s):
|
||||||
try:
|
try:
|
||||||
float(s)
|
float(s)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def home_dir(path):
|
def home_dir(path):
|
||||||
'''Return the absolute path of an existing directory.'''
|
'''Return the absolute path of an existing directory.'''
|
||||||
|
|
||||||
fullpath = expanduser(path) if path.startswith('~') else abspath(path)
|
fullpath = expanduser(path) if path.startswith('~') else abspath(path)
|
||||||
|
|
||||||
if not exists(fullpath):
|
if not exists(fullpath):
|
||||||
if path == DEFAULT_JOY_HOME:
|
if path == DEFAULT_JOY_HOME:
|
||||||
print('Creating JOY_HOME', repr(fullpath))
|
print('Creating JOY_HOME', repr(fullpath))
|
||||||
mkdir(fullpath, 0o700)
|
mkdir(fullpath, 0o700)
|
||||||
else:
|
else:
|
||||||
print(repr(fullpath), "doesn't exist.", file=sys.stderr)
|
print(repr(fullpath), "doesn't exist.", file=sys.stderr)
|
||||||
raise ValueError(path)
|
raise ValueError(path)
|
||||||
|
|
||||||
return fullpath
|
return fullpath
|
||||||
|
|
||||||
|
|
||||||
def init_home(fullpath):
|
def init_home(fullpath):
|
||||||
'''
|
'''
|
||||||
Open or create the Repo.
|
Open or create the Repo.
|
||||||
If there are contents in the dir but it's not a git repo, quit.
|
If there are contents in the dir but it's not a git repo, quit.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
repo = Repo(fullpath)
|
repo = Repo(fullpath)
|
||||||
except NotGitRepository:
|
except NotGitRepository:
|
||||||
print(repr(fullpath), "no repository", file=sys.stderr)
|
print(repr(fullpath), "no repository", file=sys.stderr)
|
||||||
|
|
||||||
if listdir(fullpath):
|
if listdir(fullpath):
|
||||||
print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr)
|
print(repr(fullpath), "has contents\nQUIT.", file=sys.stderr)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
print('Initializing repository in', fullpath)
|
print('Initializing repository in', fullpath)
|
||||||
repo = init_repo(fullpath)
|
repo = init_repo(fullpath)
|
||||||
|
|
||||||
print('Using repository in', fullpath)
|
print('Using repository in', fullpath)
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
|
||||||
def init_repo(repo_dir):
|
def init_repo(repo_dir):
|
||||||
'''
|
'''
|
||||||
Create a repo, load the initial content, and make the first commit.
|
Create a repo, load the initial content, and make the first commit.
|
||||||
Return the Repo object.
|
Return the Repo object.
|
||||||
'''
|
'''
|
||||||
repo = Repo.init(repo_dir)
|
repo = Repo.init(repo_dir)
|
||||||
import joy.gui.init_joy_home
|
import joy.gui.init_joy_home
|
||||||
joy.gui.init_joy_home.initialize(repo_dir)
|
joy.gui.init_joy_home.initialize(repo_dir)
|
||||||
repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))])
|
repo.stage([fn for fn in listdir(repo_dir) if isfile(join(repo_dir, fn))])
|
||||||
repo.do_commit('Initial commit.', committer=COMMITTER)
|
repo.do_commit('Initial commit.', committer=COMMITTER)
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
|
||||||
argparser = argparse.ArgumentParser(
|
argparser = argparse.ArgumentParser(
|
||||||
description='Experimental Brutalist UI for Joy.',
|
description='Experimental Brutalist UI for Joy.',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
argparser.add_argument(
|
argparser.add_argument(
|
||||||
'-j', '--joy-home',
|
'-j', '--joy-home',
|
||||||
help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME,
|
help='Use a directory other than %s as JOY_HOME' % DEFAULT_JOY_HOME,
|
||||||
default=DEFAULT_JOY_HOME,
|
default=DEFAULT_JOY_HOME,
|
||||||
dest='joy_home',
|
dest='joy_home',
|
||||||
type=home_dir,
|
type=home_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FileFaker(object):
|
class FileFaker(object):
|
||||||
|
|
||||||
def __init__(self, T):
|
def __init__(self, T):
|
||||||
self.T = T
|
self.T = T
|
||||||
|
|
||||||
def write(self, text):
|
def write(self, text):
|
||||||
self.T.insert('end', text)
|
self.T.insert('end', text)
|
||||||
self.T.see('end')
|
self.T.see('end')
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
194
joy/gui/world.py
194
joy/gui/world.py
|
|
@ -35,118 +35,118 @@ from .utils import is_numerical
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, stack=(), dictionary=None, text_widget=None):
|
def __init__(self, stack=(), dictionary=None, text_widget=None):
|
||||||
self.stack = stack
|
self.stack = stack
|
||||||
self.dictionary = dictionary or {}
|
self.dictionary = dictionary or {}
|
||||||
self.text_widget = text_widget
|
self.text_widget = text_widget
|
||||||
self.check_cache = {}
|
self.check_cache = {}
|
||||||
|
|
||||||
def check(self, name):
|
def check(self, name):
|
||||||
try:
|
try:
|
||||||
res = self.check_cache[name]
|
res = self.check_cache[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
res = self.check_cache[name] = type_check(name, self.stack)
|
res = self.check_cache[name] = type_check(name, self.stack)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def do_lookup(self, name):
|
def do_lookup(self, name):
|
||||||
if name in self.dictionary:
|
if name in self.dictionary:
|
||||||
self.stack = (Symbol(name), ()), self.stack
|
self.stack = (Symbol(name), ()), self.stack
|
||||||
self.print_stack()
|
self.print_stack()
|
||||||
self.check_cache.clear()
|
self.check_cache.clear()
|
||||||
else:
|
else:
|
||||||
assert is_numerical(name)
|
assert is_numerical(name)
|
||||||
self.interpret(name)
|
self.interpret(name)
|
||||||
|
|
||||||
def do_opendoc(self, name):
|
def do_opendoc(self, name):
|
||||||
if is_numerical(name):
|
if is_numerical(name):
|
||||||
print('The number', name)
|
print('The number', name)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
word = self.dictionary[name]
|
word = self.dictionary[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print(repr(name), '???')
|
print(repr(name), '???')
|
||||||
else:
|
else:
|
||||||
print(getdoc(word))
|
print(getdoc(word))
|
||||||
self.print_stack()
|
self.print_stack()
|
||||||
|
|
||||||
def pop(self):
|
def pop(self):
|
||||||
if self.stack:
|
if self.stack:
|
||||||
self.stack = self.stack[1]
|
self.stack = self.stack[1]
|
||||||
self.print_stack()
|
self.print_stack()
|
||||||
self.check_cache.clear()
|
self.check_cache.clear()
|
||||||
|
|
||||||
def push(self, it):
|
def push(self, it):
|
||||||
it = it.encode('utf8')
|
it = it.encode('utf8')
|
||||||
self.stack = it, self.stack
|
self.stack = it, self.stack
|
||||||
self.print_stack()
|
self.print_stack()
|
||||||
self.check_cache.clear()
|
self.check_cache.clear()
|
||||||
|
|
||||||
def peek(self):
|
def peek(self):
|
||||||
if self.stack:
|
if self.stack:
|
||||||
return self.stack[0]
|
return self.stack[0]
|
||||||
|
|
||||||
def interpret(self, command):
|
def interpret(self, command):
|
||||||
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
||||||
return
|
return
|
||||||
old_stack = self.stack
|
old_stack = self.stack
|
||||||
try:
|
try:
|
||||||
self.stack, _, self.dictionary = run(
|
self.stack, _, self.dictionary = run(
|
||||||
command,
|
command,
|
||||||
self.stack,
|
self.stack,
|
||||||
self.dictionary,
|
self.dictionary,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
self.print_stack()
|
self.print_stack()
|
||||||
if old_stack != self.stack:
|
if old_stack != self.stack:
|
||||||
self.check_cache.clear()
|
self.check_cache.clear()
|
||||||
|
|
||||||
def has(self, name):
|
def has(self, name):
|
||||||
return name in self.dictionary
|
return name in self.dictionary
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def print_stack(self):
|
def print_stack(self):
|
||||||
stack_out_index = self.text_widget.search('<' 'STACK', 1.0)
|
stack_out_index = self.text_widget.search('<' 'STACK', 1.0)
|
||||||
if stack_out_index:
|
if stack_out_index:
|
||||||
self.text_widget.see(stack_out_index)
|
self.text_widget.see(stack_out_index)
|
||||||
s = stack_to_string(self.stack) + '\n'
|
s = stack_to_string(self.stack) + '\n'
|
||||||
self.text_widget.insert(stack_out_index, s)
|
self.text_widget.insert(stack_out_index, s)
|
||||||
|
|
||||||
|
|
||||||
class StackDisplayWorld(World):
|
class StackDisplayWorld(World):
|
||||||
|
|
||||||
def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None):
|
def __init__(self, repo, filename, rel_filename, dictionary=None, text_widget=None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
stack = self.load_stack() or ()
|
stack = self.load_stack() or ()
|
||||||
World.__init__(self, stack, dictionary, text_widget)
|
World.__init__(self, stack, dictionary, text_widget)
|
||||||
self.repo = repo
|
self.repo = repo
|
||||||
self.relative_STACK_FN = rel_filename
|
self.relative_STACK_FN = rel_filename
|
||||||
|
|
||||||
def interpret(self, command):
|
def interpret(self, command):
|
||||||
command = command.strip()
|
command = command.strip()
|
||||||
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
if self.has(command) and self.check(command) == False: # not in {True, None}:
|
||||||
return
|
return
|
||||||
print('\njoy?', command)
|
print('\njoy?', command)
|
||||||
super(StackDisplayWorld, self).interpret(command)
|
super(StackDisplayWorld, self).interpret(command)
|
||||||
|
|
||||||
def print_stack(self):
|
def print_stack(self):
|
||||||
print('\n%s <-' % stack_to_string(self.stack))
|
print('\n%s <-' % stack_to_string(self.stack))
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
with open(self.filename, 'wb') as f:
|
with open(self.filename, 'wb') as f:
|
||||||
os.chmod(self.filename, 0o600)
|
os.chmod(self.filename, 0o600)
|
||||||
pickle.dump(self.stack, f, protocol=2)
|
pickle.dump(self.stack, f, protocol=2)
|
||||||
f.flush()
|
f.flush()
|
||||||
os.fsync(f.fileno())
|
os.fsync(f.fileno())
|
||||||
self.repo.stage([self.relative_STACK_FN])
|
self.repo.stage([self.relative_STACK_FN])
|
||||||
commit_id = self.repo.do_commit(
|
commit_id = self.repo.do_commit(
|
||||||
b'auto-save',
|
b'auto-save',
|
||||||
committer=b'thun-auto-save <nobody@example.com>',
|
committer=b'thun-auto-save <nobody@example.com>',
|
||||||
)
|
)
|
||||||
_log.info('commit %s', commit_id)
|
_log.info('commit %s', commit_id)
|
||||||
|
|
||||||
def load_stack(self):
|
def load_stack(self):
|
||||||
if os.path.exists(self.filename):
|
if os.path.exists(self.filename):
|
||||||
with open(self.filename, 'rb') as f:
|
with open(self.filename, 'rb') as f:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
|
|
|
||||||
130
joy/joy.py
130
joy/joy.py
|
|
@ -32,86 +32,86 @@ from .utils.pretty_print import TracePrinter
|
||||||
|
|
||||||
|
|
||||||
def joy(stack, expression, dictionary, viewer=None):
|
def joy(stack, expression, dictionary, viewer=None):
|
||||||
'''Evaluate a Joy expression on a stack.
|
'''Evaluate a Joy expression on a stack.
|
||||||
|
|
||||||
This function iterates through a sequence of terms which are either
|
This function iterates through a sequence of terms which are either
|
||||||
literals (strings, numbers, sequences of terms) or function symbols.
|
literals (strings, numbers, sequences of terms) or function symbols.
|
||||||
Literals are put onto the stack and functions are looked up in the
|
Literals are put onto the stack and functions are looked up in the
|
||||||
disctionary and executed.
|
disctionary and executed.
|
||||||
|
|
||||||
The viewer is a function that is called with the stack and expression
|
The viewer is a function that is called with the stack and expression
|
||||||
on every iteration, its return value is ignored.
|
on every iteration, its return value is ignored.
|
||||||
|
|
||||||
:param stack stack: The stack.
|
:param stack stack: The stack.
|
||||||
:param stack expression: The expression to evaluate.
|
:param stack expression: The expression to evaluate.
|
||||||
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
||||||
:param function viewer: Optional viewer function.
|
:param function viewer: Optional viewer function.
|
||||||
:rtype: (stack, (), dictionary)
|
:rtype: (stack, (), dictionary)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
while expression:
|
while expression:
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
if viewer: viewer(stack, expression)
|
||||||
|
|
||||||
term, expression = expression
|
term, expression = expression
|
||||||
if isinstance(term, Symbol):
|
if isinstance(term, Symbol):
|
||||||
term = dictionary[term]
|
term = dictionary[term]
|
||||||
stack, expression, dictionary = term(stack, expression, dictionary)
|
stack, expression, dictionary = term(stack, expression, dictionary)
|
||||||
else:
|
else:
|
||||||
stack = term, stack
|
stack = term, stack
|
||||||
|
|
||||||
if viewer: viewer(stack, expression)
|
if viewer: viewer(stack, expression)
|
||||||
return stack, expression, dictionary
|
return stack, expression, dictionary
|
||||||
|
|
||||||
|
|
||||||
def run(text, stack, dictionary, viewer=None):
|
def run(text, stack, dictionary, viewer=None):
|
||||||
'''
|
'''
|
||||||
Return the stack resulting from running the Joy code text on the stack.
|
Return the stack resulting from running the Joy code text on the stack.
|
||||||
|
|
||||||
:param str text: Joy code.
|
:param str text: Joy code.
|
||||||
:param stack stack: The stack.
|
:param stack stack: The stack.
|
||||||
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
||||||
:param function viewer: Optional viewer function.
|
:param function viewer: Optional viewer function.
|
||||||
:rtype: (stack, (), dictionary)
|
:rtype: (stack, (), dictionary)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
expression = text_to_expression(text)
|
expression = text_to_expression(text)
|
||||||
return joy(stack, expression, dictionary, viewer)
|
return joy(stack, expression, dictionary, viewer)
|
||||||
|
|
||||||
|
|
||||||
def repl(stack=(), dictionary=None):
|
def repl(stack=(), dictionary=None):
|
||||||
'''
|
'''
|
||||||
Read-Evaluate-Print Loop
|
Read-Evaluate-Print Loop
|
||||||
|
|
||||||
Accept input and run it on the stack, loop.
|
Accept input and run it on the stack, loop.
|
||||||
|
|
||||||
:param stack stack: The stack.
|
:param stack stack: The stack.
|
||||||
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
:param dict dictionary: A ``dict`` mapping names to Joy functions.
|
||||||
:rtype: stack
|
:rtype: stack
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if dictionary is None:
|
if dictionary is None:
|
||||||
dictionary = {}
|
dictionary = {}
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
print()
|
print()
|
||||||
print(stack_to_string(stack), '<-top')
|
print(stack_to_string(stack), '<-top')
|
||||||
print()
|
print()
|
||||||
try:
|
try:
|
||||||
text = input('joy? ')
|
text = input('joy? ')
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
break
|
break
|
||||||
viewer = TracePrinter()
|
viewer = TracePrinter()
|
||||||
try:
|
try:
|
||||||
stack, _, dictionary = run(text, stack, dictionary, viewer.viewer)
|
stack, _, dictionary = run(text, stack, dictionary, viewer.viewer)
|
||||||
except:
|
except:
|
||||||
exc = format_exc() # Capture the exception.
|
exc = format_exc() # Capture the exception.
|
||||||
viewer.print_() # Print the Joy trace.
|
viewer.print_() # Print the Joy trace.
|
||||||
print('-' * 73)
|
print('-' * 73)
|
||||||
print(exc) # Print the original exception.
|
print(exc) # Print the original exception.
|
||||||
else:
|
else:
|
||||||
viewer.print_()
|
viewer.print_()
|
||||||
except:
|
except:
|
||||||
print_exc()
|
print_exc()
|
||||||
print()
|
print()
|
||||||
return stack
|
return stack
|
||||||
|
|
|
||||||
1636
joy/library.py
1636
joy/library.py
File diff suppressed because it is too large
Load Diff
104
joy/parser.py
104
joy/parser.py
|
|
@ -51,74 +51,74 @@ BLANKS = r'\s+'
|
||||||
|
|
||||||
|
|
||||||
class Symbol(str):
|
class Symbol(str):
|
||||||
'''A string class that represents Joy function names.'''
|
'''A string class that represents Joy function names.'''
|
||||||
__repr__ = str.__str__
|
__repr__ = str.__str__
|
||||||
|
|
||||||
|
|
||||||
def text_to_expression(text):
|
def text_to_expression(text):
|
||||||
'''Convert a string to a Joy expression.
|
'''Convert a string to a Joy expression.
|
||||||
|
|
||||||
When supplied with a string this function returns a Python datastructure
|
When supplied with a string this function returns a Python datastructure
|
||||||
that represents the Joy datastructure described by the text expression.
|
that represents the Joy datastructure described by the text expression.
|
||||||
Any unbalanced square brackets will raise a ParseError.
|
Any unbalanced square brackets will raise a ParseError.
|
||||||
|
|
||||||
:param str text: Text to convert.
|
:param str text: Text to convert.
|
||||||
:rtype: stack
|
:rtype: stack
|
||||||
:raises ParseError: if the parse fails.
|
:raises ParseError: if the parse fails.
|
||||||
'''
|
'''
|
||||||
return _parse(_tokenize(text))
|
return _parse(_tokenize(text))
|
||||||
|
|
||||||
|
|
||||||
class ParseError(ValueError):
|
class ParseError(ValueError):
|
||||||
'''Raised when there is a error while parsing text.'''
|
'''Raised when there is a error while parsing text.'''
|
||||||
|
|
||||||
|
|
||||||
def _tokenize(text):
|
def _tokenize(text):
|
||||||
'''Convert a text into a stream of tokens.
|
'''Convert a text into a stream of tokens.
|
||||||
|
|
||||||
Converts function names to Symbols.
|
Converts function names to Symbols.
|
||||||
|
|
||||||
Raise ParseError (with some of the failing text) if the scan fails.
|
Raise ParseError (with some of the failing text) if the scan fails.
|
||||||
'''
|
'''
|
||||||
tokens, rest = _scanner.scan(text)
|
tokens, rest = _scanner.scan(text)
|
||||||
if rest:
|
if rest:
|
||||||
raise ParseError(
|
raise ParseError(
|
||||||
'Scan failed at position %i, %r'
|
'Scan failed at position %i, %r'
|
||||||
% (len(text) - len(rest), rest[:10])
|
% (len(text) - len(rest), rest[:10])
|
||||||
)
|
)
|
||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
def _parse(tokens):
|
def _parse(tokens):
|
||||||
'''
|
'''
|
||||||
Return a stack/list expression of the tokens.
|
Return a stack/list expression of the tokens.
|
||||||
'''
|
'''
|
||||||
frame = []
|
frame = []
|
||||||
stack = []
|
stack = []
|
||||||
for tok in tokens:
|
for tok in tokens:
|
||||||
if tok == '[':
|
if tok == '[':
|
||||||
stack.append(frame)
|
stack.append(frame)
|
||||||
frame = []
|
frame = []
|
||||||
stack[-1].append(frame)
|
stack[-1].append(frame)
|
||||||
elif tok == ']':
|
elif tok == ']':
|
||||||
try:
|
try:
|
||||||
frame = stack.pop()
|
frame = stack.pop()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise ParseError('Extra closing bracket.')
|
raise ParseError('Extra closing bracket.')
|
||||||
frame[-1] = list_to_stack(frame[-1])
|
frame[-1] = list_to_stack(frame[-1])
|
||||||
else:
|
else:
|
||||||
frame.append(tok)
|
frame.append(tok)
|
||||||
if stack:
|
if stack:
|
||||||
raise ParseError('Unclosed bracket.')
|
raise ParseError('Unclosed bracket.')
|
||||||
return list_to_stack(frame)
|
return list_to_stack(frame)
|
||||||
|
|
||||||
|
|
||||||
_scanner = Scanner([
|
_scanner = Scanner([
|
||||||
(FLOAT, lambda _, token: float(token)),
|
(FLOAT, lambda _, token: float(token)),
|
||||||
(INT, lambda _, token: int(token)),
|
(INT, lambda _, token: int(token)),
|
||||||
(SYMBOL, lambda _, token: Symbol(token)),
|
(SYMBOL, lambda _, token: Symbol(token)),
|
||||||
(BRACKETS, lambda _, token: token),
|
(BRACKETS, lambda _, token: token),
|
||||||
(STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')),
|
(STRING_DOUBLE_QUOTED, lambda _, token: token[1:-1].replace('\\"', '"')),
|
||||||
(STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")),
|
(STRING_SINGLE_QUOTED, lambda _, token: token[1:-1].replace("\\'", "'")),
|
||||||
(BLANKS, None),
|
(BLANKS, None),
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -54,43 +54,43 @@ function name! Hopefully they will discover this documentation.
|
||||||
|
|
||||||
|
|
||||||
def rename_code_object(new_name):
|
def rename_code_object(new_name):
|
||||||
'''
|
'''
|
||||||
If you want to wrap a function in another function and have the wrapped
|
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
|
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
|
the wrapper function, you must do this brutal hackery to change the
|
||||||
func.__code__.co_name attribute. Just functools.wraps() is not enough.
|
func.__code__.co_name attribute. Just functools.wraps() is not enough.
|
||||||
|
|
||||||
See:
|
See:
|
||||||
|
|
||||||
https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
|
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
|
https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
|
||||||
|
|
||||||
I'm just glad it's possible.
|
I'm just glad it's possible.
|
||||||
'''
|
'''
|
||||||
def inner(func):
|
def inner(func):
|
||||||
name = new_name + ':' + func.__name__
|
name = new_name + ':' + func.__name__
|
||||||
code_object = func.__code__
|
code_object = func.__code__
|
||||||
return type(func)(
|
return type(func)(
|
||||||
type(code_object)(
|
type(code_object)(
|
||||||
code_object.co_argcount,
|
code_object.co_argcount,
|
||||||
code_object.co_nlocals,
|
code_object.co_nlocals,
|
||||||
code_object.co_stacksize,
|
code_object.co_stacksize,
|
||||||
code_object.co_flags,
|
code_object.co_flags,
|
||||||
code_object.co_code,
|
code_object.co_code,
|
||||||
code_object.co_consts,
|
code_object.co_consts,
|
||||||
code_object.co_names,
|
code_object.co_names,
|
||||||
code_object.co_varnames,
|
code_object.co_varnames,
|
||||||
code_object.co_filename,
|
code_object.co_filename,
|
||||||
name,
|
name,
|
||||||
code_object.co_firstlineno,
|
code_object.co_firstlineno,
|
||||||
code_object.co_lnotab,
|
code_object.co_lnotab,
|
||||||
code_object.co_freevars,
|
code_object.co_freevars,
|
||||||
code_object.co_cellvars
|
code_object.co_cellvars
|
||||||
),
|
),
|
||||||
func.__globals__,
|
func.__globals__,
|
||||||
name,
|
name,
|
||||||
func.__defaults__,
|
func.__defaults__,
|
||||||
func.__closure__
|
func.__closure__
|
||||||
)
|
)
|
||||||
return inner
|
return inner
|
||||||
|
|
|
||||||
|
|
@ -21,49 +21,49 @@ from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
def import_yin():
|
def import_yin():
|
||||||
from joy.utils.generated_library import *
|
from joy.utils.generated_library import *
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
class InfiniteStack(tuple):
|
class InfiniteStack(tuple):
|
||||||
|
|
||||||
def _names():
|
def _names():
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
m = yield Symbol('a' + str(n))
|
m = yield Symbol('a' + str(n))
|
||||||
n = n + 1 if m is None else m
|
n = n + 1 if m is None else m
|
||||||
|
|
||||||
_NAMES = _names()
|
_NAMES = _names()
|
||||||
next(_NAMES)
|
next(_NAMES)
|
||||||
|
|
||||||
names = lambda: next(_NAMES)
|
names = lambda: next(_NAMES)
|
||||||
reset = lambda _self, _n=_NAMES: _n.send(-1)
|
reset = lambda _self, _n=_NAMES: _n.send(-1)
|
||||||
|
|
||||||
def __init__(self, code):
|
def __init__(self, code):
|
||||||
self.reset()
|
self.reset()
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if not self:
|
if not self:
|
||||||
new_var = self.names()
|
new_var = self.names()
|
||||||
self.code.append(('pop', new_var))
|
self.code.append(('pop', new_var))
|
||||||
return iter((new_var, self))
|
return iter((new_var, self))
|
||||||
|
|
||||||
|
|
||||||
def I(expression):
|
def I(expression):
|
||||||
code = []
|
code = []
|
||||||
stack = InfiniteStack(code)
|
stack = InfiniteStack(code)
|
||||||
|
|
||||||
while expression:
|
while expression:
|
||||||
term, expression = expression
|
term, expression = expression
|
||||||
if isinstance(term, Symbol):
|
if isinstance(term, Symbol):
|
||||||
func = D[term]
|
func = D[term]
|
||||||
stack, expression, _ = func(stack, expression, code)
|
stack, expression, _ = func(stack, expression, code)
|
||||||
else:
|
else:
|
||||||
stack = term, stack
|
stack = term, stack
|
||||||
|
|
||||||
code.append(tuple(['ret'] + list(iter_stack(stack))))
|
code.append(tuple(['ret'] + list(iter_stack(stack))))
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
|
||||||
strtup = lambda a, b: '(%s, %s)' % (b, a)
|
strtup = lambda a, b: '(%s, %s)' % (b, a)
|
||||||
|
|
@ -71,123 +71,123 @@ strstk = lambda rest: reduce(strtup, rest, 'stack')
|
||||||
|
|
||||||
|
|
||||||
def code_gen(code):
|
def code_gen(code):
|
||||||
#for p in code: print p
|
#for p in code: print p
|
||||||
coalesce_pops(code)
|
coalesce_pops(code)
|
||||||
lines = []
|
lines = []
|
||||||
emit = lines.append
|
emit = lines.append
|
||||||
for t in code:
|
for t in code:
|
||||||
tag, rest = t[0], t[1:]
|
tag, rest = t[0], t[1:]
|
||||||
if tag == 'pop': emit(strstk(rest) + ' = stack')
|
if tag == 'pop': emit(strstk(rest) + ' = stack')
|
||||||
elif tag == 'call': emit('%s = %s%s' % rest)
|
elif tag == 'call': emit('%s = %s%s' % rest)
|
||||||
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
|
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
|
||||||
else:
|
else:
|
||||||
raise ValueError(tag)
|
raise ValueError(tag)
|
||||||
return '\n'.join(' ' + line for line in lines)
|
return '\n'.join(' ' + line for line in lines)
|
||||||
|
|
||||||
|
|
||||||
def coalesce_pops(code):
|
def coalesce_pops(code):
|
||||||
code.sort(key=lambda p: p[0] != 'pop') # All pops to the front.
|
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'))
|
try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
|
||||||
except StopIteration: return
|
except StopIteration: return
|
||||||
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
|
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
|
||||||
|
|
||||||
|
|
||||||
def compile_yinyang(name, text):
|
def compile_yinyang(name, text):
|
||||||
return '''
|
return '''
|
||||||
def %s(stack):
|
def %s(stack):
|
||||||
%s
|
%s
|
||||||
''' % (name, code_gen(I(text_to_expression(text))))
|
''' % (name, code_gen(I(text_to_expression(text))))
|
||||||
|
|
||||||
|
|
||||||
def q():
|
def q():
|
||||||
memo = {}
|
memo = {}
|
||||||
def bar(type_var):
|
def bar(type_var):
|
||||||
try:
|
try:
|
||||||
res = memo[type_var]
|
res = memo[type_var]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
res = memo[type_var] = InfiniteStack.names()
|
res = memo[type_var] = InfiniteStack.names()
|
||||||
return res
|
return res
|
||||||
return bar
|
return bar
|
||||||
|
|
||||||
|
|
||||||
def type_vars_to_labels(thing, map_):
|
def type_vars_to_labels(thing, map_):
|
||||||
if not thing:
|
if not thing:
|
||||||
return thing
|
return thing
|
||||||
if not isinstance(thing, tuple):
|
if not isinstance(thing, tuple):
|
||||||
return map_(thing)
|
return map_(thing)
|
||||||
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
|
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
|
||||||
|
|
||||||
|
|
||||||
def remap_inputs(in_, stack, code):
|
def remap_inputs(in_, stack, code):
|
||||||
map_ = q()
|
map_ = q()
|
||||||
while in_:
|
while in_:
|
||||||
term, in_ = in_
|
term, in_ = in_
|
||||||
arg0, stack = stack
|
arg0, stack = stack
|
||||||
term = type_vars_to_labels(term, map_)
|
term = type_vars_to_labels(term, map_)
|
||||||
code.append(('call', term, '', arg0))
|
code.append(('call', term, '', arg0))
|
||||||
return stack, map_
|
return stack, map_
|
||||||
|
|
||||||
|
|
||||||
class BinaryBuiltin(object):
|
class BinaryBuiltin(object):
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
def __call__(self, stack, expression, code):
|
def __call__(self, stack, expression, code):
|
||||||
in1, (in0, stack) = stack
|
in1, (in0, stack) = stack
|
||||||
out = InfiniteStack.names()
|
out = InfiniteStack.names()
|
||||||
code.append(('call', out, self.name, (in0, in1)))
|
code.append(('call', out, self.name, (in0, in1)))
|
||||||
return (out, stack), expression, code
|
return (out, stack), expression, code
|
||||||
|
|
||||||
|
|
||||||
YIN = import_yin()
|
YIN = import_yin()
|
||||||
|
|
||||||
|
|
||||||
D = {
|
D = {
|
||||||
name: SimpleFunctionWrapper(YIN[name])
|
name: SimpleFunctionWrapper(YIN[name])
|
||||||
for name in '''
|
for name in '''
|
||||||
ccons
|
ccons
|
||||||
cons
|
cons
|
||||||
dup
|
dup
|
||||||
dupd
|
dupd
|
||||||
dupdd
|
dupdd
|
||||||
over
|
over
|
||||||
pop
|
pop
|
||||||
popd
|
popd
|
||||||
popdd
|
popdd
|
||||||
popop
|
popop
|
||||||
popopd
|
popopd
|
||||||
popopdd
|
popopdd
|
||||||
rolldown
|
rolldown
|
||||||
rollup
|
rollup
|
||||||
swap
|
swap
|
||||||
swons
|
swons
|
||||||
tuck
|
tuck
|
||||||
unit
|
unit
|
||||||
'''.split()
|
'''.split()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for name in '''
|
for name in '''
|
||||||
first
|
first
|
||||||
first_two
|
first_two
|
||||||
fourth
|
fourth
|
||||||
rest
|
rest
|
||||||
rrest
|
rrest
|
||||||
second
|
second
|
||||||
third
|
third
|
||||||
uncons
|
uncons
|
||||||
unswons
|
unswons
|
||||||
'''.split():
|
'''.split():
|
||||||
|
|
||||||
def foo(stack, expression, code, name=name):
|
def foo(stack, expression, code, name=name):
|
||||||
in_, out = YIN_STACK_EFFECTS[name]
|
in_, out = YIN_STACK_EFFECTS[name]
|
||||||
stack, map_ = remap_inputs(in_, stack, code)
|
stack, map_ = remap_inputs(in_, stack, code)
|
||||||
out = type_vars_to_labels(out, map_)
|
out = type_vars_to_labels(out, map_)
|
||||||
return concat(out, stack), expression, code
|
return concat(out, stack), expression, code
|
||||||
|
|
||||||
foo.__name__ = name
|
foo.__name__ = name
|
||||||
D[name] = foo
|
D[name] = foo
|
||||||
|
|
||||||
|
|
||||||
for name in '''
|
for name in '''
|
||||||
|
|
@ -210,18 +210,18 @@ for name in '''
|
||||||
sub
|
sub
|
||||||
truediv
|
truediv
|
||||||
'''.split():
|
'''.split():
|
||||||
D[name.rstrip('-')] = BinaryBuiltin(name)
|
D[name.rstrip('-')] = BinaryBuiltin(name)
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
stack
|
stack
|
||||||
stuncons
|
stuncons
|
||||||
stununcons
|
stununcons
|
||||||
swaack
|
swaack
|
||||||
'''
|
'''
|
||||||
|
|
||||||
for name in sorted(D):
|
for name in sorted(D):
|
||||||
print(name, end=' ')
|
print(name, end=' ')
|
||||||
## print compile_yinyang(name, name)
|
## print compile_yinyang(name, name)
|
||||||
print('-' * 100)
|
print('-' * 100)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@ from joy.parser import Symbol
|
||||||
|
|
||||||
|
|
||||||
def _names():
|
def _names():
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
yield Symbol('a' + str(n))
|
yield Symbol('a' + str(n))
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
|
|
||||||
class InfiniteStack(tuple):
|
class InfiniteStack(tuple):
|
||||||
|
|
||||||
names = lambda n=_names(): next(n)
|
names = lambda n=_names(): next(n)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
if not self:
|
if not self:
|
||||||
return iter((self.names(), self))
|
return iter((self.names(), self))
|
||||||
|
|
||||||
|
|
||||||
i = InfiniteStack()
|
i = InfiniteStack()
|
||||||
|
|
@ -23,9 +23,9 @@ i = InfiniteStack()
|
||||||
a, b = i
|
a, b = i
|
||||||
|
|
||||||
lambda u: (lambda fu, u: fu * fu * u)(
|
lambda u: (lambda fu, u: fu * fu * u)(
|
||||||
(lambda u: (lambda fu, u: fu * fu)(
|
(lambda u: (lambda fu, u: fu * fu)(
|
||||||
(lambda u: (lambda fu, u: fu * fu * u)(
|
(lambda u: (lambda fu, u: fu * fu * u)(
|
||||||
(lambda u: 1)(u), u))(u), u))(u),
|
(lambda u: 1)(u), 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)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -46,54 +46,54 @@ from .stack import expression_to_string, stack_to_string
|
||||||
|
|
||||||
|
|
||||||
class TracePrinter(object):
|
class TracePrinter(object):
|
||||||
'''
|
'''
|
||||||
This is what does the formatting. You instantiate it and pass the ``viewer()``
|
This is what does the formatting. You instantiate it and pass the ``viewer()``
|
||||||
method to the :py:func:`joy.joy.joy` function, then print it to see the
|
method to the :py:func:`joy.joy.joy` function, then print it to see the
|
||||||
trace.
|
trace.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.history = []
|
self.history = []
|
||||||
|
|
||||||
def viewer(self, stack, expression):
|
def viewer(self, stack, expression):
|
||||||
'''
|
'''
|
||||||
Record the current stack and expression in the TracePrinter's history.
|
Record the current stack and expression in the TracePrinter's history.
|
||||||
Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function.
|
Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function.
|
||||||
|
|
||||||
:param stack quote: A stack.
|
:param stack quote: A stack.
|
||||||
:param stack expression: A stack.
|
:param stack expression: A stack.
|
||||||
'''
|
'''
|
||||||
self.history.append((stack, expression))
|
self.history.append((stack, expression))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '\n'.join(self.go())
|
return '\n'.join(self.go())
|
||||||
|
|
||||||
def go(self):
|
def go(self):
|
||||||
'''
|
'''
|
||||||
Return a list of strings, one for each entry in the history, prefixed
|
Return a list of strings, one for each entry in the history, prefixed
|
||||||
with enough spaces to align all the interpreter dots.
|
with enough spaces to align all the interpreter dots.
|
||||||
|
|
||||||
This method is called internally by the ``__str__()`` method.
|
This method is called internally by the ``__str__()`` method.
|
||||||
|
|
||||||
:rtype: list(str)
|
:rtype: list(str)
|
||||||
'''
|
'''
|
||||||
max_stack_length = 0
|
max_stack_length = 0
|
||||||
lines = []
|
lines = []
|
||||||
for stack, expression in self.history:
|
for stack, expression in self.history:
|
||||||
stack = stack_to_string(stack)
|
stack = stack_to_string(stack)
|
||||||
expression = expression_to_string(expression)
|
expression = expression_to_string(expression)
|
||||||
n = len(stack)
|
n = len(stack)
|
||||||
if n > max_stack_length:
|
if n > max_stack_length:
|
||||||
max_stack_length = n
|
max_stack_length = n
|
||||||
lines.append((n, '%s . %s' % (stack, expression)))
|
lines.append((n, '%s . %s' % (stack, expression)))
|
||||||
return [ # Prefix spaces to line up '.'s.
|
return [ # Prefix spaces to line up '.'s.
|
||||||
(' ' * (max_stack_length - length) + line)
|
(' ' * (max_stack_length - length) + line)
|
||||||
for length, line in lines
|
for length, line in lines
|
||||||
]
|
]
|
||||||
|
|
||||||
def print_(self):
|
def print_(self):
|
||||||
try:
|
try:
|
||||||
print(self)
|
print(self)
|
||||||
except:
|
except:
|
||||||
print_exc()
|
print_exc()
|
||||||
print('Exception while printing viewer.')
|
print('Exception while printing viewer.')
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ means we can directly "unpack" the expected arguments to a Joy function.
|
||||||
|
|
||||||
For example::
|
For example::
|
||||||
|
|
||||||
def dup((head, tail)):
|
def dup((head, tail)):
|
||||||
return head, (head, tail)
|
return head, (head, tail)
|
||||||
|
|
||||||
We replace the argument "stack" by the expected structure of the stack,
|
We replace the argument "stack" by the expected structure of the stack,
|
||||||
in this case "(head, tail)", and Python takes care of unpacking the
|
in this case "(head, tail)", and Python takes care of unpacking the
|
||||||
|
|
@ -56,9 +56,9 @@ Unfortunately, the Sphinx documentation generator, which is used to generate thi
|
||||||
web page, doesn't handle tuples in the function parameters. And in Python 3, this
|
web page, doesn't handle tuples in the function parameters. And in Python 3, this
|
||||||
syntax was removed entirely. Instead you would have to write::
|
syntax was removed entirely. Instead you would have to write::
|
||||||
|
|
||||||
def dup(stack):
|
def dup(stack):
|
||||||
head, tail = stack
|
head, tail = stack
|
||||||
return head, (head, tail)
|
return head, (head, tail)
|
||||||
|
|
||||||
|
|
||||||
We have two very simple functions, one to build up a stack from a Python
|
We have two very simple functions, one to build up a stack from a Python
|
||||||
|
|
@ -74,95 +74,95 @@ printed left-to-right. These functions are written to support :doc:`../pretty`.
|
||||||
|
|
||||||
from builtins import map
|
from builtins import map
|
||||||
def list_to_stack(el, stack=()):
|
def list_to_stack(el, stack=()):
|
||||||
'''Convert a Python list (or other sequence) to a Joy stack::
|
'''Convert a Python list (or other sequence) to a Joy stack::
|
||||||
|
|
||||||
[1, 2, 3] -> (1, (2, (3, ())))
|
[1, 2, 3] -> (1, (2, (3, ())))
|
||||||
|
|
||||||
:param list el: A Python list or other sequence (iterators and generators
|
:param list el: A Python list or other sequence (iterators and generators
|
||||||
won't work because ``reverse()`` is called on ``el``.)
|
won't work because ``reverse()`` is called on ``el``.)
|
||||||
:param stack stack: A stack, optional, defaults to the empty stack.
|
:param stack stack: A stack, optional, defaults to the empty stack.
|
||||||
:rtype: stack
|
:rtype: stack
|
||||||
|
|
||||||
'''
|
'''
|
||||||
for item in reversed(el):
|
for item in reversed(el):
|
||||||
stack = item, stack
|
stack = item, stack
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
|
|
||||||
def iter_stack(stack):
|
def iter_stack(stack):
|
||||||
'''Iterate through the items on the stack.
|
'''Iterate through the items on the stack.
|
||||||
|
|
||||||
:param stack stack: A stack.
|
:param stack stack: A stack.
|
||||||
:rtype: iterator
|
:rtype: iterator
|
||||||
'''
|
'''
|
||||||
while stack:
|
while stack:
|
||||||
item, stack = stack
|
item, stack = stack
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
|
|
||||||
def stack_to_string(stack):
|
def stack_to_string(stack):
|
||||||
'''
|
'''
|
||||||
Return a "pretty print" string for a stack.
|
Return a "pretty print" string for a stack.
|
||||||
|
|
||||||
The items are written right-to-left::
|
The items are written right-to-left::
|
||||||
|
|
||||||
(top, (second, ...)) -> '... second top'
|
(top, (second, ...)) -> '... second top'
|
||||||
|
|
||||||
:param stack stack: A stack.
|
:param stack stack: A stack.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
'''
|
'''
|
||||||
f = lambda stack: reversed(list(iter_stack(stack)))
|
f = lambda stack: reversed(list(iter_stack(stack)))
|
||||||
return _to_string(stack, f)
|
return _to_string(stack, f)
|
||||||
|
|
||||||
|
|
||||||
def expression_to_string(expression):
|
def expression_to_string(expression):
|
||||||
'''
|
'''
|
||||||
Return a "pretty print" string for a expression.
|
Return a "pretty print" string for a expression.
|
||||||
|
|
||||||
The items are written left-to-right::
|
The items are written left-to-right::
|
||||||
|
|
||||||
(top, (second, ...)) -> 'top second ...'
|
(top, (second, ...)) -> 'top second ...'
|
||||||
|
|
||||||
:param stack expression: A stack.
|
:param stack expression: A stack.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
'''
|
'''
|
||||||
return _to_string(expression, iter_stack)
|
return _to_string(expression, iter_stack)
|
||||||
|
|
||||||
|
|
||||||
def _to_string(stack, f):
|
def _to_string(stack, f):
|
||||||
if not isinstance(stack, tuple): return repr(stack)
|
if not isinstance(stack, tuple): return repr(stack)
|
||||||
if not stack: return '' # shortcut
|
if not stack: return '' # shortcut
|
||||||
return ' '.join(map(_s, f(stack)))
|
return ' '.join(map(_s, f(stack)))
|
||||||
|
|
||||||
|
|
||||||
_s = lambda s: (
|
_s = lambda s: (
|
||||||
'[%s]' % expression_to_string(s) if isinstance(s, tuple)
|
'[%s]' % expression_to_string(s) if isinstance(s, tuple)
|
||||||
else repr(s)
|
else repr(s)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def concat(quote, expression):
|
def concat(quote, expression):
|
||||||
'''Concatinate quote onto expression.
|
'''Concatinate quote onto expression.
|
||||||
|
|
||||||
In joy [1 2] [3 4] would become [1 2 3 4].
|
In joy [1 2] [3 4] would become [1 2 3 4].
|
||||||
|
|
||||||
:param stack quote: A stack.
|
:param stack quote: A stack.
|
||||||
:param stack expression: A stack.
|
:param stack expression: A stack.
|
||||||
:raises RuntimeError: if quote is larger than sys.getrecursionlimit().
|
:raises RuntimeError: if quote is larger than sys.getrecursionlimit().
|
||||||
:rtype: stack
|
:rtype: stack
|
||||||
'''
|
'''
|
||||||
# This is the fastest implementation, but will trigger
|
# This is the fastest implementation, but will trigger
|
||||||
# RuntimeError: maximum recursion depth exceeded
|
# RuntimeError: maximum recursion depth exceeded
|
||||||
# on quotes longer than sys.getrecursionlimit().
|
# on quotes longer than sys.getrecursionlimit().
|
||||||
|
|
||||||
return (quote[0], concat(quote[1], expression)) if quote else expression
|
return (quote[0], concat(quote[1], expression)) if quote else expression
|
||||||
|
|
||||||
# Original implementation.
|
# Original implementation.
|
||||||
|
|
||||||
## return list_to_stack(list(iter_stack(quote)), expression)
|
## return list_to_stack(list(iter_stack(quote)), expression)
|
||||||
|
|
||||||
# In-lining is slightly faster (and won't break the
|
# In-lining is slightly faster (and won't break the
|
||||||
# recursion limit on long quotes.)
|
# recursion limit on long quotes.)
|
||||||
|
|
||||||
## temp = []
|
## temp = []
|
||||||
## while quote:
|
## while quote:
|
||||||
|
|
@ -175,23 +175,23 @@ def concat(quote, expression):
|
||||||
|
|
||||||
|
|
||||||
def pick(stack, n):
|
def pick(stack, n):
|
||||||
'''
|
'''
|
||||||
Return the nth item on the stack.
|
Return the nth item on the stack.
|
||||||
|
|
||||||
:param stack stack: A stack.
|
:param stack stack: A stack.
|
||||||
:param int n: An index into the stack.
|
:param int n: An index into the stack.
|
||||||
:raises ValueError: if ``n`` is less than zero.
|
:raises ValueError: if ``n`` is less than zero.
|
||||||
:raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
|
:raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
|
||||||
:rtype: whatever
|
:rtype: whatever
|
||||||
'''
|
'''
|
||||||
if n < 0:
|
if n < 0:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
item, stack = stack
|
item, stack = stack
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise IndexError
|
raise IndexError
|
||||||
n -= 1
|
n -= 1
|
||||||
if n < 0:
|
if n < 0:
|
||||||
break
|
break
|
||||||
return item
|
return item
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
322
joy/vui/core.py
322
joy/vui/core.py
|
|
@ -48,18 +48,18 @@ GREEN = 70, 200, 70
|
||||||
|
|
||||||
|
|
||||||
MOUSE_EVENTS = frozenset({
|
MOUSE_EVENTS = frozenset({
|
||||||
pygame.MOUSEMOTION,
|
pygame.MOUSEMOTION,
|
||||||
pygame.MOUSEBUTTONDOWN,
|
pygame.MOUSEBUTTONDOWN,
|
||||||
pygame.MOUSEBUTTONUP
|
pygame.MOUSEBUTTONUP
|
||||||
})
|
})
|
||||||
'PyGame mouse events.'
|
'PyGame mouse events.'
|
||||||
|
|
||||||
ARROW_KEYS = frozenset({
|
ARROW_KEYS = frozenset({
|
||||||
pygame.K_UP,
|
pygame.K_UP,
|
||||||
pygame.K_DOWN,
|
pygame.K_DOWN,
|
||||||
pygame.K_LEFT,
|
pygame.K_LEFT,
|
||||||
pygame.K_RIGHT
|
pygame.K_RIGHT
|
||||||
})
|
})
|
||||||
'PyGame arrow key events.'
|
'PyGame arrow key events.'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -82,201 +82,201 @@ SUCCESS = 1
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
class Message(object):
|
||||||
'''Message base class. Contains ``sender`` field.'''
|
'''Message base class. Contains ``sender`` field.'''
|
||||||
def __init__(self, sender):
|
def __init__(self, sender):
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
|
|
||||||
|
|
||||||
class CommandMessage(Message):
|
class CommandMessage(Message):
|
||||||
'''For commands, adds ``command`` field.'''
|
'''For commands, adds ``command`` field.'''
|
||||||
def __init__(self, sender, command):
|
def __init__(self, sender, command):
|
||||||
Message.__init__(self, sender)
|
Message.__init__(self, sender)
|
||||||
self.command = command
|
self.command = command
|
||||||
|
|
||||||
|
|
||||||
class ModifyMessage(Message):
|
class ModifyMessage(Message):
|
||||||
'''
|
'''
|
||||||
For when resources are modified, adds ``subject`` and ``details``
|
For when resources are modified, adds ``subject`` and ``details``
|
||||||
fields.
|
fields.
|
||||||
'''
|
'''
|
||||||
def __init__(self, sender, subject, **details):
|
def __init__(self, sender, subject, **details):
|
||||||
Message.__init__(self, sender)
|
Message.__init__(self, sender)
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.details = details
|
self.details = details
|
||||||
|
|
||||||
|
|
||||||
class OpenMessage(Message):
|
class OpenMessage(Message):
|
||||||
'''
|
'''
|
||||||
For when resources are modified, adds ``name``, content_id``,
|
For when resources are modified, adds ``name``, content_id``,
|
||||||
``status``, and ``traceback`` fields.
|
``status``, and ``traceback`` fields.
|
||||||
'''
|
'''
|
||||||
def __init__(self, sender, name):
|
def __init__(self, sender, name):
|
||||||
Message.__init__(self, sender)
|
Message.__init__(self, sender)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.content_id = self.thing = None
|
self.content_id = self.thing = None
|
||||||
self.status = PENDING
|
self.status = PENDING
|
||||||
self.traceback = None
|
self.traceback = None
|
||||||
|
|
||||||
|
|
||||||
class PersistMessage(Message):
|
class PersistMessage(Message):
|
||||||
'''
|
'''
|
||||||
For when resources are modified, adds ``content_id`` and ``details``
|
For when resources are modified, adds ``content_id`` and ``details``
|
||||||
fields.
|
fields.
|
||||||
'''
|
'''
|
||||||
def __init__(self, sender, content_id, **details):
|
def __init__(self, sender, content_id, **details):
|
||||||
Message.__init__(self, sender)
|
Message.__init__(self, sender)
|
||||||
self.content_id = content_id
|
self.content_id = content_id
|
||||||
self.details = details
|
self.details = details
|
||||||
|
|
||||||
|
|
||||||
class ShutdownMessage(Message):
|
class ShutdownMessage(Message):
|
||||||
'''Signals that the system is shutting down.'''
|
'''Signals that the system is shutting down.'''
|
||||||
|
|
||||||
|
|
||||||
# Joy Interpreter & Context
|
# Joy Interpreter & Context
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
'''
|
'''
|
||||||
This object contains the system context, the stack, dictionary, a
|
This object contains the system context, the stack, dictionary, a
|
||||||
reference to the display broadcast method, and the log.
|
reference to the display broadcast method, and the log.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
|
def __init__(self, stack_id, stack_holder, dictionary, notify, log):
|
||||||
self.stack_holder = stack_holder
|
self.stack_holder = stack_holder
|
||||||
self.dictionary = dictionary
|
self.dictionary = dictionary
|
||||||
self.notify = notify
|
self.notify = notify
|
||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
self.log = log.lines
|
self.log = log.lines
|
||||||
self.log_id = log.content_id
|
self.log_id = log.content_id
|
||||||
|
|
||||||
def handle(self, message):
|
def handle(self, message):
|
||||||
'''
|
'''
|
||||||
Deal with updates to the stack and commands.
|
Deal with updates to the stack and commands.
|
||||||
'''
|
'''
|
||||||
if (isinstance(message, ModifyMessage)
|
if (isinstance(message, ModifyMessage)
|
||||||
and message.subject is self.stack_holder
|
and message.subject is self.stack_holder
|
||||||
):
|
):
|
||||||
self._log_lines('', '%s <-' % self.format_stack())
|
self._log_lines('', '%s <-' % self.format_stack())
|
||||||
if not isinstance(message, CommandMessage):
|
if not isinstance(message, CommandMessage):
|
||||||
return
|
return
|
||||||
c, s, d = message.command, self.stack_holder[0], self.dictionary
|
c, s, d = message.command, self.stack_holder[0], self.dictionary
|
||||||
self._log_lines('', '-> %s' % (c,))
|
self._log_lines('', '-> %s' % (c,))
|
||||||
self.stack_holder[0], _, self.dictionary = run(c, s, d)
|
self.stack_holder[0], _, self.dictionary = run(c, s, d)
|
||||||
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
|
mm = ModifyMessage(self, self.stack_holder, content_id=self.stack_id)
|
||||||
self.notify(mm)
|
self.notify(mm)
|
||||||
|
|
||||||
def _log_lines(self, *lines):
|
def _log_lines(self, *lines):
|
||||||
self.log.extend(lines)
|
self.log.extend(lines)
|
||||||
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
|
self.notify(ModifyMessage(self, self.log, content_id=self.log_id))
|
||||||
|
|
||||||
def format_stack(self):
|
def format_stack(self):
|
||||||
try:
|
try:
|
||||||
return stack_to_string(self.stack_holder[0])
|
return stack_to_string(self.stack_holder[0])
|
||||||
except:
|
except:
|
||||||
print(format_exc(), file=stderr)
|
print(format_exc(), file=stderr)
|
||||||
return str(self.stack_holder[0])
|
return str(self.stack_holder[0])
|
||||||
|
|
||||||
|
|
||||||
def push(sender, item, notify, stack_name='stack.pickle'):
|
def push(sender, item, notify, stack_name='stack.pickle'):
|
||||||
'''
|
'''
|
||||||
Helper function to push an item onto the system stack with message.
|
Helper function to push an item onto the system stack with message.
|
||||||
'''
|
'''
|
||||||
om = OpenMessage(sender, stack_name)
|
om = OpenMessage(sender, stack_name)
|
||||||
notify(om)
|
notify(om)
|
||||||
if om.status == SUCCESS:
|
if om.status == SUCCESS:
|
||||||
om.thing[0] = item, om.thing[0]
|
om.thing[0] = item, om.thing[0]
|
||||||
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
|
notify(ModifyMessage(sender, om.thing, content_id=om.content_id))
|
||||||
return om.status
|
return om.status
|
||||||
|
|
||||||
|
|
||||||
def open_viewer_on_string(sender, content, notify):
|
def open_viewer_on_string(sender, content, notify):
|
||||||
'''
|
'''
|
||||||
Helper function to open a text viewer on a string.
|
Helper function to open a text viewer on a string.
|
||||||
Typically used to show tracebacks.
|
Typically used to show tracebacks.
|
||||||
'''
|
'''
|
||||||
push(sender, content, notify)
|
push(sender, content, notify)
|
||||||
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
|
notify(CommandMessage(sender, 'good_viewer_location open_viewer'))
|
||||||
|
|
||||||
|
|
||||||
# main loop
|
# main loop
|
||||||
|
|
||||||
|
|
||||||
class TheLoop(object):
|
class TheLoop(object):
|
||||||
'''
|
'''
|
||||||
The main loop manages tasks and the PyGame event queue
|
The main loop manages tasks and the PyGame event queue
|
||||||
and framerate clock.
|
and framerate clock.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
FRAME_RATE = 24
|
FRAME_RATE = 24
|
||||||
|
|
||||||
def __init__(self, display, clock):
|
def __init__(self, display, clock):
|
||||||
self.display = display
|
self.display = display
|
||||||
self.clock = clock
|
self.clock = clock
|
||||||
self.tasks = {}
|
self.tasks = {}
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def install_task(self, F, milliseconds):
|
def install_task(self, F, milliseconds):
|
||||||
'''
|
'''
|
||||||
Install a task to run every so many milliseconds.
|
Install a task to run every so many milliseconds.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
task_event_id = AVAILABLE_TASK_EVENTS.pop()
|
task_event_id = AVAILABLE_TASK_EVENTS.pop()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise RuntimeError('out of task ids')
|
raise RuntimeError('out of task ids')
|
||||||
self.tasks[task_event_id] = F
|
self.tasks[task_event_id] = F
|
||||||
pygame.time.set_timer(task_event_id, milliseconds)
|
pygame.time.set_timer(task_event_id, milliseconds)
|
||||||
return task_event_id
|
return task_event_id
|
||||||
|
|
||||||
def remove_task(self, task_event_id):
|
def remove_task(self, task_event_id):
|
||||||
'''
|
'''
|
||||||
Remove an installed task.
|
Remove an installed task.
|
||||||
'''
|
'''
|
||||||
assert task_event_id in self.tasks, repr(task_event_id)
|
assert task_event_id in self.tasks, repr(task_event_id)
|
||||||
pygame.time.set_timer(task_event_id, 0)
|
pygame.time.set_timer(task_event_id, 0)
|
||||||
del self.tasks[task_event_id]
|
del self.tasks[task_event_id]
|
||||||
AVAILABLE_TASK_EVENTS.add(task_event_id)
|
AVAILABLE_TASK_EVENTS.add(task_event_id)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
# Best effort to cancel all running tasks.
|
# Best effort to cancel all running tasks.
|
||||||
for task_event_id in self.tasks:
|
for task_event_id in self.tasks:
|
||||||
pygame.time.set_timer(task_event_id, 0)
|
pygame.time.set_timer(task_event_id, 0)
|
||||||
|
|
||||||
def run_task(self, task_event_id):
|
def run_task(self, task_event_id):
|
||||||
'''
|
'''
|
||||||
Give a task its time to shine.
|
Give a task its time to shine.
|
||||||
'''
|
'''
|
||||||
task = self.tasks[task_event_id]
|
task = self.tasks[task_event_id]
|
||||||
try:
|
try:
|
||||||
task()
|
task()
|
||||||
except:
|
except:
|
||||||
traceback = format_exc()
|
traceback = format_exc()
|
||||||
self.remove_task(task_event_id)
|
self.remove_task(task_event_id)
|
||||||
print(traceback, file=stderr)
|
print(traceback, file=stderr)
|
||||||
print('TASK removed due to ERROR', task, file=stderr)
|
print('TASK removed due to ERROR', task, file=stderr)
|
||||||
open_viewer_on_string(self, traceback, self.display.broadcast)
|
open_viewer_on_string(self, traceback, self.display.broadcast)
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
'''
|
'''
|
||||||
The actual main loop machinery.
|
The actual main loop machinery.
|
||||||
|
|
||||||
Maintain a ``running`` flag, pump the PyGame event queue and
|
Maintain a ``running`` flag, pump the PyGame event queue and
|
||||||
handle the events (dispatching to the display), tick the clock.
|
handle the events (dispatching to the display), tick the clock.
|
||||||
|
|
||||||
When the loop is exited (by clicking the window close button or
|
When the loop is exited (by clicking the window close button or
|
||||||
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
|
pressing the ``escape`` key) it broadcasts a ``ShutdownMessage``.
|
||||||
'''
|
'''
|
||||||
self.running = True
|
self.running = True
|
||||||
while self.running:
|
while self.running:
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
self.running = False
|
self.running = False
|
||||||
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
|
elif event.type == pygame.KEYUP and event.key == pygame.K_ESCAPE:
|
||||||
self.running = False
|
self.running = False
|
||||||
elif event.type in self.tasks:
|
elif event.type in self.tasks:
|
||||||
self.run_task(event.type)
|
self.run_task(event.type)
|
||||||
else:
|
else:
|
||||||
self.display.dispatch_event(event)
|
self.display.dispatch_event(event)
|
||||||
pygame.display.update()
|
pygame.display.update()
|
||||||
self.clock.tick(self.FRAME_RATE)
|
self.clock.tick(self.FRAME_RATE)
|
||||||
self.display.broadcast(ShutdownMessage(self))
|
self.display.broadcast(ShutdownMessage(self))
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,17 @@ import sys, traceback
|
||||||
|
|
||||||
# To enable "hot" reloading in the IDLE shell.
|
# To enable "hot" reloading in the IDLE shell.
|
||||||
for name in 'core main display viewer text_viewer stack_viewer persist_task'.split():
|
for name in 'core main display viewer text_viewer stack_viewer persist_task'.split():
|
||||||
try:
|
try:
|
||||||
del sys.modules[name]
|
del sys.modules[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from . import main
|
from . import main
|
||||||
|
|
||||||
try:
|
try:
|
||||||
A = A # (screen, clock, pt), three things that we DON'T want to recreate
|
A = A # (screen, clock, pt), three things that we DON'T want to recreate
|
||||||
# each time we restart main().
|
# each time we restart main().
|
||||||
except NameError:
|
except NameError:
|
||||||
A = main.init()
|
A = main.init()
|
||||||
|
|
||||||
d = main.main(*A)
|
d = main.main(*A)
|
||||||
|
|
|
||||||
|
|
@ -38,473 +38,473 @@ from sys import stderr
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import pygame
|
import pygame
|
||||||
from .core import (
|
from .core import (
|
||||||
open_viewer_on_string,
|
open_viewer_on_string,
|
||||||
GREY,
|
GREY,
|
||||||
MOUSE_EVENTS,
|
MOUSE_EVENTS,
|
||||||
)
|
)
|
||||||
from .viewer import Viewer
|
from .viewer import Viewer
|
||||||
from joy.vui import text_viewer
|
from joy.vui import text_viewer
|
||||||
|
|
||||||
|
|
||||||
class Display(object):
|
class Display(object):
|
||||||
'''
|
'''
|
||||||
Manage tracks and viewers on a screen (Pygame surface.)
|
Manage tracks and viewers on a screen (Pygame surface.)
|
||||||
|
|
||||||
The size and number of tracks are defined by passing in at least two
|
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
|
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
|
small one on the left and two larger ones of the same size, each four
|
||||||
times wider than the left one.
|
times wider than the left one.
|
||||||
|
|
||||||
All tracks take up the whole height of the display screen. Tracks
|
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
|
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
|
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
|
the last viewer in an overlay track is closed the track closes too
|
||||||
and reveals the hidden tracks (and their viewers, if any.)
|
and reveals the hidden tracks (and their viewers, if any.)
|
||||||
|
|
||||||
In order to facilitate command underlining while mouse dragging the
|
In order to facilitate command underlining while mouse dragging the
|
||||||
lookup parameter must be a function that accepts a string and returns
|
lookup parameter must be a function that accepts a string and returns
|
||||||
a Boolean indicating whether that string is a valid Joy function name.
|
a Boolean indicating whether that string is a valid Joy function name.
|
||||||
Typically you pass in the __contains__ method of the Joy dict. This
|
Typically you pass in the __contains__ method of the Joy dict. This
|
||||||
is a case of breaking "loose coupling" to gain efficiency, as otherwise
|
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
|
we would have to e.g. send some sort of lookup message to the
|
||||||
World context object, going through the whole Display.broadcast()
|
World context object, going through the whole Display.broadcast()
|
||||||
machinery, etc. Not something you want to do on each MOUSEMOTION
|
machinery, etc. Not something you want to do on each MOUSEMOTION
|
||||||
event.
|
event.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, screen, lookup, *track_ratios):
|
def __init__(self, screen, lookup, *track_ratios):
|
||||||
self.screen = screen
|
self.screen = screen
|
||||||
self.w, self.h = screen.get_width(), screen.get_height()
|
self.w, self.h = screen.get_width(), screen.get_height()
|
||||||
self.lookup = lookup
|
self.lookup = lookup
|
||||||
self.focused_viewer = None
|
self.focused_viewer = None
|
||||||
self.tracks = [] # (x, track)
|
self.tracks = [] # (x, track)
|
||||||
self.handlers = [] # Non-viewers that should receive messages.
|
self.handlers = [] # Non-viewers that should receive messages.
|
||||||
# Create the tracks.
|
# Create the tracks.
|
||||||
if not track_ratios: track_ratios = 1, 4
|
if not track_ratios: track_ratios = 1, 4
|
||||||
x, total = 0, sum(track_ratios)
|
x, total = 0, sum(track_ratios)
|
||||||
for ratio in track_ratios[:-1]:
|
for ratio in track_ratios[:-1]:
|
||||||
track_width = old_div(self.w * ratio, total)
|
track_width = old_div(self.w * ratio, total)
|
||||||
assert track_width >= 10 # minimum width 10 pixels
|
assert track_width >= 10 # minimum width 10 pixels
|
||||||
self._open_track(x, track_width)
|
self._open_track(x, track_width)
|
||||||
x += track_width
|
x += track_width
|
||||||
self._open_track(x, self.w - x)
|
self._open_track(x, self.w - x)
|
||||||
|
|
||||||
def _open_track(self, x, w):
|
def _open_track(self, x, w):
|
||||||
'''Helper function to create the pygame surface and Track.'''
|
'''Helper function to create the pygame surface and Track.'''
|
||||||
track_surface = self.screen.subsurface((x, 0, w, self.h))
|
track_surface = self.screen.subsurface((x, 0, w, self.h))
|
||||||
self.tracks.append((x, Track(track_surface)))
|
self.tracks.append((x, Track(track_surface)))
|
||||||
|
|
||||||
def open_viewer(self, x, y, class_):
|
def open_viewer(self, x, y, class_):
|
||||||
'''
|
'''
|
||||||
Open a viewer of class_ at the x, y location on the display,
|
Open a viewer of class_ at the x, y location on the display,
|
||||||
return the viewer.
|
return the viewer.
|
||||||
'''
|
'''
|
||||||
track = self._track_at(x)[0]
|
track = self._track_at(x)[0]
|
||||||
V = track.open_viewer(y, class_)
|
V = track.open_viewer(y, class_)
|
||||||
V.focus(self)
|
V.focus(self)
|
||||||
return V
|
return V
|
||||||
|
|
||||||
def close_viewer(self, viewer):
|
def close_viewer(self, viewer):
|
||||||
'''Close the viewer.'''
|
'''Close the viewer.'''
|
||||||
for x, track in self.tracks:
|
for x, track in self.tracks:
|
||||||
if track.close_viewer(viewer):
|
if track.close_viewer(viewer):
|
||||||
if not track.viewers and track.hiding:
|
if not track.viewers and track.hiding:
|
||||||
i = self.tracks.index((x, track))
|
i = self.tracks.index((x, track))
|
||||||
self.tracks[i:i + 1] = track.hiding
|
self.tracks[i:i + 1] = track.hiding
|
||||||
assert sorted(self.tracks) == self.tracks
|
assert sorted(self.tracks) == self.tracks
|
||||||
for _, exposed_track in track.hiding:
|
for _, exposed_track in track.hiding:
|
||||||
exposed_track.redraw()
|
exposed_track.redraw()
|
||||||
if viewer is self.focused_viewer:
|
if viewer is self.focused_viewer:
|
||||||
self.focused_viewer = None
|
self.focused_viewer = None
|
||||||
break
|
break
|
||||||
|
|
||||||
def change_viewer(self, viewer, y, relative=False):
|
def change_viewer(self, viewer, y, relative=False):
|
||||||
'''
|
'''
|
||||||
Adjust the top of the viewer to a new y within the boundaries of
|
Adjust the top of the viewer to a new y within the boundaries of
|
||||||
its neighbors.
|
its neighbors.
|
||||||
|
|
||||||
If relative is False new_y should be in screen coords, else new_y
|
If relative is False new_y should be in screen coords, else new_y
|
||||||
should be relative to the top of the viewer.
|
should be relative to the top of the viewer.
|
||||||
'''
|
'''
|
||||||
for _, track in self.tracks:
|
for _, track in self.tracks:
|
||||||
if track.change_viewer(viewer, y, relative):
|
if track.change_viewer(viewer, y, relative):
|
||||||
break
|
break
|
||||||
|
|
||||||
def grow_viewer(self, viewer):
|
def grow_viewer(self, viewer):
|
||||||
'''
|
'''
|
||||||
Cause the viewer to take up its whole track or, if it does
|
Cause the viewer to take up its whole track or, if it does
|
||||||
already, take up another track, up to the whole screen.
|
already, take up another track, up to the whole screen.
|
||||||
|
|
||||||
This is the inverse of closing a viewer. "Growing" a viewer
|
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
|
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
|
tracks and viewers are retained, and they get restored when the
|
||||||
covering track closes, which happens automatically when the last
|
covering track closes, which happens automatically when the last
|
||||||
viewer in the covering track is closed.
|
viewer in the covering track is closed.
|
||||||
'''
|
'''
|
||||||
for x, track in self.tracks:
|
for x, track in self.tracks:
|
||||||
for _, V in track.viewers:
|
for _, V in track.viewers:
|
||||||
if V is viewer:
|
if V is viewer:
|
||||||
return self._grow_viewer(x, track, viewer)
|
return self._grow_viewer(x, track, viewer)
|
||||||
|
|
||||||
def _grow_viewer(self, x, track, viewer):
|
def _grow_viewer(self, x, track, viewer):
|
||||||
'''Helper function to "grow" a viewer.'''
|
'''Helper function to "grow" a viewer.'''
|
||||||
new_viewer = None
|
new_viewer = None
|
||||||
|
|
||||||
if viewer.h < self.h:
|
if viewer.h < self.h:
|
||||||
# replace the track with a new track that contains
|
# replace the track with a new track that contains
|
||||||
# a copy of the viewer at full height.
|
# a copy of the viewer at full height.
|
||||||
new_track = Track(track.surface) # Reuse it, why not?
|
new_track = Track(track.surface) # Reuse it, why not?
|
||||||
new_viewer = copy(viewer)
|
new_viewer = copy(viewer)
|
||||||
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
|
new_track._grow_by(new_viewer, 0, self.h - viewer.h)
|
||||||
new_track.viewers.append((0, new_viewer))
|
new_track.viewers.append((0, new_viewer))
|
||||||
new_track.hiding = [(x, track)]
|
new_track.hiding = [(x, track)]
|
||||||
self.tracks[self.tracks.index((x, track))] = x, new_track
|
self.tracks[self.tracks.index((x, track))] = x, new_track
|
||||||
|
|
||||||
elif viewer.w < self.w:
|
elif viewer.w < self.w:
|
||||||
# replace two tracks
|
# replace two tracks
|
||||||
i = self.tracks.index((x, track))
|
i = self.tracks.index((x, track))
|
||||||
try: # prefer the one on the right
|
try: # prefer the one on the right
|
||||||
xx, xtrack = self.tracks[i + 1]
|
xx, xtrack = self.tracks[i + 1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
i -= 1 # okay, the one on the left
|
i -= 1 # okay, the one on the left
|
||||||
xx, xtrack = self.tracks[i]
|
xx, xtrack = self.tracks[i]
|
||||||
hiding = [(xx, xtrack), (x, track)]
|
hiding = [(xx, xtrack), (x, track)]
|
||||||
else:
|
else:
|
||||||
hiding = [(x, track), (xx, xtrack)]
|
hiding = [(x, track), (xx, xtrack)]
|
||||||
# We know there has to be at least one other track because it
|
# 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
|
# there weren't then that implies that the one track takes up
|
||||||
# the whole display screen (the only way you can get just one
|
# the whole display screen (the only way you can get just one
|
||||||
# track is by growing a viewer to cover the whole screen.)
|
# track is by growing a viewer to cover the whole screen.)
|
||||||
# Ergo, viewer.w == self.w, so this branch doesn't run.
|
# Ergo, viewer.w == self.w, so this branch doesn't run.
|
||||||
new_x = min(x, xx)
|
new_x = min(x, xx)
|
||||||
new_w = track.w + xtrack.w
|
new_w = track.w + xtrack.w
|
||||||
r = new_x, 0, new_w, self.h
|
r = new_x, 0, new_w, self.h
|
||||||
new_track = Track(self.screen.subsurface(r))
|
new_track = Track(self.screen.subsurface(r))
|
||||||
new_viewer = copy(viewer)
|
new_viewer = copy(viewer)
|
||||||
r = 0, 0, new_w, self.h
|
r = 0, 0, new_w, self.h
|
||||||
new_viewer.resurface(new_track.surface.subsurface(r))
|
new_viewer.resurface(new_track.surface.subsurface(r))
|
||||||
new_track.viewers.append((0, new_viewer))
|
new_track.viewers.append((0, new_viewer))
|
||||||
new_track.hiding = hiding
|
new_track.hiding = hiding
|
||||||
self.tracks[i:i + 2] = [(new_x, new_track)]
|
self.tracks[i:i + 2] = [(new_x, new_track)]
|
||||||
new_viewer.draw()
|
new_viewer.draw()
|
||||||
|
|
||||||
return new_viewer
|
return new_viewer
|
||||||
|
|
||||||
def _move_viewer(self, to, rel_y, viewer, _x, y):
|
def _move_viewer(self, to, rel_y, viewer, _x, y):
|
||||||
'''
|
'''
|
||||||
Helper function to move (really copy) a viewer to a new location.
|
Helper function to move (really copy) a viewer to a new location.
|
||||||
'''
|
'''
|
||||||
h = to.split(rel_y)
|
h = to.split(rel_y)
|
||||||
new_viewer = copy(viewer)
|
new_viewer = copy(viewer)
|
||||||
if not isinstance(to, Track):
|
if not isinstance(to, Track):
|
||||||
to = next(T for _, T in self.tracks
|
to = next(T for _, T in self.tracks
|
||||||
for _, V in T.viewers
|
for _, V in T.viewers
|
||||||
if V is to)
|
if V is to)
|
||||||
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
|
new_viewer.resurface(to.surface.subsurface((0, y, to.w, h)))
|
||||||
to.viewers.append((y, new_viewer))
|
to.viewers.append((y, new_viewer))
|
||||||
to.viewers.sort() # bisect.insort() would be overkill here.
|
to.viewers.sort() # bisect.insort() would be overkill here.
|
||||||
new_viewer.draw()
|
new_viewer.draw()
|
||||||
self.close_viewer(viewer)
|
self.close_viewer(viewer)
|
||||||
|
|
||||||
def _track_at(self, x):
|
def _track_at(self, x):
|
||||||
'''
|
'''
|
||||||
Return the track at x along with the track-relative x coordinate,
|
Return the track at x along with the track-relative x coordinate,
|
||||||
raise ValueError if x is off-screen.
|
raise ValueError if x is off-screen.
|
||||||
'''
|
'''
|
||||||
for track_x, track in self.tracks:
|
for track_x, track in self.tracks:
|
||||||
if x < track_x + track.w:
|
if x < track_x + track.w:
|
||||||
return track, x - track_x
|
return track, x - track_x
|
||||||
raise ValueError('x outside display: %r' % (x,))
|
raise ValueError('x outside display: %r' % (x,))
|
||||||
|
|
||||||
def at(self, x, y):
|
def at(self, x, y):
|
||||||
'''
|
'''
|
||||||
Return the viewer (which can be a Track) at the x, y location,
|
Return the viewer (which can be a Track) at the x, y location,
|
||||||
along with the relative-to-viewer-surface x and y coordinates.
|
along with the relative-to-viewer-surface x and y coordinates.
|
||||||
If there is no viewer at the location the Track will be returned
|
If there is no viewer at the location the Track will be returned
|
||||||
instead.
|
instead.
|
||||||
'''
|
'''
|
||||||
track, x = self._track_at(x)
|
track, x = self._track_at(x)
|
||||||
viewer, y = track.viewer_at(y)
|
viewer, y = track.viewer_at(y)
|
||||||
return viewer, x, y
|
return viewer, x, y
|
||||||
|
|
||||||
def iter_viewers(self):
|
def iter_viewers(self):
|
||||||
'''
|
'''
|
||||||
Iterate through all viewers yielding (viewer, x, y) three-tuples.
|
Iterate through all viewers yielding (viewer, x, y) three-tuples.
|
||||||
The x and y coordinates are screen pixels of the top-left corner
|
The x and y coordinates are screen pixels of the top-left corner
|
||||||
of the viewer.
|
of the viewer.
|
||||||
'''
|
'''
|
||||||
for x, T in self.tracks:
|
for x, T in self.tracks:
|
||||||
for y, V in T.viewers:
|
for y, V in T.viewers:
|
||||||
yield V, x, y
|
yield V, x, y
|
||||||
|
|
||||||
def done_resizing(self):
|
def done_resizing(self):
|
||||||
'''
|
'''
|
||||||
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
|
Helper method called directly by ``MenuViewer.mouse_up()`` to (hackily)
|
||||||
update the display when done resizing a viewer.
|
update the display when done resizing a viewer.
|
||||||
'''
|
'''
|
||||||
for _, track in self.tracks: # This should be done by a Message?
|
for _, track in self.tracks: # This should be done by a Message?
|
||||||
if track.resizing_viewer:
|
if track.resizing_viewer:
|
||||||
track.resizing_viewer.draw()
|
track.resizing_viewer.draw()
|
||||||
track.resizing_viewer = None
|
track.resizing_viewer = None
|
||||||
break
|
break
|
||||||
|
|
||||||
def broadcast(self, message):
|
def broadcast(self, message):
|
||||||
'''
|
'''
|
||||||
Broadcast a message to all viewers (except the sender) and all
|
Broadcast a message to all viewers (except the sender) and all
|
||||||
registered handlers.
|
registered handlers.
|
||||||
'''
|
'''
|
||||||
for _, track in self.tracks:
|
for _, track in self.tracks:
|
||||||
track.broadcast(message)
|
track.broadcast(message)
|
||||||
for handler in self.handlers:
|
for handler in self.handlers:
|
||||||
handler(message)
|
handler(message)
|
||||||
|
|
||||||
def redraw(self):
|
def redraw(self):
|
||||||
'''
|
'''
|
||||||
Redraw all tracks (which will redraw all viewers.)
|
Redraw all tracks (which will redraw all viewers.)
|
||||||
'''
|
'''
|
||||||
for _, track in self.tracks:
|
for _, track in self.tracks:
|
||||||
track.redraw()
|
track.redraw()
|
||||||
|
|
||||||
def focus(self, viewer):
|
def focus(self, viewer):
|
||||||
'''
|
'''
|
||||||
Set system focus to a given viewer (or no viewer if a track.)
|
Set system focus to a given viewer (or no viewer if a track.)
|
||||||
'''
|
'''
|
||||||
if isinstance(viewer, Track):
|
if isinstance(viewer, Track):
|
||||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||||
self.focused_viewer = None
|
self.focused_viewer = None
|
||||||
elif viewer is not self.focused_viewer:
|
elif viewer is not self.focused_viewer:
|
||||||
if self.focused_viewer: self.focused_viewer.unfocus()
|
if self.focused_viewer: self.focused_viewer.unfocus()
|
||||||
self.focused_viewer = viewer
|
self.focused_viewer = viewer
|
||||||
viewer.focus(self)
|
viewer.focus(self)
|
||||||
|
|
||||||
def dispatch_event(self, event):
|
def dispatch_event(self, event):
|
||||||
'''
|
'''
|
||||||
Display event handling.
|
Display event handling.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
|
if event.type in {pygame.KEYUP, pygame.KEYDOWN}:
|
||||||
self._keyboard_event(event)
|
self._keyboard_event(event)
|
||||||
elif event.type in MOUSE_EVENTS:
|
elif event.type in MOUSE_EVENTS:
|
||||||
self._mouse_event(event)
|
self._mouse_event(event)
|
||||||
else:
|
else:
|
||||||
print((
|
print((
|
||||||
'received event %s Use pygame.event.set_allowed().'
|
'received event %s Use pygame.event.set_allowed().'
|
||||||
% pygame.event.event_name(event.type)
|
% pygame.event.event_name(event.type)
|
||||||
), file=stderr)
|
), file=stderr)
|
||||||
# Catch all exceptions and open a viewer.
|
# Catch all exceptions and open a viewer.
|
||||||
except:
|
except:
|
||||||
err = format_exc()
|
err = format_exc()
|
||||||
print(err, file=stderr) # To be safe just print it right away.
|
print(err, file=stderr) # To be safe just print it right away.
|
||||||
open_viewer_on_string(self, err, self.broadcast)
|
open_viewer_on_string(self, err, self.broadcast)
|
||||||
|
|
||||||
def _keyboard_event(self, event):
|
def _keyboard_event(self, event):
|
||||||
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
|
if event.key == pygame.K_PAUSE and event.type == pygame.KEYUP:
|
||||||
# At least on my keyboard the break/pause key sends K_PAUSE.
|
# At least on my keyboard the break/pause key sends K_PAUSE.
|
||||||
# The main use of this is to open a TextViewer if you
|
# The main use of this is to open a TextViewer if you
|
||||||
# accidentally close all the viewers, so you can recover.
|
# accidentally close all the viewers, so you can recover.
|
||||||
raise KeyboardInterrupt('break')
|
raise KeyboardInterrupt('break')
|
||||||
if not self.focused_viewer:
|
if not self.focused_viewer:
|
||||||
return
|
return
|
||||||
if event.type == pygame.KEYUP:
|
if event.type == pygame.KEYUP:
|
||||||
self.focused_viewer.key_up(self, event.key, event.mod)
|
self.focused_viewer.key_up(self, event.key, event.mod)
|
||||||
elif event.type == pygame.KEYDOWN:
|
elif event.type == pygame.KEYDOWN:
|
||||||
self.focused_viewer.key_down(
|
self.focused_viewer.key_down(
|
||||||
self, event.unicode, event.key, event.mod)
|
self, event.unicode, event.key, event.mod)
|
||||||
# This is not UnicodeType. TODO does this need to be fixed?
|
# This is not UnicodeType. TODO does this need to be fixed?
|
||||||
# self, event.str, event.key, event.mod)
|
# self, event.str, event.key, event.mod)
|
||||||
|
|
||||||
def _mouse_event(self, event):
|
def _mouse_event(self, event):
|
||||||
V, x, y = self.at(*event.pos)
|
V, x, y = self.at(*event.pos)
|
||||||
|
|
||||||
if event.type == pygame.MOUSEMOTION:
|
if event.type == pygame.MOUSEMOTION:
|
||||||
if not isinstance(V, Track):
|
if not isinstance(V, Track):
|
||||||
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
|
V.mouse_motion(self, x, y, *(event.rel + event.buttons))
|
||||||
|
|
||||||
elif event.type == pygame.MOUSEBUTTONDOWN:
|
elif event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
if event.button == 1:
|
if event.button == 1:
|
||||||
self.focus(V)
|
self.focus(V)
|
||||||
V.mouse_down(self, x, y, event.button)
|
V.mouse_down(self, x, y, event.button)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
assert event.type == pygame.MOUSEBUTTONUP
|
assert event.type == pygame.MOUSEBUTTONUP
|
||||||
|
|
||||||
# Check for moving viewer.
|
# Check for moving viewer.
|
||||||
if (event.button == 2
|
if (event.button == 2
|
||||||
and self.focused_viewer
|
and self.focused_viewer
|
||||||
and V is not self.focused_viewer
|
and V is not self.focused_viewer
|
||||||
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
|
and V.MINIMUM_HEIGHT < y < V.h - self.focused_viewer.MINIMUM_HEIGHT
|
||||||
):
|
):
|
||||||
self._move_viewer(V, y, self.focused_viewer, *event.pos)
|
self._move_viewer(V, y, self.focused_viewer, *event.pos)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
V.mouse_up(self, x, y, event.button)
|
V.mouse_up(self, x, y, event.button)
|
||||||
|
|
||||||
def init_text(self, pt, x, y, filename):
|
def init_text(self, pt, x, y, filename):
|
||||||
'''
|
'''
|
||||||
Open and return a ``TextViewer`` on a given file (which must be present
|
Open and return a ``TextViewer`` on a given file (which must be present
|
||||||
in the ``JOYHOME`` directory.)
|
in the ``JOYHOME`` directory.)
|
||||||
'''
|
'''
|
||||||
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
|
viewer = self.open_viewer(x, y, text_viewer.TextViewer)
|
||||||
viewer.content_id, viewer.lines = pt.open(filename)
|
viewer.content_id, viewer.lines = pt.open(filename)
|
||||||
viewer.draw()
|
viewer.draw()
|
||||||
return viewer
|
return viewer
|
||||||
|
|
||||||
|
|
||||||
class Track(Viewer):
|
class Track(Viewer):
|
||||||
'''
|
'''
|
||||||
Manage a vertical strip of the display, and the viewers on it.
|
Manage a vertical strip of the display, and the viewers on it.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, surface):
|
def __init__(self, surface):
|
||||||
Viewer.__init__(self, surface)
|
Viewer.__init__(self, surface)
|
||||||
self.viewers = [] # (y, viewer)
|
self.viewers = [] # (y, viewer)
|
||||||
self.hiding = None
|
self.hiding = None
|
||||||
self.resizing_viewer = None
|
self.resizing_viewer = None
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def split(self, y):
|
def split(self, y):
|
||||||
'''
|
'''
|
||||||
Split the Track at the y coordinate and return the height
|
Split the Track at the y coordinate and return the height
|
||||||
available for a new viewer. Tracks manage a vertical strip of
|
available for a new viewer. Tracks manage a vertical strip of
|
||||||
the display screen so they don't resize their surface when split.
|
the display screen so they don't resize their surface when split.
|
||||||
'''
|
'''
|
||||||
h = self.viewers[0][0] if self.viewers else self.h
|
h = self.viewers[0][0] if self.viewers else self.h
|
||||||
assert h > y
|
assert h > y
|
||||||
return h - y
|
return h - y
|
||||||
|
|
||||||
def draw(self, rect=None):
|
def draw(self, rect=None):
|
||||||
'''Draw the track onto its surface, clearing all content.
|
'''Draw the track onto its surface, clearing all content.
|
||||||
|
|
||||||
If rect is passed only draw to that area. This supports e.g.
|
If rect is passed only draw to that area. This supports e.g.
|
||||||
closing a viewer that then exposes part of the track.
|
closing a viewer that then exposes part of the track.
|
||||||
'''
|
'''
|
||||||
self.surface.fill(GREY, rect=rect)
|
self.surface.fill(GREY, rect=rect)
|
||||||
|
|
||||||
def viewer_at(self, y):
|
def viewer_at(self, y):
|
||||||
'''
|
'''
|
||||||
Return the viewer at y along with the viewer-relative y coordinate,
|
Return the viewer at y along with the viewer-relative y coordinate,
|
||||||
if there's no viewer at y return this track and y.
|
if there's no viewer at y return this track and y.
|
||||||
'''
|
'''
|
||||||
for viewer_y, viewer in self.viewers:
|
for viewer_y, viewer in self.viewers:
|
||||||
if viewer_y < y <= viewer_y + viewer.h:
|
if viewer_y < y <= viewer_y + viewer.h:
|
||||||
return viewer, y - viewer_y
|
return viewer, y - viewer_y
|
||||||
return self, y
|
return self, y
|
||||||
|
|
||||||
def open_viewer(self, y, class_):
|
def open_viewer(self, y, class_):
|
||||||
'''Open and return a viewer of class at y.'''
|
'''Open and return a viewer of class at y.'''
|
||||||
# Todo: if y coincides with some other viewer's y replace it.
|
# Todo: if y coincides with some other viewer's y replace it.
|
||||||
viewer, viewer_y = self.viewer_at(y)
|
viewer, viewer_y = self.viewer_at(y)
|
||||||
h = viewer.split(viewer_y)
|
h = viewer.split(viewer_y)
|
||||||
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
|
new_viewer = class_(self.surface.subsurface((0, y, self.w, h)))
|
||||||
new_viewer.draw()
|
new_viewer.draw()
|
||||||
self.viewers.append((y, new_viewer))
|
self.viewers.append((y, new_viewer))
|
||||||
self.viewers.sort() # Could use bisect module but how many
|
self.viewers.sort() # Could use bisect module but how many
|
||||||
# viewers will you ever have?
|
# viewers will you ever have?
|
||||||
return new_viewer
|
return new_viewer
|
||||||
|
|
||||||
def close_viewer(self, viewer):
|
def close_viewer(self, viewer):
|
||||||
'''Close the viewer, reuse the freed space.'''
|
'''Close the viewer, reuse the freed space.'''
|
||||||
for y, V in self.viewers:
|
for y, V in self.viewers:
|
||||||
if V is viewer:
|
if V is viewer:
|
||||||
self._close_viewer(y, V)
|
self._close_viewer(y, V)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _close_viewer(self, y, viewer):
|
def _close_viewer(self, y, viewer):
|
||||||
'''Helper function to do the actual closing.'''
|
'''Helper function to do the actual closing.'''
|
||||||
i = self.viewers.index((y, viewer))
|
i = self.viewers.index((y, viewer))
|
||||||
del self.viewers[i]
|
del self.viewers[i]
|
||||||
if i: # The previous viewer gets the space.
|
if i: # The previous viewer gets the space.
|
||||||
previous_y, previous_viewer = self.viewers[i - 1]
|
previous_y, previous_viewer = self.viewers[i - 1]
|
||||||
self._grow_by(previous_viewer, previous_y, viewer.h)
|
self._grow_by(previous_viewer, previous_y, viewer.h)
|
||||||
else: # This track gets the space.
|
else: # This track gets the space.
|
||||||
self.draw((0, y, self.w, viewer.surface.get_height()))
|
self.draw((0, y, self.w, viewer.surface.get_height()))
|
||||||
viewer.close()
|
viewer.close()
|
||||||
|
|
||||||
def _grow_by(self, viewer, y, h):
|
def _grow_by(self, viewer, y, h):
|
||||||
'''Grow a viewer (located at y) by height h.
|
'''Grow a viewer (located at y) by height h.
|
||||||
|
|
||||||
This might seem like it should be a method of the viewer, but
|
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 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
|
the parent track's surface (to make a new subsurface) so it has
|
||||||
to be a method of the track, which has both.
|
to be a method of the track, which has both.
|
||||||
'''
|
'''
|
||||||
h = viewer.surface.get_height() + h
|
h = viewer.surface.get_height() + h
|
||||||
try:
|
try:
|
||||||
surface = self.surface.subsurface((0, y, self.w, h))
|
surface = self.surface.subsurface((0, y, self.w, h))
|
||||||
except ValueError: # subsurface rectangle outside surface area
|
except ValueError: # subsurface rectangle outside surface area
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
viewer.resurface(surface)
|
viewer.resurface(surface)
|
||||||
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
|
if h <= viewer.last_touch[1]: viewer.last_touch = 0, 0
|
||||||
viewer.draw()
|
viewer.draw()
|
||||||
|
|
||||||
def change_viewer(self, viewer, new_y, relative=False):
|
def change_viewer(self, viewer, new_y, relative=False):
|
||||||
'''
|
'''
|
||||||
Adjust the top of the viewer to a new y within the boundaries of
|
Adjust the top of the viewer to a new y within the boundaries of
|
||||||
its neighbors.
|
its neighbors.
|
||||||
|
|
||||||
If relative is False new_y should be in screen coords, else new_y
|
If relative is False new_y should be in screen coords, else new_y
|
||||||
should be relative to the top of the viewer.
|
should be relative to the top of the viewer.
|
||||||
'''
|
'''
|
||||||
for old_y, V in self.viewers:
|
for old_y, V in self.viewers:
|
||||||
if V is viewer:
|
if V is viewer:
|
||||||
if relative: new_y += old_y
|
if relative: new_y += old_y
|
||||||
if new_y != old_y: self._change_viewer(new_y, old_y, V)
|
if new_y != old_y: self._change_viewer(new_y, old_y, V)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _change_viewer(self, new_y, old_y, viewer):
|
def _change_viewer(self, new_y, old_y, viewer):
|
||||||
new_y = max(0, min(self.h, new_y))
|
new_y = max(0, min(self.h, new_y))
|
||||||
i = self.viewers.index((old_y, viewer))
|
i = self.viewers.index((old_y, viewer))
|
||||||
if new_y < old_y: # Enlarge self, shrink upper neighbor.
|
if new_y < old_y: # Enlarge self, shrink upper neighbor.
|
||||||
if i:
|
if i:
|
||||||
previous_y, previous_viewer = self.viewers[i - 1]
|
previous_y, previous_viewer = self.viewers[i - 1]
|
||||||
if new_y - previous_y < self.MINIMUM_HEIGHT:
|
if new_y - previous_y < self.MINIMUM_HEIGHT:
|
||||||
return
|
return
|
||||||
previous_viewer.resizing = 1
|
previous_viewer.resizing = 1
|
||||||
h = previous_viewer.split(new_y - previous_y)
|
h = previous_viewer.split(new_y - previous_y)
|
||||||
previous_viewer.resizing = 0
|
previous_viewer.resizing = 0
|
||||||
self.resizing_viewer = previous_viewer
|
self.resizing_viewer = previous_viewer
|
||||||
else:
|
else:
|
||||||
h = old_y - new_y
|
h = old_y - new_y
|
||||||
self._grow_by(viewer, new_y, h)
|
self._grow_by(viewer, new_y, h)
|
||||||
|
|
||||||
else: # Shink self, enlarge upper neighbor.
|
else: # Shink self, enlarge upper neighbor.
|
||||||
# Enforce invariant.
|
# Enforce invariant.
|
||||||
try:
|
try:
|
||||||
h, _ = self.viewers[i + 1]
|
h, _ = self.viewers[i + 1]
|
||||||
except IndexError: # No next viewer.
|
except IndexError: # No next viewer.
|
||||||
h = self.h
|
h = self.h
|
||||||
if h - new_y < self.MINIMUM_HEIGHT:
|
if h - new_y < self.MINIMUM_HEIGHT:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Change the viewer and adjust the upper viewer or track.
|
# Change the viewer and adjust the upper viewer or track.
|
||||||
h = new_y - old_y
|
h = new_y - old_y
|
||||||
self._grow_by(viewer, new_y, -h) # grow by negative height!
|
self._grow_by(viewer, new_y, -h) # grow by negative height!
|
||||||
if i:
|
if i:
|
||||||
previous_y, previous_viewer = self.viewers[i - 1]
|
previous_y, previous_viewer = self.viewers[i - 1]
|
||||||
previous_viewer.resizing = 1
|
previous_viewer.resizing = 1
|
||||||
self._grow_by(previous_viewer, previous_y, h)
|
self._grow_by(previous_viewer, previous_y, h)
|
||||||
previous_viewer.resizing = 0
|
previous_viewer.resizing = 0
|
||||||
self.resizing_viewer = previous_viewer
|
self.resizing_viewer = previous_viewer
|
||||||
else:
|
else:
|
||||||
self.draw((0, old_y, self.w, h))
|
self.draw((0, old_y, self.w, h))
|
||||||
|
|
||||||
self.viewers[i] = new_y, viewer
|
self.viewers[i] = new_y, viewer
|
||||||
# self.viewers.sort() # Not necessary, invariant holds.
|
# self.viewers.sort() # Not necessary, invariant holds.
|
||||||
assert sorted(self.viewers) == self.viewers
|
assert sorted(self.viewers) == self.viewers
|
||||||
|
|
||||||
def broadcast(self, message):
|
def broadcast(self, message):
|
||||||
'''
|
'''
|
||||||
Broadcast a message to all viewers on this track (except the sender.)
|
Broadcast a message to all viewers on this track (except the sender.)
|
||||||
'''
|
'''
|
||||||
for _, viewer in self.viewers:
|
for _, viewer in self.viewers:
|
||||||
if viewer is not message.sender:
|
if viewer is not message.sender:
|
||||||
viewer.handle(message)
|
viewer.handle(message)
|
||||||
|
|
||||||
def redraw(self):
|
def redraw(self):
|
||||||
'''Redraw the track and all of its viewers.'''
|
'''Redraw the track and all of its viewers.'''
|
||||||
self.draw()
|
self.draw()
|
||||||
for _, viewer in self.viewers:
|
for _, viewer in self.viewers:
|
||||||
viewer.draw()
|
viewer.draw()
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ import base64, zlib
|
||||||
|
|
||||||
|
|
||||||
def create(fn='Iosevka12.BMP'):
|
def create(fn='Iosevka12.BMP'):
|
||||||
with open(fn, 'rb') as f:
|
with open(fn, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
return base64.encodestring(zlib.compress(data))
|
return base64.encodestring(zlib.compress(data))
|
||||||
|
|
||||||
|
|
||||||
data = StringIO(zlib.decompress(base64.decodestring('''\
|
data = StringIO(zlib.decompress(base64.decodestring('''\
|
||||||
|
|
@ -186,4 +186,4 @@ lnalXc/9SsNb2vUirzS8pV0v8gJv/w/2vRht''')))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(create())
|
print(create())
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ JOY_HOME directory.
|
||||||
These contents are kept in this Python module as a base64-encoded zip
|
These contents are kept in this Python module as a base64-encoded zip
|
||||||
file, so you can just do, e.g.:
|
file, so you can just do, e.g.:
|
||||||
|
|
||||||
import init_joy_home
|
import init_joy_home
|
||||||
init_joy_home.initialize(JOY_HOME)
|
init_joy_home.initialize(JOY_HOME)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
@ -35,17 +35,17 @@ import base64, os, io, zipfile
|
||||||
|
|
||||||
|
|
||||||
def initialize(joy_home):
|
def initialize(joy_home):
|
||||||
Z.extractall(joy_home)
|
Z.extractall(joy_home)
|
||||||
|
|
||||||
|
|
||||||
def create_data(from_dir='./default_joy_home'):
|
def create_data(from_dir='./default_joy_home'):
|
||||||
f = io.StringIO()
|
f = io.StringIO()
|
||||||
z = zipfile.ZipFile(f, mode='w')
|
z = zipfile.ZipFile(f, mode='w')
|
||||||
for fn in os.listdir(from_dir):
|
for fn in os.listdir(from_dir):
|
||||||
from_fn = os.path.join(from_dir, fn)
|
from_fn = os.path.join(from_dir, fn)
|
||||||
z.write(from_fn, fn)
|
z.write(from_fn, fn)
|
||||||
z.close()
|
z.close()
|
||||||
return base64.encodestring(f.getvalue())
|
return base64.encodestring(f.getvalue())
|
||||||
|
|
||||||
|
|
||||||
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
Z = zipfile.ZipFile(io.StringIO(base64.decodestring('''\
|
||||||
|
|
@ -275,4 +275,4 @@ c3RhY2sucGlja2xlUEsFBgAAAAAGAAYAUwEAACcwAAAAAA==''')))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print(create_data())
|
print(create_data())
|
||||||
|
|
|
||||||
206
joy/vui/main.py
206
joy/vui/main.py
|
|
@ -41,139 +41,139 @@ FULLSCREEN = '-f' in sys.argv
|
||||||
|
|
||||||
JOY_HOME = os.environ.get('JOY_HOME')
|
JOY_HOME = os.environ.get('JOY_HOME')
|
||||||
if JOY_HOME is None:
|
if JOY_HOME is None:
|
||||||
JOY_HOME = os.path.expanduser('~/.thun')
|
JOY_HOME = os.path.expanduser('~/.thun')
|
||||||
if not os.path.isabs(JOY_HOME):
|
if not os.path.isabs(JOY_HOME):
|
||||||
raise ValueError('what directory?')
|
raise ValueError('what directory?')
|
||||||
|
|
||||||
|
|
||||||
def load_definitions(pt, dictionary):
|
def load_definitions(pt, dictionary):
|
||||||
'''Load definitions from ``definitions.txt``.'''
|
'''Load definitions from ``definitions.txt``.'''
|
||||||
lines = pt.open('definitions.txt')[1]
|
lines = pt.open('definitions.txt')[1]
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if '==' in line:
|
if '==' in line:
|
||||||
DefinitionWrapper.add_def(line, dictionary)
|
DefinitionWrapper.add_def(line, dictionary)
|
||||||
|
|
||||||
|
|
||||||
def load_primitives(home, name_space):
|
def load_primitives(home, name_space):
|
||||||
'''Load primitives from ``library.py``.'''
|
'''Load primitives from ``library.py``.'''
|
||||||
fn = os.path.join(home, 'library.py')
|
fn = os.path.join(home, 'library.py')
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
execfile(fn, name_space)
|
execfile(fn, name_space)
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
'''
|
'''
|
||||||
Initialize the system.
|
Initialize the system.
|
||||||
|
|
||||||
* Init PyGame
|
* Init PyGame
|
||||||
* Create main window
|
* Create main window
|
||||||
* Start the PyGame clock
|
* Start the PyGame clock
|
||||||
* Set the event mask
|
* Set the event mask
|
||||||
* Create the PersistTask
|
* Create the PersistTask
|
||||||
|
|
||||||
'''
|
'''
|
||||||
print('Initializing Pygame...')
|
print('Initializing Pygame...')
|
||||||
pygame.init()
|
pygame.init()
|
||||||
print('Creating window...')
|
print('Creating window...')
|
||||||
if FULLSCREEN:
|
if FULLSCREEN:
|
||||||
screen = pygame.display.set_mode()
|
screen = pygame.display.set_mode()
|
||||||
else:
|
else:
|
||||||
screen = pygame.display.set_mode((1024, 768))
|
screen = pygame.display.set_mode((1024, 768))
|
||||||
clock = pygame.time.Clock()
|
clock = pygame.time.Clock()
|
||||||
pygame.event.set_allowed(None)
|
pygame.event.set_allowed(None)
|
||||||
pygame.event.set_allowed(core.ALLOWED_EVENTS)
|
pygame.event.set_allowed(core.ALLOWED_EVENTS)
|
||||||
pt = persist_task.PersistTask(JOY_HOME)
|
pt = persist_task.PersistTask(JOY_HOME)
|
||||||
return screen, clock, pt
|
return screen, clock, pt
|
||||||
|
|
||||||
|
|
||||||
def init_context(screen, clock, pt):
|
def init_context(screen, clock, pt):
|
||||||
'''
|
'''
|
||||||
More initialization
|
More initialization
|
||||||
|
|
||||||
* Create the Joy dictionary
|
* Create the Joy dictionary
|
||||||
* Create the Display
|
* Create the Display
|
||||||
* Open the log, menu, and scratch text viewers, and the stack pickle
|
* Open the log, menu, and scratch text viewers, and the stack pickle
|
||||||
* Start the main loop
|
* Start the main loop
|
||||||
* Create the World object
|
* Create the World object
|
||||||
* Register PersistTask and World message handlers with the Display
|
* Register PersistTask and World message handlers with the Display
|
||||||
* Load user function definitions.
|
* Load user function definitions.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
D = initialize()
|
D = initialize()
|
||||||
d = display.Display(
|
d = display.Display(
|
||||||
screen,
|
screen,
|
||||||
D.__contains__,
|
D.__contains__,
|
||||||
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
|
*((144 - 89, 144, 89) if FULLSCREEN else (89, 144))
|
||||||
)
|
)
|
||||||
log = d.init_text(pt, 0, 0, 'log.txt')
|
log = d.init_text(pt, 0, 0, 'log.txt')
|
||||||
tho = d.init_text(pt, 0, old_div(d.h, 3), 'menu.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')
|
t = d.init_text(pt, old_div(d.w, 2), 0, 'scratch.txt')
|
||||||
loop = core.TheLoop(d, clock)
|
loop = core.TheLoop(d, clock)
|
||||||
stack_id, stack_holder = pt.open('stack.pickle')
|
stack_id, stack_holder = pt.open('stack.pickle')
|
||||||
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
|
world = core.World(stack_id, stack_holder, D, d.broadcast, log)
|
||||||
loop.install_task(pt.task_run, 10000) # save files every ten seconds
|
loop.install_task(pt.task_run, 10000) # save files every ten seconds
|
||||||
d.handlers.append(pt.handle)
|
d.handlers.append(pt.handle)
|
||||||
d.handlers.append(world.handle)
|
d.handlers.append(world.handle)
|
||||||
load_definitions(pt, D)
|
load_definitions(pt, D)
|
||||||
return locals()
|
return locals()
|
||||||
|
|
||||||
|
|
||||||
def error_guard(loop, n=10):
|
def error_guard(loop, n=10):
|
||||||
'''
|
'''
|
||||||
Run a loop function, retry for ``n`` exceptions.
|
Run a loop function, retry for ``n`` exceptions.
|
||||||
Prints tracebacks on ``sys.stderr``.
|
Prints tracebacks on ``sys.stderr``.
|
||||||
'''
|
'''
|
||||||
error_count = 0
|
error_count = 0
|
||||||
while error_count < n:
|
while error_count < n:
|
||||||
try:
|
try:
|
||||||
loop()
|
loop()
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
error_count += 1
|
error_count += 1
|
||||||
|
|
||||||
|
|
||||||
class FileFaker(object):
|
class FileFaker(object):
|
||||||
'''Pretends to be a file object but writes to log instead.'''
|
'''Pretends to be a file object but writes to log instead.'''
|
||||||
|
|
||||||
def __init__(self, log):
|
def __init__(self, log):
|
||||||
self.log = log
|
self.log = log
|
||||||
|
|
||||||
def write(self, text):
|
def write(self, text):
|
||||||
'''Write text to log.'''
|
'''Write text to log.'''
|
||||||
self.log.append(text)
|
self.log.append(text)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main(screen, clock, pt):
|
def main(screen, clock, pt):
|
||||||
'''
|
'''
|
||||||
Main function.
|
Main function.
|
||||||
|
|
||||||
* Call ``init_context()``
|
* Call ``init_context()``
|
||||||
* Load primitives
|
* Load primitives
|
||||||
* Create an ``evaluate`` function that lets you just eval some Python code
|
* Create an ``evaluate`` function that lets you just eval some Python code
|
||||||
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
|
* Redirect ``stdout`` to the log using a ``FileFaker`` object, and...
|
||||||
* Start the main loop.
|
* Start the main loop.
|
||||||
'''
|
'''
|
||||||
name_space = init_context(screen, clock, pt)
|
name_space = init_context(screen, clock, pt)
|
||||||
load_primitives(pt.home, name_space.copy())
|
load_primitives(pt.home, name_space.copy())
|
||||||
|
|
||||||
@SimpleFunctionWrapper
|
@SimpleFunctionWrapper
|
||||||
def evaluate(stack):
|
def evaluate(stack):
|
||||||
'''Evaluate the Python code text on the top of the stack.'''
|
'''Evaluate the Python code text on the top of the stack.'''
|
||||||
code, stack = stack
|
code, stack = stack
|
||||||
exec(code, name_space.copy())
|
exec(code, name_space.copy())
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
name_space['D']['evaluate'] = evaluate
|
name_space['D']['evaluate'] = evaluate
|
||||||
|
|
||||||
|
|
||||||
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
|
sys.stdout, old_stdout = FileFaker(name_space['log']), sys.stdout
|
||||||
try:
|
try:
|
||||||
error_guard(name_space['loop'].loop)
|
error_guard(name_space['loop'].loop)
|
||||||
finally:
|
finally:
|
||||||
sys.stdout = old_stdout
|
sys.stdout = old_stdout
|
||||||
|
|
||||||
return name_space['d']
|
return name_space['d']
|
||||||
|
|
|
||||||
|
|
@ -36,239 +36,239 @@ from joy.vui import core, init_joy_home
|
||||||
|
|
||||||
|
|
||||||
def open_repo(repo_dir=None, initialize=False):
|
def open_repo(repo_dir=None, initialize=False):
|
||||||
'''
|
'''
|
||||||
Open, or create, and return a Dulwich git repo object for the given
|
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
|
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``
|
does exist but isn't a repo the result depends on the ``initialize``
|
||||||
argument. If it is ``False`` (the default) a ``NotGitRepository``
|
argument. If it is ``False`` (the default) a ``NotGitRepository``
|
||||||
exception is raised, otherwise ``git init`` is effected in the dir.
|
exception is raised, otherwise ``git init`` is effected in the dir.
|
||||||
'''
|
'''
|
||||||
if not os.path.exists(repo_dir):
|
if not os.path.exists(repo_dir):
|
||||||
os.makedirs(repo_dir, 0o700)
|
os.makedirs(repo_dir, 0o700)
|
||||||
return init_repo(repo_dir)
|
return init_repo(repo_dir)
|
||||||
try:
|
try:
|
||||||
return Repo(repo_dir)
|
return Repo(repo_dir)
|
||||||
except NotGitRepository:
|
except NotGitRepository:
|
||||||
if initialize:
|
if initialize:
|
||||||
return init_repo(repo_dir)
|
return init_repo(repo_dir)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def init_repo(repo_dir):
|
def init_repo(repo_dir):
|
||||||
'''
|
'''
|
||||||
Initialize a git repository in the directory. Stage and commit all
|
Initialize a git repository in the directory. Stage and commit all
|
||||||
files (toplevel, not those in subdirectories if any) in the dir.
|
files (toplevel, not those in subdirectories if any) in the dir.
|
||||||
'''
|
'''
|
||||||
repo = Repo.init(repo_dir)
|
repo = Repo.init(repo_dir)
|
||||||
init_joy_home.initialize(repo_dir)
|
init_joy_home.initialize(repo_dir)
|
||||||
repo.stage([
|
repo.stage([
|
||||||
fn
|
fn
|
||||||
for fn in os.listdir(repo_dir)
|
for fn in os.listdir(repo_dir)
|
||||||
if os.path.isfile(os.path.join(repo_dir, fn))
|
if os.path.isfile(os.path.join(repo_dir, fn))
|
||||||
])
|
])
|
||||||
repo.do_commit('Initial commit.', committer=core.COMMITTER)
|
repo.do_commit('Initial commit.', committer=core.COMMITTER)
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
|
||||||
def make_repo_relative_path_maker(repo):
|
def make_repo_relative_path_maker(repo):
|
||||||
'''
|
'''
|
||||||
Helper function to return a function that returns a path given a path,
|
Helper function to return a function that returns a path given a path,
|
||||||
that's relative to the repository.
|
that's relative to the repository.
|
||||||
'''
|
'''
|
||||||
c = repo.controldir()
|
c = repo.controldir()
|
||||||
def repo_relative_path(path):
|
def repo_relative_path(path):
|
||||||
return os.path.relpath(path, os.path.commonprefix((c, path)))
|
return os.path.relpath(path, os.path.commonprefix((c, path)))
|
||||||
return repo_relative_path
|
return repo_relative_path
|
||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
class Resource(object):
|
||||||
'''
|
'''
|
||||||
Handle the content of a text files as a list of lines, deal with
|
Handle the content of a text files as a list of lines, deal with
|
||||||
saving it and staging the changes to a repo.
|
saving it and staging the changes to a repo.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, filename, repo_relative_filename, thing=None):
|
def __init__(self, filename, repo_relative_filename, thing=None):
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.repo_relative_filename = repo_relative_filename
|
self.repo_relative_filename = repo_relative_filename
|
||||||
self.thing = thing or self._from_file(open(filename))
|
self.thing = thing or self._from_file(open(filename))
|
||||||
|
|
||||||
def _from_file(self, f):
|
def _from_file(self, f):
|
||||||
return f.read().splitlines()
|
return f.read().splitlines()
|
||||||
|
|
||||||
def _to_file(self, f):
|
def _to_file(self, f):
|
||||||
for line in self.thing:
|
for line in self.thing:
|
||||||
print(line, file=f)
|
print(line, file=f)
|
||||||
|
|
||||||
def persist(self, repo):
|
def persist(self, repo):
|
||||||
'''
|
'''
|
||||||
Save the lines to the file and stage the file in the repo.
|
Save the lines to the file and stage the file in the repo.
|
||||||
'''
|
'''
|
||||||
with open(self.filename, 'w') as f:
|
with open(self.filename, 'w') as f:
|
||||||
os.chmod(self.filename, 0o600)
|
os.chmod(self.filename, 0o600)
|
||||||
self._to_file(f)
|
self._to_file(f)
|
||||||
f.flush()
|
f.flush()
|
||||||
os.fsync(f.fileno())
|
os.fsync(f.fileno())
|
||||||
# For goodness's sake, write it to the disk already!
|
# For goodness's sake, write it to the disk already!
|
||||||
repo.stage([self.repo_relative_filename])
|
repo.stage([self.repo_relative_filename])
|
||||||
|
|
||||||
|
|
||||||
class PickledResource(Resource):
|
class PickledResource(Resource):
|
||||||
'''
|
'''
|
||||||
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
|
A ``Resource`` subclass that uses ``pickle`` on its file/thing.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def _from_file(self, f):
|
def _from_file(self, f):
|
||||||
return [pickle.load(f)]
|
return [pickle.load(f)]
|
||||||
|
|
||||||
def _to_file(self, f):
|
def _to_file(self, f):
|
||||||
pickle.dump(self.thing[0], f, protocol=2)
|
pickle.dump(self.thing[0], f, protocol=2)
|
||||||
|
|
||||||
|
|
||||||
class PersistTask(object):
|
class PersistTask(object):
|
||||||
'''
|
'''
|
||||||
This class deals with saving changes to the git repo.
|
This class deals with saving changes to the git repo.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
LIMIT = 10
|
LIMIT = 10
|
||||||
MAX_SAVE = 10
|
MAX_SAVE = 10
|
||||||
|
|
||||||
def __init__(self, home):
|
def __init__(self, home):
|
||||||
self.home = home
|
self.home = home
|
||||||
self.repo = open_repo(home)
|
self.repo = open_repo(home)
|
||||||
self._r = make_repo_relative_path_maker(self.repo)
|
self._r = make_repo_relative_path_maker(self.repo)
|
||||||
self.counter = Counter()
|
self.counter = Counter()
|
||||||
self.store = {}
|
self.store = {}
|
||||||
|
|
||||||
def open(self, name):
|
def open(self, name):
|
||||||
'''
|
'''
|
||||||
Look up the named file in home and return its content_id and data.
|
Look up the named file in home and return its content_id and data.
|
||||||
'''
|
'''
|
||||||
fn = os.path.join(self.home, name)
|
fn = os.path.join(self.home, name)
|
||||||
content_id = name # hash(fn)
|
content_id = name # hash(fn)
|
||||||
try:
|
try:
|
||||||
resource = self.store[content_id]
|
resource = self.store[content_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
R = PickledResource if name.endswith('.pickle') else Resource
|
R = PickledResource if name.endswith('.pickle') else Resource
|
||||||
resource = self.store[content_id] = R(fn, self._r(fn))
|
resource = self.store[content_id] = R(fn, self._r(fn))
|
||||||
return content_id, resource.thing
|
return content_id, resource.thing
|
||||||
|
|
||||||
def handle(self, message):
|
def handle(self, message):
|
||||||
'''
|
'''
|
||||||
Handle messages, dispatch to ``handle_FOO()`` methods.
|
Handle messages, dispatch to ``handle_FOO()`` methods.
|
||||||
'''
|
'''
|
||||||
if isinstance(message, core.OpenMessage):
|
if isinstance(message, core.OpenMessage):
|
||||||
self.handle_open(message)
|
self.handle_open(message)
|
||||||
elif isinstance(message, core.ModifyMessage):
|
elif isinstance(message, core.ModifyMessage):
|
||||||
self.handle_modify(message)
|
self.handle_modify(message)
|
||||||
elif isinstance(message, core.PersistMessage):
|
elif isinstance(message, core.PersistMessage):
|
||||||
self.handle_persist(message)
|
self.handle_persist(message)
|
||||||
elif isinstance(message, core.ShutdownMessage):
|
elif isinstance(message, core.ShutdownMessage):
|
||||||
for content_id in self.counter:
|
for content_id in self.counter:
|
||||||
self.store[content_id].persist(self.repo)
|
self.store[content_id].persist(self.repo)
|
||||||
self.commit('shutdown')
|
self.commit('shutdown')
|
||||||
|
|
||||||
def handle_open(self, message):
|
def handle_open(self, message):
|
||||||
'''
|
'''
|
||||||
Foo.
|
Foo.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
message.content_id, message.thing = self.open(message.name)
|
message.content_id, message.thing = self.open(message.name)
|
||||||
except:
|
except:
|
||||||
message.traceback = traceback.format_exc()
|
message.traceback = traceback.format_exc()
|
||||||
message.status = core.ERROR
|
message.status = core.ERROR
|
||||||
else:
|
else:
|
||||||
message.status = core.SUCCESS
|
message.status = core.SUCCESS
|
||||||
|
|
||||||
def handle_modify(self, message):
|
def handle_modify(self, message):
|
||||||
'''
|
'''
|
||||||
Foo.
|
Foo.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
content_id = message.details['content_id']
|
content_id = message.details['content_id']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
if not content_id:
|
if not content_id:
|
||||||
return
|
return
|
||||||
self.counter[content_id] += 1
|
self.counter[content_id] += 1
|
||||||
if self.counter[content_id] > self.LIMIT:
|
if self.counter[content_id] > self.LIMIT:
|
||||||
self.persist(content_id)
|
self.persist(content_id)
|
||||||
self.commit('due to activity')
|
self.commit('due to activity')
|
||||||
|
|
||||||
def handle_persist(self, message):
|
def handle_persist(self, message):
|
||||||
'''
|
'''
|
||||||
Foo.
|
Foo.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
resource = self.store[message.content_id]
|
resource = self.store[message.content_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
resource = self.handle_persist_new(message)
|
resource = self.handle_persist_new(message)
|
||||||
resource.persist(self.repo)
|
resource.persist(self.repo)
|
||||||
self.commit('by request from %r' % (message.sender,))
|
self.commit('by request from %r' % (message.sender,))
|
||||||
|
|
||||||
def handle_persist_new(self, message):
|
def handle_persist_new(self, message):
|
||||||
'''
|
'''
|
||||||
Foo.
|
Foo.
|
||||||
'''
|
'''
|
||||||
name = message.content_id
|
name = message.content_id
|
||||||
check_filename(name)
|
check_filename(name)
|
||||||
fn = os.path.join(self.home, name)
|
fn = os.path.join(self.home, name)
|
||||||
thing = message.details['thing']
|
thing = message.details['thing']
|
||||||
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
|
R = PickledResource if name.endswith('.pickle') else Resource # !!! refactor!
|
||||||
resource = self.store[name] = R(fn, self._r(fn), thing)
|
resource = self.store[name] = R(fn, self._r(fn), thing)
|
||||||
return resource
|
return resource
|
||||||
|
|
||||||
def persist(self, content_id):
|
def persist(self, content_id):
|
||||||
'''
|
'''
|
||||||
Persist a resource.
|
Persist a resource.
|
||||||
'''
|
'''
|
||||||
del self.counter[content_id]
|
del self.counter[content_id]
|
||||||
self.store[content_id].persist(self.repo)
|
self.store[content_id].persist(self.repo)
|
||||||
|
|
||||||
def task_run(self):
|
def task_run(self):
|
||||||
'''
|
'''
|
||||||
Stage any outstanding changes.
|
Stage any outstanding changes.
|
||||||
'''
|
'''
|
||||||
if not self.counter:
|
if not self.counter:
|
||||||
return
|
return
|
||||||
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
|
for content_id, _ in self.counter.most_common(self.MAX_SAVE):
|
||||||
self.persist(content_id)
|
self.persist(content_id)
|
||||||
self.commit()
|
self.commit()
|
||||||
|
|
||||||
def commit(self, message='auto-commit'):
|
def commit(self, message='auto-commit'):
|
||||||
'''
|
'''
|
||||||
Commit.
|
Commit.
|
||||||
'''
|
'''
|
||||||
return self.repo.do_commit(message, committer=core.COMMITTER)
|
return self.repo.do_commit(message, committer=core.COMMITTER)
|
||||||
|
|
||||||
def scan(self):
|
def scan(self):
|
||||||
'''
|
'''
|
||||||
Return a sorted list of all the files in the home dir.
|
Return a sorted list of all the files in the home dir.
|
||||||
'''
|
'''
|
||||||
return sorted([
|
return sorted([
|
||||||
fn
|
fn
|
||||||
for fn in os.listdir(self.home)
|
for fn in os.listdir(self.home)
|
||||||
if os.path.isfile(os.path.join(self.home, fn))
|
if os.path.isfile(os.path.join(self.home, fn))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
def check_filename(name):
|
def check_filename(name):
|
||||||
'''
|
'''
|
||||||
Sanity checks for filename.
|
Sanity checks for filename.
|
||||||
'''
|
'''
|
||||||
# TODO: improve this...
|
# TODO: improve this...
|
||||||
if len(name) > 64:
|
if len(name) > 64:
|
||||||
raise ValueError('bad name %r' % (name,))
|
raise ValueError('bad name %r' % (name,))
|
||||||
left, dot, right = name.partition('.')
|
left, dot, right = name.partition('.')
|
||||||
if not left.isalnum() or dot and not right.isalnum():
|
if not left.isalnum() or dot and not right.isalnum():
|
||||||
raise ValueError('bad name %r' % (name,))
|
raise ValueError('bad name %r' % (name,))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
JOY_HOME = os.path.expanduser('~/.thun')
|
JOY_HOME = os.path.expanduser('~/.thun')
|
||||||
pt = PersistTask(JOY_HOME)
|
pt = PersistTask(JOY_HOME)
|
||||||
content_id, thing = pt.open('stack.pickle')
|
content_id, thing = pt.open('stack.pickle')
|
||||||
pt.persist(content_id)
|
pt.persist(content_id)
|
||||||
print(pt.counter)
|
print(pt.counter)
|
||||||
mm = core.ModifyMessage(None, None, content_id=content_id)
|
mm = core.ModifyMessage(None, None, content_id=content_id)
|
||||||
pt.handle(mm)
|
pt.handle(mm)
|
||||||
print(pt.counter)
|
print(pt.counter)
|
||||||
|
|
|
||||||
|
|
@ -32,44 +32,44 @@ MAX_WIDTH = 64
|
||||||
|
|
||||||
|
|
||||||
def fsi(item):
|
def fsi(item):
|
||||||
'''Format Stack Item'''
|
'''Format Stack Item'''
|
||||||
if isinstance(item, tuple):
|
if isinstance(item, tuple):
|
||||||
res = '[%s]' % expression_to_string(item)
|
res = '[%s]' % expression_to_string(item)
|
||||||
elif isinstance(item, str):
|
elif isinstance(item, str):
|
||||||
res = '"%s"' % item
|
res = '"%s"' % item
|
||||||
else:
|
else:
|
||||||
res = str(item)
|
res = str(item)
|
||||||
if len(res) > MAX_WIDTH:
|
if len(res) > MAX_WIDTH:
|
||||||
return res[:MAX_WIDTH - 3] + '...'
|
return res[:MAX_WIDTH - 3] + '...'
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class StackViewer(text_viewer.TextViewer):
|
class StackViewer(text_viewer.TextViewer):
|
||||||
|
|
||||||
def __init__(self, surface):
|
def __init__(self, surface):
|
||||||
super(StackViewer, self).__init__(surface)
|
super(StackViewer, self).__init__(surface)
|
||||||
self.stack_holder = None
|
self.stack_holder = None
|
||||||
self.content_id = 'stack viewer'
|
self.content_id = 'stack viewer'
|
||||||
|
|
||||||
def _attach(self, display):
|
def _attach(self, display):
|
||||||
if self.stack_holder:
|
if self.stack_holder:
|
||||||
return
|
return
|
||||||
om = core.OpenMessage(self, 'stack.pickle')
|
om = core.OpenMessage(self, 'stack.pickle')
|
||||||
display.broadcast(om)
|
display.broadcast(om)
|
||||||
if om.status != core.SUCCESS:
|
if om.status != core.SUCCESS:
|
||||||
raise RuntimeError('stack unavailable')
|
raise RuntimeError('stack unavailable')
|
||||||
self.stack_holder = om.thing
|
self.stack_holder = om.thing
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
|
self.lines[:] = list(map(fsi, iter_stack(self.stack_holder[0]))) or ['']
|
||||||
|
|
||||||
def focus(self, display):
|
def focus(self, display):
|
||||||
self._attach(display)
|
self._attach(display)
|
||||||
super(StackViewer, self).focus(display)
|
super(StackViewer, self).focus(display)
|
||||||
|
|
||||||
def handle(self, message):
|
def handle(self, message):
|
||||||
if (isinstance(message, core.ModifyMessage)
|
if (isinstance(message, core.ModifyMessage)
|
||||||
and message.subject is self.stack_holder
|
and message.subject is self.stack_holder
|
||||||
):
|
):
|
||||||
self._update()
|
self._update()
|
||||||
self.draw_body()
|
self.draw_body()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -32,208 +32,208 @@ from joy.vui.core import BACKGROUND, FOREGROUND
|
||||||
|
|
||||||
|
|
||||||
class Viewer(object):
|
class Viewer(object):
|
||||||
'''
|
'''
|
||||||
Base Viewer class
|
Base Viewer class
|
||||||
'''
|
'''
|
||||||
|
|
||||||
MINIMUM_HEIGHT = 11
|
MINIMUM_HEIGHT = 11
|
||||||
|
|
||||||
def __init__(self, surface):
|
def __init__(self, surface):
|
||||||
self.resurface(surface)
|
self.resurface(surface)
|
||||||
self.last_touch = 0, 0
|
self.last_touch = 0, 0
|
||||||
|
|
||||||
def resurface(self, surface):
|
def resurface(self, surface):
|
||||||
self.w, self.h = surface.get_width(), surface.get_height()
|
self.w, self.h = surface.get_width(), surface.get_height()
|
||||||
self.surface = surface
|
self.surface = surface
|
||||||
|
|
||||||
def split(self, y):
|
def split(self, y):
|
||||||
'''
|
'''
|
||||||
Split the viewer at the y coordinate (which is relative to the
|
Split the viewer at the y coordinate (which is relative to the
|
||||||
viewer's surface and must be inside it somewhere) and return the
|
viewer's surface and must be inside it somewhere) and return the
|
||||||
remaining height. The upper part of the viewer remains (and gets
|
remaining height. The upper part of the viewer remains (and gets
|
||||||
redrawn on a new surface) and the lower space is now available
|
redrawn on a new surface) and the lower space is now available
|
||||||
for e.g. a new viewer.
|
for e.g. a new viewer.
|
||||||
'''
|
'''
|
||||||
assert y >= self.MINIMUM_HEIGHT
|
assert y >= self.MINIMUM_HEIGHT
|
||||||
new_viewer_h = self.h - y
|
new_viewer_h = self.h - y
|
||||||
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
|
self.resurface(self.surface.subsurface((0, 0, self.w, y)))
|
||||||
if y <= self.last_touch[1]: self.last_touch = 0, 0
|
if y <= self.last_touch[1]: self.last_touch = 0, 0
|
||||||
self.draw()
|
self.draw()
|
||||||
return new_viewer_h
|
return new_viewer_h
|
||||||
|
|
||||||
def handle(self, message):
|
def handle(self, message):
|
||||||
assert self is not message.sender
|
assert self is not message.sender
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
'''Draw the viewer onto its surface.'''
|
'''Draw the viewer onto its surface.'''
|
||||||
self.surface.fill(BACKGROUND)
|
self.surface.fill(BACKGROUND)
|
||||||
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
|
x, y, h = self.w - 1, self.MINIMUM_HEIGHT, self.h - 1
|
||||||
# Right-hand side.
|
# Right-hand side.
|
||||||
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
|
pygame.draw.line(self.surface, FOREGROUND, (x, 0), (x, h))
|
||||||
# Between header and body.
|
# Between header and body.
|
||||||
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
|
pygame.draw.line(self.surface, FOREGROUND, (0, y), (x, y))
|
||||||
# Bottom.
|
# Bottom.
|
||||||
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
|
pygame.draw.line(self.surface, FOREGROUND, (0, h), (x, h))
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
'''Close the viewer and release any resources, etc...'''
|
'''Close the viewer and release any resources, etc...'''
|
||||||
|
|
||||||
def focus(self, display):
|
def focus(self, display):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def unfocus(self):
|
def unfocus(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Event handling.
|
# Event handling.
|
||||||
|
|
||||||
def mouse_down(self, display, x, y, button):
|
def mouse_down(self, display, x, y, button):
|
||||||
self.last_touch = x, y
|
self.last_touch = x, y
|
||||||
|
|
||||||
def mouse_up(self, display, x, y, button):
|
def mouse_up(self, display, x, y, button):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
|
def mouse_motion(self, display, x, y, dx, dy, button0, button1, button2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def key_up(self, display, key, mod):
|
def key_up(self, display, key, mod):
|
||||||
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
|
if key == pygame.K_q and mod & pygame.KMOD_CTRL: # Ctrl-q
|
||||||
display.close_viewer(self)
|
display.close_viewer(self)
|
||||||
return True
|
return True
|
||||||
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
|
if key == pygame.K_g and mod & pygame.KMOD_CTRL: # Ctrl-g
|
||||||
display.grow_viewer(self)
|
display.grow_viewer(self)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def key_down(self, display, uch, key, mod):
|
def key_down(self, display, uch, key, mod):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MenuViewer(Viewer):
|
class MenuViewer(Viewer):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
MenuViewer class
|
MenuViewer class
|
||||||
'''
|
'''
|
||||||
|
|
||||||
MINIMUM_HEIGHT = 26
|
MINIMUM_HEIGHT = 26
|
||||||
|
|
||||||
def __init__(self, surface):
|
def __init__(self, surface):
|
||||||
Viewer.__init__(self, surface)
|
Viewer.__init__(self, surface)
|
||||||
self.resizing = 0
|
self.resizing = 0
|
||||||
self.bg = 100, 150, 100
|
self.bg = 100, 150, 100
|
||||||
|
|
||||||
def resurface(self, surface):
|
def resurface(self, surface):
|
||||||
Viewer.resurface(self, surface)
|
Viewer.resurface(self, surface)
|
||||||
n = self.MINIMUM_HEIGHT - 2
|
n = self.MINIMUM_HEIGHT - 2
|
||||||
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
|
self.close_rect = pygame.rect.Rect(self.w - 2 - n, 1, n, n)
|
||||||
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
|
self.grow_rect = pygame.rect.Rect(1, 1, n, n)
|
||||||
self.body_rect = pygame.rect.Rect(
|
self.body_rect = pygame.rect.Rect(
|
||||||
0, self.MINIMUM_HEIGHT + 1,
|
0, self.MINIMUM_HEIGHT + 1,
|
||||||
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
|
self.w - 1, self.h - self.MINIMUM_HEIGHT - 2)
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
'''Draw the viewer onto its surface.'''
|
'''Draw the viewer onto its surface.'''
|
||||||
Viewer.draw(self)
|
Viewer.draw(self)
|
||||||
if not self.resizing:
|
if not self.resizing:
|
||||||
self.draw_menu()
|
self.draw_menu()
|
||||||
self.draw_body()
|
self.draw_body()
|
||||||
|
|
||||||
def draw_menu(self):
|
def draw_menu(self):
|
||||||
# menu buttons
|
# menu buttons
|
||||||
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
|
pygame.draw.rect(self.surface, FOREGROUND, self.close_rect, 1)
|
||||||
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
|
pygame.draw.rect(self.surface, FOREGROUND, self.grow_rect, 1)
|
||||||
|
|
||||||
def draw_body(self):
|
def draw_body(self):
|
||||||
self.surface.fill(self.bg, self.body_rect)
|
self.surface.fill(self.bg, self.body_rect)
|
||||||
|
|
||||||
def mouse_down(self, display, x, y, button):
|
def mouse_down(self, display, x, y, button):
|
||||||
Viewer.mouse_down(self, display, x, y, button)
|
Viewer.mouse_down(self, display, x, y, button)
|
||||||
if y <= self.MINIMUM_HEIGHT:
|
if y <= self.MINIMUM_HEIGHT:
|
||||||
self.menu_click(display, x, y, button)
|
self.menu_click(display, x, y, button)
|
||||||
else:
|
else:
|
||||||
bx, by = self.body_rect.topleft
|
bx, by = self.body_rect.topleft
|
||||||
self.body_click(display, x - bx, y - by, button)
|
self.body_click(display, x - bx, y - by, button)
|
||||||
|
|
||||||
def body_click(self, display, x, y, button):
|
def body_click(self, display, x, y, button):
|
||||||
if button == 1:
|
if button == 1:
|
||||||
self.draw_an_a(x, y)
|
self.draw_an_a(x, y)
|
||||||
|
|
||||||
def menu_click(self, display, x, y, button):
|
def menu_click(self, display, x, y, button):
|
||||||
if button == 1:
|
if button == 1:
|
||||||
self.resizing = 1
|
self.resizing = 1
|
||||||
elif button == 3:
|
elif button == 3:
|
||||||
if self.close_rect.collidepoint(x, y):
|
if self.close_rect.collidepoint(x, y):
|
||||||
display.close_viewer(self)
|
display.close_viewer(self)
|
||||||
return True
|
return True
|
||||||
elif self.grow_rect.collidepoint(x, y):
|
elif self.grow_rect.collidepoint(x, y):
|
||||||
display.grow_viewer(self)
|
display.grow_viewer(self)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mouse_up(self, display, x, y, button):
|
def mouse_up(self, display, x, y, button):
|
||||||
|
|
||||||
if button == 1 and self.resizing:
|
if button == 1 and self.resizing:
|
||||||
if self.resizing == 2:
|
if self.resizing == 2:
|
||||||
self.resizing = 0
|
self.resizing = 0
|
||||||
self.draw()
|
self.draw()
|
||||||
display.done_resizing()
|
display.done_resizing()
|
||||||
self.resizing = 0
|
self.resizing = 0
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
||||||
if self.resizing and button0:
|
if self.resizing and button0:
|
||||||
self.resizing = 2
|
self.resizing = 2
|
||||||
display.change_viewer(self, rel_y, relative=True)
|
display.change_viewer(self, rel_y, relative=True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.resizing = 0
|
self.resizing = 0
|
||||||
#self.draw_an_a(x, y)
|
#self.draw_an_a(x, y)
|
||||||
|
|
||||||
def key_up(self, display, key, mod):
|
def key_up(self, display, key, mod):
|
||||||
if Viewer.key_up(self, display, key, mod):
|
if Viewer.key_up(self, display, key, mod):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def draw_an_a(self, x, y):
|
def draw_an_a(self, x, y):
|
||||||
# Draw a crude letter A.
|
# Draw a crude letter A.
|
||||||
lw, lh = 10, 14
|
lw, lh = 10, 14
|
||||||
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
|
try: surface = self.surface.subsurface((x - lw, y - lh, lw, lh))
|
||||||
except ValueError: return
|
except ValueError: return
|
||||||
draw_a(surface, blend=1)
|
draw_a(surface, blend=1)
|
||||||
|
|
||||||
|
|
||||||
class SomeViewer(MenuViewer):
|
class SomeViewer(MenuViewer):
|
||||||
|
|
||||||
def __init__(self, surface):
|
def __init__(self, surface):
|
||||||
MenuViewer.__init__(self, surface)
|
MenuViewer.__init__(self, surface)
|
||||||
|
|
||||||
def resurface(self, surface):
|
def resurface(self, surface):
|
||||||
MenuViewer.resurface(self, surface)
|
MenuViewer.resurface(self, surface)
|
||||||
|
|
||||||
def draw_menu(self):
|
def draw_menu(self):
|
||||||
MenuViewer.draw_menu(self)
|
MenuViewer.draw_menu(self)
|
||||||
|
|
||||||
def draw_body(self):
|
def draw_body(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def body_click(self, display, x, y, button):
|
def body_click(self, display, x, y, button):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def menu_click(self, display, x, y, button):
|
def menu_click(self, display, x, y, button):
|
||||||
if MenuViewer.menu_click(self, display, x, y, button):
|
if MenuViewer.menu_click(self, display, x, y, button):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mouse_up(self, display, x, y, button):
|
def mouse_up(self, display, x, y, button):
|
||||||
if MenuViewer.mouse_up(self, display, x, y, button):
|
if MenuViewer.mouse_up(self, display, x, y, button):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def mouse_motion(self, display, x, y, rel_x, rel_y, button0, button1, button2):
|
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,
|
if MenuViewer.mouse_motion(self, display, x, y, rel_x, rel_y,
|
||||||
button0, button1, button2):
|
button0, button1, button2):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def key_down(self, display, uch, key, mod):
|
def key_down(self, display, uch, key, mod):
|
||||||
try:
|
try:
|
||||||
print(chr(key), end=' ')
|
print(chr(key), end=' ')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Note that Oberon book says that if you split at the exact top of a viewer
|
# Note that Oberon book says that if you split at the exact top of a viewer
|
||||||
|
|
@ -243,7 +243,7 @@ class SomeViewer(MenuViewer):
|
||||||
|
|
||||||
|
|
||||||
def draw_a(surface, color=FOREGROUND, blend=False):
|
def draw_a(surface, color=FOREGROUND, blend=False):
|
||||||
w, h = surface.get_width() - 2, surface.get_height() - 2
|
w, h = surface.get_width() - 2, surface.get_height() - 2
|
||||||
pygame.draw.aalines(surface, color, False, (
|
pygame.draw.aalines(surface, color, False, (
|
||||||
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
|
(1, h), (old_div(w, 2), 1), (w, h), (1, old_div(h, 2))
|
||||||
), blend)
|
), blend)
|
||||||
|
|
|
||||||
44
setup.py
44
setup.py
|
|
@ -23,25 +23,25 @@ from textwrap import dedent
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='Thun',
|
name='Thun',
|
||||||
version='0.2.0',
|
version='0.2.0',
|
||||||
description='Python Implementation of Joy',
|
description='Python Implementation of Joy',
|
||||||
long_description=dedent('''\
|
long_description=dedent('''\
|
||||||
Joy is a programming language created by Manfred von Thun that is easy to
|
Joy is a programming language created by Manfred von Thun that is easy to
|
||||||
use and understand and has many other nice properties. This Python
|
use and understand and has many other nice properties. This Python
|
||||||
package implements an interpreter for a dialect of Joy that attempts to
|
package implements an interpreter for a dialect of Joy that attempts to
|
||||||
stay very close to the spirit of Joy but does not precisely match the
|
stay very close to the spirit of Joy but does not precisely match the
|
||||||
behaviour of the original version written in C.'''),
|
behaviour of the original version written in C.'''),
|
||||||
author='Simon Forman',
|
author='Simon Forman',
|
||||||
author_email='forman.simon@gmail.com',
|
author_email='forman.simon@gmail.com',
|
||||||
url='https://joypy.osdn.io',
|
url='https://joypy.osdn.io',
|
||||||
license='GPLv3+',
|
license='GPLv3+',
|
||||||
packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'],
|
packages=['joy', 'joy.utils', 'joy.gui', 'joy.vui'],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
|
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Other',
|
'Programming Language :: Other',
|
||||||
'Topic :: Software Development :: Interpreters',
|
'Topic :: Software Development :: Interpreters',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue