199 lines
5.5 KiB
Python
Executable File
199 lines
5.5 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# This is a script, the module namespace is used as a kind of singleton
|
|
# for organizing the moving parts of the system. I forget why I didn't
|
|
# use a more typical class.
|
|
#
|
|
# This docstring doubles as the log header that the system prints when
|
|
# the log is reset.
|
|
|
|
('''\
|
|
Joypy - Copyright © 2018 Simon Forman
|
|
'''
|
|
'This program comes with ABSOLUTELY NO WARRANTY; for details right-click "warranty".'
|
|
' This is free software, and you are welcome to redistribute it under certain conditions;'
|
|
' right-click "sharing" for details.'
|
|
' Right-click on these commands to see docs on UI commands: key_bindings mouse_bindings')
|
|
|
|
from __future__ import print_function
|
|
from future import standard_library
|
|
standard_library.install_aliases()
|
|
import logging, os, pickle, sys
|
|
from datetime import datetime
|
|
from textwrap import dedent
|
|
from configparser import RawConfigParser
|
|
|
|
from joy.gui.utils import init_home, argparser, FileFaker
|
|
|
|
|
|
DATETIME_FORMAT = "Thun • %B %d %a • %I:%M %p"
|
|
VIEWER_DEFAULTS = dict(width=80, height=25)
|
|
|
|
|
|
args = argparser.parse_args()
|
|
JOY_HOME = args.joy_home
|
|
repo = init_home(JOY_HOME)
|
|
homed = lambda fn: os.path.join(JOY_HOME, fn)
|
|
|
|
# Set up logging before doing anything else.
|
|
|
|
_log = logging.getLogger(__name__)
|
|
logging.basicConfig(
|
|
format='%(asctime)-15s %(levelname)s %(name)s %(message)s',
|
|
filename=os.path.join(JOY_HOME, 'thun.log'),
|
|
level=logging.INFO,
|
|
)
|
|
_log.info('Starting with JOY_HOME=%s', JOY_HOME)
|
|
|
|
# Now that logging is set up, continue loading the system.
|
|
|
|
from joy.gui.textwidget import TextViewerWidget, tk, get_font
|
|
from joy.gui.world import StackWorld
|
|
from joy.gui.controllerlistbox import StackListbox
|
|
from joy.library import initialize, DefinitionWrapper
|
|
from joy.utils.stack import stack_to_string
|
|
|
|
|
|
cp = RawConfigParser()
|
|
# Don't mess with uppercase. We need it for Tk event binding.
|
|
cp.optionxform = str
|
|
with open(os.path.join(args.joy_home, 'thun.config')) as f:
|
|
cp.readfp(f)
|
|
|
|
|
|
GLOBAL_COMMANDS = dict(cp.items('key bindings'))
|
|
|
|
|
|
def repo_relative_path(path):
|
|
return os.path.relpath(
|
|
path,
|
|
os.path.commonprefix((repo.controldir(), path))
|
|
)
|
|
|
|
def commands():
|
|
'''
|
|
We define a bunch of meta-interpreter command functions here and
|
|
return them in a dictionary. They have all the contents of this
|
|
module in their scope so they can e.g. modify the log viewer window.
|
|
'''
|
|
# pylint: disable=unused-variable
|
|
|
|
def key_bindings(*args):
|
|
commands = [ # These are bound in the TextViewerWidget.
|
|
'Control-Enter - Run the selection as Joy code, or if there\'s no selection the line containing the cursor.',
|
|
'F3 - Copy selection to stack.',
|
|
'Shift-F3 - Cut selection to stack.',
|
|
'F4 - Paste item on top of stack to insertion cursor.',
|
|
'Shift-F4 - Pop and paste top of stack to insertion cursor.',
|
|
]
|
|
for key, command in GLOBAL_COMMANDS.items():
|
|
commands.append('%s - %s' % (key.lstrip('<').rstrip('>'), command))
|
|
print('\n'.join([''] + sorted(commands)))
|
|
return args
|
|
|
|
|
|
def mouse_bindings(*args):
|
|
print(dedent('''
|
|
Mouse button chords (to cancel a chord, click the third mouse button.)
|
|
|
|
Left - Point, sweep selection
|
|
Left-Middle - Copy the selection, place text on stack
|
|
Left-Right - Run the selection as Joy code
|
|
|
|
Middle - Paste selection (bypass stack); click and drag to scroll.
|
|
Middle-Left - Paste from top of stack, preserve
|
|
Middle-Right - Paste from top of stack, pop
|
|
|
|
Right - Execute command word under mouse cursor
|
|
Right-Left - Print docs of command word under mouse cursor
|
|
Right-Middle - Lookup word (kinda useless now)
|
|
'''))
|
|
return args
|
|
|
|
|
|
def reset_log(*args):
|
|
log.delete('0.0', tk.END)
|
|
print(datetime.now().strftime(DATETIME_FORMAT))
|
|
return args
|
|
|
|
|
|
def Thun(*args):
|
|
print(__doc__)
|
|
return args
|
|
|
|
|
|
def show_log(*args):
|
|
log_window.wm_deiconify()
|
|
log_window.update()
|
|
return args
|
|
|
|
|
|
def show_stack(*args):
|
|
stack_window.wm_deiconify()
|
|
stack_window.update()
|
|
return args
|
|
|
|
|
|
def grand_reset(s, e, d):
|
|
stack = world.load_stack() or ()
|
|
log.reset()
|
|
t.reset()
|
|
return stack, e, d
|
|
|
|
return locals()
|
|
|
|
|
|
# Identify the system core files.
|
|
DEFS_FN = homed('definitions.txt')
|
|
JOY_FN = homed('scratch.txt')
|
|
LOG_FN = homed('log.txt')
|
|
STACK_FN = homed('stack.pickle')
|
|
REL_STACK_FN = repo_relative_path(STACK_FN)
|
|
|
|
# Initialize the Joy dictionary.
|
|
D = initialize()
|
|
D.update(commands())
|
|
DefinitionWrapper.load_definitions(DEFS_FN, D)
|
|
|
|
world = StackWorld(repo, STACK_FN, REL_STACK_FN, dictionary=D)
|
|
|
|
t = TextViewerWidget(world, **VIEWER_DEFAULTS)
|
|
|
|
log_window = tk.Toplevel()
|
|
# Make it so that you can't actually close the log window, if you try it
|
|
# will just "withdraw" (which is like minifying but without a entry in
|
|
# the taskbar or icon or whatever.)
|
|
log_window.protocol("WM_DELETE_WINDOW", log_window.withdraw)
|
|
log = TextViewerWidget(world, log_window, **VIEWER_DEFAULTS)
|
|
|
|
FONT = get_font('Iosevka', size=14) # Requires Tk root already set up.
|
|
|
|
stack_window = tk.Toplevel()
|
|
stack_window.title("Stack")
|
|
stack_window.protocol("WM_DELETE_WINDOW", log_window.withdraw)
|
|
stack_viewer = StackListbox(world, stack_window, items=[], font=FONT)
|
|
stack_viewer.pack(expand=True, fill=tk.BOTH)
|
|
world.set_viewer(stack_viewer)
|
|
|
|
|
|
log.init('Log', LOG_FN, repo_relative_path(LOG_FN), repo, FONT)
|
|
t.init('Joy - ' + JOY_HOME, JOY_FN, repo_relative_path(JOY_FN), repo, FONT)
|
|
|
|
for event, command in GLOBAL_COMMANDS.items():
|
|
callback = lambda _, _command=command: world.interpret(_command)
|
|
t.bind_all(event, callback)
|
|
|
|
|
|
def main():
|
|
sys.stdout, old_stdout = FileFaker(log), sys.stdout
|
|
try:
|
|
t.mainloop()
|
|
finally:
|
|
sys.stdout = old_stdout
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|