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