1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449
|
#! /usr/bin/env python
# A STDWIN-based front end for the Python interpreter.
#
# This is useful if you want to avoid console I/O and instead
# use text windows to issue commands to the interpreter.
#
# It supports multiple interpreter windows, each with its own context.
#
# BUGS AND CAVEATS:
#
# This was written long ago as a demonstration, and slightly hacked to
# keep it up-to-date, but never as an industry-strength alternative
# interface to Python. It should be rewritten using more classes, and
# merged with something like wdb.
#
# Although this supports multiple windows, the whole application
# is deaf and dumb when a command is running in one window.
#
# Interrupt is (ab)used to signal EOF on input requests.
#
# On UNIX (using X11), interrupts typed in the window will not be
# seen until the next input or output operation. When you are stuck
# in an infinite loop, try typing ^C in the shell window where you
# started this interpreter. (On the Mac, interrupts work normally.)
import sys
import stdwin
from stdwinevents import *
import rand
import mainloop
import os
# Stack of windows waiting for [raw_]input().
# Element [0] is the top.
# If there are multiple windows waiting for input, only the
# one on top of the stack can accept input, because the way
# raw_input() is implemented (using recursive mainloop() calls).
#
inputwindows = []
# Exception raised when input is available
#
InputAvailable = 'input available for raw_input (not an error)'
# Main program -- create the window and call the mainloop
#
def main():
# Hack so 'import python' won't load another copy
# of this if we were loaded though 'python python.py'.
# (Should really look at sys.argv[0]...)
if 'inputwindows' in dir(sys.modules['__main__']) and \
sys.modules['__main__'].inputwindows is inputwindows:
sys.modules['python'] = sys.modules['__main__']
#
win = makewindow()
mainloop.mainloop()
# Create a new window
#
def makewindow():
# stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1
# stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1
# width, height = stdwin.textwidth('in')*40, stdwin.lineheight()*24
# stdwin.setdefwinsize(width, height)
win = stdwin.open('Python interpreter ready')
win.editor = win.textcreate((0,0), win.getwinsize())
win.globals = {} # Dictionary for user's globals
win.command = '' # Partially read command
win.busy = 0 # Ready to accept a command
win.auto = 1 # [CR] executes command
win.insertOutput = 1 # Insert output at focus
win.insertError = 1 # Insert error output at focus
win.setwincursor('ibeam')
win.filename = '' # Empty if no file for this window
makefilemenu(win)
makeeditmenu(win)
win.dispatch = pdispatch # Event dispatch function
mainloop.register(win)
return win
# Make a 'File' menu
#
def makefilemenu(win):
win.filemenu = mp = win.menucreate('File')
mp.callback = []
additem(mp, 'New', 'N', do_new)
additem(mp, 'Open...', 'O', do_open)
additem(mp, '', '', None)
additem(mp, 'Close', 'W', do_close)
additem(mp, 'Save', 'S', do_save)
additem(mp, 'Save as...', '', do_saveas)
additem(mp, '', '', None)
additem(mp, 'Quit', 'Q', do_quit)
# Make an 'Edit' menu
#
def makeeditmenu(win):
win.editmenu = mp = win.menucreate('Edit')
mp.callback = []
additem(mp, 'Cut', 'X', do_cut)
additem(mp, 'Copy', 'C', do_copy)
additem(mp, 'Paste', 'V', do_paste)
additem(mp, 'Clear', '', do_clear)
additem(mp, '', '', None)
win.iauto = len(mp.callback)
additem(mp, 'Autoexecute', '', do_auto)
mp.check(win.iauto, win.auto)
win.insertOutputNum = len(mp.callback)
additem(mp, 'Insert Output', '', do_insertOutputOption)
win.insertErrorNum = len(mp.callback)
additem(mp, 'Insert Error', '', do_insertErrorOption)
additem(mp, 'Exec', '\r', do_exec)
# Helper to add a menu item and callback function
#
def additem(mp, text, shortcut, handler):
if shortcut:
mp.additem(text, shortcut)
else:
mp.additem(text)
mp.callback.append(handler)
# Dispatch a single event to the interpreter.
# Resize events cause a resize of the editor.
# Some events are treated specially.
# Most other events are passed directly to the editor.
#
def pdispatch(event):
type, win, detail = event
if not win:
win = stdwin.getactive()
if not win: return
if type == WE_CLOSE:
do_close(win)
return
elif type == WE_SIZE:
win.editor.move((0, 0), win.getwinsize())
elif type == WE_COMMAND and detail == WC_RETURN:
if win.auto:
do_exec(win)
else:
void = win.editor.event(event)
elif type == WE_COMMAND and detail == WC_CANCEL:
if win.busy:
raise KeyboardInterrupt
else:
win.command = ''
settitle(win)
elif type == WE_MENU:
mp, item = detail
mp.callback[item](win)
else:
void = win.editor.event(event)
if win in mainloop.windows:
# May have been deleted by close...
win.setdocsize(0, win.editor.getrect()[1][1])
if type in (WE_CHAR, WE_COMMAND):
win.editor.setfocus(win.editor.getfocus())
# Helper to set the title of the window
#
def settitle(win):
if win.filename == '':
win.settitle('Python interpreter ready')
else:
win.settitle(win.filename)
# Helper to replace the text of the focus
#
def replace(win, text):
win.editor.replace(text)
# Resize the window to display the text
win.setdocsize(0, win.editor.getrect()[1][1]) # update the size before
win.editor.setfocus(win.editor.getfocus()) # move focus to the change
# File menu handlers
#
def do_new(win):
win = makewindow()
#
def do_open(win):
try:
filename = stdwin.askfile('Open file', '', 0)
win = makewindow()
win.filename = filename
win.editor.replace(open(filename, 'r').read())
win.editor.setfocus(0, 0)
win.settitle(win.filename)
#
except KeyboardInterrupt:
pass # Don't give an error on cancel
#
def do_save(win):
try:
if win.filename == '':
win.filename = stdwin.askfile('Open file', '', 1)
f = open(win.filename, 'w')
f.write(win.editor.gettext())
#
except KeyboardInterrupt:
pass # Don't give an error on cancel
def do_saveas(win):
currentFilename = win.filename
win.filename = ''
do_save(win) # Use do_save with empty filename
if win.filename == '': # Restore the name if do_save did not set it
win.filename = currentFilename
#
def do_close(win):
if win.busy:
stdwin.message('Can\'t close busy window')
return # need to fail if quitting??
win.editor = None # Break circular reference
#del win.editmenu # What about the filemenu??
mainloop.unregister(win)
win.close()
#
def do_quit(win):
# Call win.dispatch instead of do_close because there
# may be 'alien' windows in the list.
for win in mainloop.windows[:]:
mainloop.dispatch((WE_CLOSE, win, None))
# need to catch failed close
# Edit menu handlers
#
def do_cut(win):
text = win.editor.getfocustext()
if not text:
stdwin.fleep()
return
stdwin.setcutbuffer(0, text)
replace(win, '')
#
def do_copy(win):
text = win.editor.getfocustext()
if not text:
stdwin.fleep()
return
stdwin.setcutbuffer(0, text)
#
def do_paste(win):
text = stdwin.getcutbuffer(0)
if not text:
stdwin.fleep()
return
replace(win, text)
#
def do_clear(win):
replace(win, '')
# These would be better in a preferences dialog:
#
def do_auto(win):
win.auto = (not win.auto)
win.editmenu.check(win.iauto, win.auto)
#
def do_insertOutputOption(win):
win.insertOutput = (not win.insertOutput)
title = ['Append Output', 'Insert Output'][win.insertOutput]
win.editmenu.setitem(win.insertOutputNum, title)
#
def do_insertErrorOption(win):
win.insertError = (not win.insertError)
title = ['Error Dialog', 'Insert Error'][win.insertError]
win.editmenu.setitem(win.insertErrorNum, title)
# Extract a command from the editor and execute it, or pass input to
# an interpreter waiting for it.
# Incomplete commands are merely placed in the window's command buffer.
# All exceptions occurring during the execution are caught and reported.
# (Tracebacks are currently not possible, as the interpreter does not
# save the traceback pointer until it reaches its outermost level.)
#
def do_exec(win):
if win.busy:
if win not in inputwindows:
stdwin.message('Can\'t run recursive commands')
return
if win <> inputwindows[0]:
stdwin.message('Please complete recursive input first')
return
#
# Set text to the string to execute.
a, b = win.editor.getfocus()
alltext = win.editor.gettext()
n = len(alltext)
if a == b:
# There is no selected text, just an insert point;
# so execute the current line.
while 0 < a and alltext[a-1] <> '\n': # Find beginning of line
a = a-1
while b < n and alltext[b] <> '\n': # Find end of line after b
b = b+1
text = alltext[a:b] + '\n'
else:
# Execute exactly the selected text.
text = win.editor.getfocustext()
if text[-1:] <> '\n': # Make sure text ends with \n
text = text + '\n'
while b < n and alltext[b] <> '\n': # Find end of line after b
b = b+1
#
# Set the focus to expect the output, since there is always something.
# Output will be inserted at end of line after current focus,
# or appended to the end of the text.
b = [n, b][win.insertOutput]
win.editor.setfocus(b, b)
#
# Make sure there is a preceeding newline.
if alltext[b-1:b] <> '\n':
win.editor.replace('\n')
#
#
if win.busy:
# Send it to raw_input() below
raise InputAvailable, text
#
# Like the real Python interpreter, we want to execute
# single-line commands immediately, but save multi-line
# commands until they are terminated by a blank line.
# Unlike the real Python interpreter, we don't do any syntax
# checking while saving up parts of a multi-line command.
#
# The current heuristic to determine whether a command is
# the first line of a multi-line command simply checks whether
# the command ends in a colon (followed by a newline).
# This is not very robust (comments and continuations will
# confuse it), but it is usable, and simple to implement.
# (It even has the advantage that single-line loops etc.
# don't need te be terminated by a blank line.)
#
if win.command:
# Already continuing
win.command = win.command + text
if win.command[-2:] <> '\n\n':
win.settitle('Unfinished command...')
return # Need more...
else:
# New command
win.command = text
if text[-2:] == ':\n':
win.settitle('Unfinished command...')
return
command = win.command
win.command = ''
win.settitle('Executing command...')
#
# Some hacks:
# - The standard files are replaced by an IOWindow instance.
# - A 2nd argument to exec() is used to specify the directory
# holding the user's global variables. (If this wasn't done,
# the exec would be executed in the current local environment,
# and the user's assignments to globals would be lost...)
#
save_stdin = sys.stdin
save_stdout = sys.stdout
save_stderr = sys.stderr
try:
sys.stdin = sys.stdout = sys.stderr = IOWindow(win)
win.busy = 1
try:
exec(command, win.globals)
except KeyboardInterrupt:
print '[Interrupt]'
except:
if type(sys.exc_type) == type(''):
msg = sys.exc_type
else: msg = sys.exc_type.__name__
if sys.exc_value <> None:
msg = msg + ': ' + `sys.exc_value`
if win.insertError:
stdwin.fleep()
replace(win, msg + '\n')
else:
win.settitle('Unhandled exception')
stdwin.message(msg)
finally:
# Restore redirected I/O in *all* cases
win.busy = 0
sys.stderr = save_stderr
sys.stdout = save_stdout
sys.stdin = save_stdin
settitle(win)
# Class emulating file I/O from/to a window
#
class IOWindow:
#
def __init__(self, win):
self.win = win
#
def readline(self, *unused_args):
n = len(inputwindows)
save_title = self.win.gettitle()
title = n*'(' + 'Requesting input...' + ')'*n
self.win.settitle(title)
inputwindows.insert(0, self.win)
try:
try:
mainloop.mainloop()
finally:
del inputwindows[0]
self.win.settitle(save_title)
except InputAvailable, val: # See do_exec above
return val
except KeyboardInterrupt:
raise EOFError # Until we have a "send EOF" key
# If we didn't catch InputAvailable, something's wrong...
raise EOFError
#
def write(self, text):
mainloop.check()
replace(self.win, text)
mainloop.check()
# Currently unused function to test a command's syntax without executing it
#
def testsyntax(s):
import string
lines = string.splitfields(s, '\n')
for i in range(len(lines)): lines[i] = '\t' + lines[i]
lines.insert(0, 'if 0:')
lines.append('')
exec(string.joinfields(lines, '\n'))
# Call the main program
#
main()
|