blob: e362074b2734c361cbeee69652b7f47a276eee3a [file] [log] [blame]
Guido van Rossum63f4cdc1992-12-14 14:10:53 +00001#! /usr/local/bin/python
2
3# :set tabsize=4:
Guido van Rossum9cf8f331992-03-30 10:54:51 +00004
Guido van Rossum9cf8f331992-03-30 10:54:51 +00005# A STDWIN-based front end for the Python interpreter.
6#
7# This is useful if you want to avoid console I/O and instead
8# use text windows to issue commands to the interpreter.
9#
10# It supports multiple interpreter windows, each with its own context.
11#
12# BUGS AND CAVEATS:
13#
Guido van Rossum63f4cdc1992-12-14 14:10:53 +000014# I wrote this about two years ago. There are now some features in
15# Python that make it possible to overcome some of the bugs below,
16# but I haven't the time to adapt it; it's just meant as a little
17# thing to get you started...
18#
Guido van Rossum9cf8f331992-03-30 10:54:51 +000019# Although this supports multiple windows, the whole application
20# is deaf and dumb when a command is running in one window.
21#
22# Everything written to stdout or stderr is saved on a file which
23# is inserted in the window at the next input request.
24#
25# On UNIX (using X11), interrupts typed in the window will not be
26# seen until the next input request. (On the Mac, interrupts work.)
27#
28# Direct input from stdin should not be attempted.
29
30
31import sys
32import builtin
33import stdwin
34from stdwinevents import *
35import rand
36import mainloop
Guido van Rossum4ea570d1992-03-30 11:01:26 +000037import os
Guido van Rossum9cf8f331992-03-30 10:54:51 +000038
39
40# Filename used to capture output from commands; change to suit your taste
41#
42OUTFILE = '@python.stdout.tmp'
43
44
45# Stack of windows waiting for [raw_]input().
46# Element [0] is the top.
47# If there are multiple windows waiting for input, only the
48# one on top of the stack can accept input, because the way
49# raw_input() is implemented (using recursive mainloop() calls).
50#
51inputwindows = []
52
53
54# Exception raised when input is available.
55#
56InputAvailable = 'input available for raw_input (not an error)'
57
58
59# Main program. Create the window and call the mainloop.
60#
61def main():
62 # Hack so 'import python' won't load another copy
63 # of this if we were loaded though 'python python.py'.
64 # (Should really look at sys.argv[0]...)
65 if 'inputwindows' in dir(sys.modules['__main__']) and \
66 sys.modules['__main__'].inputwindows is inputwindows:
67 sys.modules['python'] = sys.modules['__main__']
68 #
69 win = makewindow()
70 mainloop.mainloop()
71
72
73# Create a new window.
74#
75def makewindow():
76 # stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1
77 # stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1
78 # stdwin.setdefwinsize(stdwin.textwidth('in')*40, stdwin.lineheight() * 24)
79 win = stdwin.open('Python interpreter ready')
80 win.editor = win.textcreate((0,0), win.getwinsize())
81 win.outfile = OUTFILE + `rand.rand()`
82 win.globals = {} # Dictionary for user's global variables
83 win.command = '' # Partially read command
84 win.busy = 0 # Ready to accept a command
85 win.auto = 1 # [CR] executes command
86 win.insertOutput = 1 # Insert output at focus.
87 win.insertError = 1 # Insert error output at focus.
88 win.setwincursor('ibeam')
89 win.filename = '' # Empty if no file associated with this window
90 makefilemenu(win)
91 makeeditmenu(win)
92 win.dispatch = pdispatch # Event dispatch function
93 mainloop.register(win)
94 return win
95
96
97# Make a 'File' menu
98#
99def makefilemenu(win):
100 win.filemenu = mp = win.menucreate('File')
101 mp.callback = []
102 additem(mp, 'New', 'N', do_new)
103 additem(mp, 'Open...', 'O', do_open)
104 additem(mp, '', '', None)
105 additem(mp, 'Close', 'W', do_close)
106 additem(mp, 'Save', 'S', do_save)
107 additem(mp, 'Save as...', '', do_saveas)
108 additem(mp, '', '', None)
109 additem(mp, 'Quit', 'Q', do_quit)
110
111
112# Make an 'Edit' menu
113#
114def makeeditmenu(win):
115 win.editmenu = mp = win.menucreate('Edit')
116 mp.callback = []
117 additem(mp, 'Cut', 'X', do_cut)
118 additem(mp, 'Copy', 'C', do_copy)
119 additem(mp, 'Paste', 'V', do_paste)
120 additem(mp, 'Clear', '', do_clear)
121 additem(mp, '', '', None)
122 win.iauto = len(mp.callback)
123 additem(mp, 'Autoexecute', '', do_auto)
124 mp.check(win.iauto, win.auto)
125 win.insertOutputNum = len(mp.callback)
126 additem(mp, 'Insert Output', '', do_insertOutputOption)
127 win.insertErrorNum = len(mp.callback)
128 additem(mp, 'Insert Error', '', do_insertErrorOption)
129 additem(mp, 'Exec', '\r', do_exec)
130
131
132# Helper to add a menu item and callback function
133#
134def additem(mp, text, shortcut, handler):
135 if shortcut:
136 mp.additem(text, shortcut)
137 else:
138 mp.additem(text)
139 mp.callback.append(handler)
140
141
142# Dispatch a single event to the interpreter.
143# Resize events cause a resize of the editor.
144# Other events are directly sent to the editor.
145#
146# Exception: WE_COMMAND/WC_RETURN causes the current selection
147# (if not empty) or current line (if empty) to be sent to the
148# interpreter. (In the future, there should be a way to insert
149# newlines in the text; or perhaps Enter or Meta-RETURN should be
150# used to trigger execution, like in MPW, though personally I prefer
151# using a plain Return to trigger execution, as this is what I want
152# in the majority of cases.)
153#
154# Also, WE_COMMAND/WC_CANCEL cancels any command in progress.
155#
156def pdispatch(event):
157 type, win, detail = event
158 if type == WE_CLOSE:
159 do_close(win)
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000160 return
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000161 elif type == WE_SIZE:
162 win.editor.move((0, 0), win.getwinsize())
163 elif type == WE_COMMAND and detail == WC_RETURN:
164 if win.auto:
165 do_exec(win)
166 else:
167 void = win.editor.event(event)
168 elif type == WE_COMMAND and detail == WC_CANCEL:
169 if win.busy:
170 raise InputAvailable, (EOFError, None)
171 else:
172 win.command = ''
173 settitle(win)
174 elif type == WE_MENU:
175 mp, item = detail
176 mp.callback[item](win)
177 else:
178 void = win.editor.event(event)
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000179 if win in mainloop.windows:
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000180 # May have been deleted by close...
181 win.setdocsize(0, win.editor.getrect()[1][1])
182 if type in (WE_CHAR, WE_COMMAND):
183 win.editor.setfocus(win.editor.getfocus())
184
185
186# Helper to set the title of the window.
187#
188def settitle(win):
189 if win.filename == '':
190 win.settitle('Python interpreter ready')
191 else:
192 win.settitle(win.filename)
193
194
195# Helper to replace the text of the focus.
196#
197def replace(win, text):
198 win.editor.replace(text)
199 # Resize the window to display the text
200 win.setdocsize(0, win.editor.getrect()[1][1]) # update the size before..
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000201 win.editor.setfocus(win.editor.getfocus()) # move focus to the change
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000202
203
204# File menu handlers
205#
206def do_new(win):
207 win = makewindow()
208#
209def do_open(win):
210 try:
211 filename = stdwin.askfile('Open file', '', 0)
212 win = makewindow()
213 win.filename = filename
Guido van Rossum4ea570d1992-03-30 11:01:26 +0000214 win.editor.replace(open(filename, 'r').read())
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000215 win.editor.setfocus(0, 0)
216 win.settitle(win.filename)
217 #
218 except KeyboardInterrupt:
219 pass # Don't give an error on cancel.
220#
221def do_save(win):
222 try:
223 if win.filename == '':
224 win.filename = stdwin.askfile('Open file', '', 1)
225 f = open(win.filename, 'w')
226 f.write(win.editor.gettext())
227 #
228 except KeyboardInterrupt:
229 pass # Don't give an error on cancel.
230
231def do_saveas(win):
232 currentFilename = win.filename
233 win.filename = ''
234 do_save(win) # Use do_save with empty filename
235 if win.filename == '': # Restore the name if do_save did not set it.
236 win.filename = currentFilename
237#
238def do_close(win):
239 if win.busy:
240 stdwin.message('Can\'t close busy window')
241 return # need to fail if quitting??
242 win.editor = None # Break circular reference
243 #del win.editmenu # What about the filemenu??
244 try:
245 os.unlink(win.outfile)
246 except os.error:
247 pass
248 mainloop.unregister(win)
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000249 win.close()
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000250#
251def do_quit(win):
252 # Call win.dispatch instead of do_close because there
253 # may be 'alien' windows in the list.
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000254 for win in mainloop.windows[:]:
255 mainloop.dispatch((WE_CLOSE, win, None)) # need to catch failed close
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000256
257
258# Edit menu handlers
259#
260def do_cut(win):
261 text = win.editor.getfocustext()
262 if not text:
263 stdwin.fleep()
264 return
265 stdwin.setcutbuffer(0, text)
266 replace(win, '')
267#
268def do_copy(win):
269 text = win.editor.getfocustext()
270 if not text:
271 stdwin.fleep()
272 return
273 stdwin.setcutbuffer(0, text)
274#
275def do_paste(win):
276 text = stdwin.getcutbuffer(0)
277 if not text:
278 stdwin.fleep()
279 return
280 replace(win, text)
281#
282def do_clear(win):
283 replace(win, '')
284
285#
286# These would be better in a preferences dialog:
287def do_auto(win):
288 win.auto = (not win.auto)
289 win.editmenu.check(win.iauto, win.auto)
290#
291def do_insertOutputOption(win):
292 win.insertOutput = (not win.insertOutput)
293 title = ['Append Output', 'Insert Output'][win.insertOutput]
294 win.editmenu.setitem(win.insertOutputNum, title)
295#
296def do_insertErrorOption(win):
297 win.insertError = (not win.insertError)
298 title = ['Error Dialog', 'Insert Error'][win.insertError]
299 win.editmenu.setitem(win.insertErrorNum, title)
300
301
302# Extract a command from the editor and execute it, or pass input to
303# an interpreter waiting for it.
304# Incomplete commands are merely placed in the window's command buffer.
305# All exceptions occurring during the execution are caught and reported.
306# (Tracebacks are currently not possible, as the interpreter does not
307# save the traceback pointer until it reaches its outermost level.)
308#
309def do_exec(win):
310 if win.busy:
311 if win not in inputwindows:
312 stdwin.message('Can\'t run recursive commands')
313 return
314 if win <> inputwindows[0]:
315 stdwin.message( \
316 'Please complete recursive input first')
317 return
318 #
319 # Set text to the string to execute.
320 a, b = win.editor.getfocus()
321 alltext = win.editor.gettext()
322 n = len(alltext)
323 if a == b:
324 # There is no selected text, just an insert point;
325 # so execute the current line.
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000326 while 0 < a and alltext[a-1] <> '\n': a = a-1 # Find beginning of line.
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000327 while b < n and alltext[b] <> '\n': # Find end of line after b.
328 b = b+1
329 text = alltext[a:b] + '\n'
330 else:
331 # Execute exactly the selected text.
332 text = win.editor.getfocustext()
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000333 if text[-1:] <> '\n': # Make sure text ends with \n.
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000334 text = text + '\n'
335 while b < n and alltext[b] <> '\n': # Find end of line after b.
336 b = b+1
337 #
338 # Set the focus to expect the output, since there is always something.
339 # Output will be inserted at end of line after current focus,
340 # or appended to the end of the text.
341 b = [n, b][win.insertOutput]
342 win.editor.setfocus(b, b)
343 #
344 # Make sure there is a preceeding newline.
345 if alltext[b-1:b] <> '\n':
346 win.editor.replace('\n')
347 #
348 #
349 if win.busy:
350 # Send it to raw_input() below
351 raise InputAvailable, (None, text)
352 #
353 # Like the real Python interpreter, we want to execute
354 # single-line commands immediately, but save multi-line
355 # commands until they are terminated by a blank line.
356 # Unlike the real Python interpreter, we don't do any syntax
357 # checking while saving up parts of a multi-line command.
358 #
359 # The current heuristic to determine whether a command is
360 # the first line of a multi-line command simply checks whether
361 # the command ends in a colon (followed by a newline).
362 # This is not very robust (comments and continuations will
363 # confuse it), but it is usable, and simple to implement.
364 # (It even has the advantage that single-line loops etc.
365 # don't need te be terminated by a blank line.)
366 #
367 if win.command:
368 # Already continuing
369 win.command = win.command + text
370 if win.command[-2:] <> '\n\n':
371 win.settitle('Unfinished command...')
372 return # Need more...
373 else:
374 # New command
375 win.command = text
376 if text[-2:] == ':\n':
377 win.settitle('Unfinished command...')
378 return
379 command = win.command
380 win.command = ''
381 win.settitle('Executing command...')
382 #
383 # Some hacks: sys.stdout is temporarily redirected to a file,
384 # so we can intercept the command's output and insert it
385 # in the editor window; the built-in function raw_input
386 # and input() are replaced by out versions;
387 # and a second, undocumented argument
388 # to exec() is used to specify the directory holding the
389 # user's global variables. (If this wasn't done, the
390 # exec would be executed in the current local environment,
391 # and the user's assignments to globals would be lost...)
392 #
393 save_input = builtin.input
394 save_raw_input = builtin.raw_input
395 save_stdout = sys.stdout
396 save_stderr = sys.stderr
397 iwin = Input().init(win)
398 try:
399 builtin.input = iwin.input
400 builtin.raw_input = iwin.raw_input
401 sys.stdout = sys.stderr = open(win.outfile, 'w')
402 win.busy = 1
403 try:
404 exec(command, win.globals)
405 except KeyboardInterrupt:
406 pass # Don't give an error.
407 except:
408 msg = sys.exc_type
409 if sys.exc_value <> None:
410 msg = msg + ': ' + `sys.exc_value`
411 if win.insertError:
412 stdwin.fleep()
413 replace(win, msg + '\n')
414 else:
415 win.settitle('Unhandled exception')
416 stdwin.message(msg)
417 finally:
418 # Restore redirected I/O in *all* cases
419 win.busy = 0
420 sys.stderr = save_stderr
421 sys.stdout = save_stdout
422 builtin.raw_input = save_raw_input
423 builtin.input = save_input
424 settitle(win)
425 getoutput(win)
426
427
428# Read any output the command may have produced back from the file
429# and show it. Optionally insert it after the focus, like MPW does,
430# or always append at the end.
431#
432def getoutput(win):
433 filename = win.outfile
434 try:
435 fp = open(filename, 'r')
436 except:
437 stdwin.message('Can\'t read output from ' + filename)
438 return
439 #out = fp.read() # Not in Python 0.9.1
440 out = fp.read(10000) # For Python 0.9.1
441 del fp # Close it
442 if out or win.insertOutput:
443 replace(win, out)
444
445
446# Implementation of input() and raw_input().
447# This uses a class only because we must support calls
448# with and without arguments; this can't be done normally in Python,
449# but the extra, implicit argument for instance methods does the trick.
450#
451class Input:
452 #
453 def init(self, win):
454 self.win = win
455 return self
456 #
457 def input(args):
458 # Hack around call with or without argument:
459 if type(args) == type(()):
460 self, prompt = args
461 else:
462 self, prompt = args, ''
463 #
464 return eval(self.raw_input(prompt), self.win.globals)
465 #
466 def raw_input(args):
467 # Hack around call with or without argument:
468 if type(args) == type(()):
469 self, prompt = args
470 else:
471 self, prompt = args, ''
472 #
473 print prompt # Need to terminate with newline.
474 sys.stdout.close()
475 sys.stdout = sys.stderr = None
476 getoutput(self.win)
477 sys.stdout = sys.stderr = open(self.win.outfile, 'w')
478 save_title = self.win.gettitle()
479 n = len(inputwindows)
480 title = n*'(' + 'Requesting input...' + ')'*n
481 self.win.settitle(title)
482 inputwindows.insert(0, self.win)
483 try:
Guido van Rossumcb4b2951992-05-15 15:40:30 +0000484 try:
485 mainloop.mainloop()
486 except InputAvailable, (exc, val): # See do_exec above.
487 if exc:
488 raise exc, val
489 if val[-1:] == '\n':
490 val = val[:-1]
491 return val
Guido van Rossum9cf8f331992-03-30 10:54:51 +0000492 finally:
493 del inputwindows[0]
494 self.win.settitle(save_title)
495 # If we don't catch InputAvailable, something's wrong...
496 raise EOFError
497 #
498
499
500# Currently unused function to test a command's syntax without executing it
501#
502def testsyntax(s):
503 import string
504 lines = string.splitfields(s, '\n')
505 for i in range(len(lines)): lines[i] = '\t' + lines[i]
506 lines.insert(0, 'if 0:')
507 lines.append('')
508 exec(string.joinfields(lines, '\n'))
509
510
511# Call the main program.
512#
513main()
Guido van Rossum63f4cdc1992-12-14 14:10:53 +0000514
515
516# This was originally coded on a Mac, so...
517# Local variables:
518# py-indent-offset: 4
519# tab-width: 4
520# end: