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