1 #! /usr/local/bin/python
3 # A STDWIN-based front end for the Python interpreter.
5 # This is useful if you want to avoid console I/O and instead
6 # use text windows to issue commands to the interpreter.
8 # It supports multiple interpreter windows, each with its own context.
12 # 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.
17 # Although this supports multiple windows, the whole application
18 # is deaf and dumb when a command is running in one window.
20 # Interrupt is (ab)used to signal EOF on input requests.
22 # On UNIX (using X11), interrupts typed in the window will not be
23 # 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.)
30 from stdwinevents
import *
36 # Stack of windows waiting for [raw_]input().
37 # Element [0] is the top.
38 # If there are multiple windows waiting for input, only the
39 # one on top of the stack can accept input, because the way
40 # raw_input() is implemented (using recursive mainloop() calls).
45 # Exception raised when input is available
47 InputAvailable
= 'input available for raw_input (not an error)'
50 # Main program -- create the window and call the mainloop
53 # Hack so 'import python' won't load another copy
54 # of this if we were loaded though 'python python.py'.
55 # (Should really look at sys.argv[0]...)
56 if 'inputwindows' in dir(sys
.modules
['__main__']) and \
57 sys
.modules
['__main__'].inputwindows
is inputwindows
:
58 sys
.modules
['python'] = sys
.modules
['__main__']
67 # stdwin.setdefscrollbars(0, 1) # Not in Python 0.9.1
68 # stdwin.setfont('monaco') # Not on UNIX! and not Python 0.9.1
69 # width, height = stdwin.textwidth('in')*40, stdwin.lineheight()*24
70 # stdwin.setdefwinsize(width, height)
71 win
= stdwin
.open('Python interpreter ready')
72 win
.editor
= win
.textcreate((0,0), win
.getwinsize())
73 win
.globals = {} # Dictionary for user's globals
74 win
.command
= '' # Partially read command
75 win
.busy
= 0 # Ready to accept a command
76 win
.auto
= 1 # [CR] executes command
77 win
.insertOutput
= 1 # Insert output at focus
78 win
.insertError
= 1 # Insert error output at focus
79 win
.setwincursor('ibeam')
80 win
.filename
= '' # Empty if no file for this window
83 win
.dispatch
= pdispatch
# Event dispatch function
84 mainloop
.register(win
)
90 def makefilemenu(win
):
91 win
.filemenu
= mp
= win
.menucreate('File')
93 additem(mp
, 'New', 'N', do_new
)
94 additem(mp
, 'Open...', 'O', do_open
)
95 additem(mp
, '', '', None)
96 additem(mp
, 'Close', 'W', do_close
)
97 additem(mp
, 'Save', 'S', do_save
)
98 additem(mp
, 'Save as...', '', do_saveas
)
99 additem(mp
, '', '', None)
100 additem(mp
, 'Quit', 'Q', do_quit
)
103 # Make an 'Edit' menu
105 def makeeditmenu(win
):
106 win
.editmenu
= mp
= win
.menucreate('Edit')
108 additem(mp
, 'Cut', 'X', do_cut
)
109 additem(mp
, 'Copy', 'C', do_copy
)
110 additem(mp
, 'Paste', 'V', do_paste
)
111 additem(mp
, 'Clear', '', do_clear
)
112 additem(mp
, '', '', None)
113 win
.iauto
= len(mp
.callback
)
114 additem(mp
, 'Autoexecute', '', do_auto
)
115 mp
.check(win
.iauto
, win
.auto
)
116 win
.insertOutputNum
= len(mp
.callback
)
117 additem(mp
, 'Insert Output', '', do_insertOutputOption
)
118 win
.insertErrorNum
= len(mp
.callback
)
119 additem(mp
, 'Insert Error', '', do_insertErrorOption
)
120 additem(mp
, 'Exec', '\r', do_exec
)
123 # Helper to add a menu item and callback function
125 def additem(mp
, text
, shortcut
, handler
):
127 mp
.additem(text
, shortcut
)
130 mp
.callback
.append(handler
)
133 # Dispatch a single event to the interpreter.
134 # Resize events cause a resize of the editor.
135 # Some events are treated specially.
136 # Most other events are passed directly to the editor.
138 def pdispatch(event
):
139 type, win
, detail
= event
141 win
= stdwin
.getactive()
146 elif type == WE_SIZE
:
147 win
.editor
.move((0, 0), win
.getwinsize())
148 elif type == WE_COMMAND
and detail
== WC_RETURN
:
152 void
= win
.editor
.event(event
)
153 elif type == WE_COMMAND
and detail
== WC_CANCEL
:
155 raise KeyboardInterrupt
159 elif type == WE_MENU
:
161 mp
.callback
[item
](win
)
163 void
= win
.editor
.event(event
)
164 if win
in mainloop
.windows
:
165 # May have been deleted by close...
166 win
.setdocsize(0, win
.editor
.getrect()[1][1])
167 if type in (WE_CHAR
, WE_COMMAND
):
168 win
.editor
.setfocus(win
.editor
.getfocus())
171 # Helper to set the title of the window
174 if win
.filename
== '':
175 win
.settitle('Python interpreter ready')
177 win
.settitle(win
.filename
)
180 # Helper to replace the text of the focus
182 def replace(win
, text
):
183 win
.editor
.replace(text
)
184 # Resize the window to display the text
185 win
.setdocsize(0, win
.editor
.getrect()[1][1]) # update the size before
186 win
.editor
.setfocus(win
.editor
.getfocus()) # move focus to the change
196 filename
= stdwin
.askfile('Open file', '', 0)
198 win
.filename
= filename
199 win
.editor
.replace(open(filename
, 'r').read())
200 win
.editor
.setfocus(0, 0)
201 win
.settitle(win
.filename
)
203 except KeyboardInterrupt:
204 pass # Don't give an error on cancel
208 if win
.filename
== '':
209 win
.filename
= stdwin
.askfile('Open file', '', 1)
210 f
= open(win
.filename
, 'w')
211 f
.write(win
.editor
.gettext())
213 except KeyboardInterrupt:
214 pass # Don't give an error on cancel
217 currentFilename
= win
.filename
219 do_save(win
) # Use do_save with empty filename
220 if win
.filename
== '': # Restore the name if do_save did not set it
221 win
.filename
= currentFilename
225 stdwin
.message('Can\'t close busy window')
226 return # need to fail if quitting??
227 win
.editor
= None # Break circular reference
228 #del win.editmenu # What about the filemenu??
229 mainloop
.unregister(win
)
233 # Call win.dispatch instead of do_close because there
234 # may be 'alien' windows in the list.
235 for win
in mainloop
.windows
[:]:
236 mainloop
.dispatch((WE_CLOSE
, win
, None))
237 # need to catch failed close
243 text
= win
.editor
.getfocustext()
247 stdwin
.setcutbuffer(0, text
)
251 text
= win
.editor
.getfocustext()
255 stdwin
.setcutbuffer(0, text
)
258 text
= stdwin
.getcutbuffer(0)
268 # These would be better in a preferences dialog:
271 win
.auto
= (not win
.auto
)
272 win
.editmenu
.check(win
.iauto
, win
.auto
)
274 def do_insertOutputOption(win
):
275 win
.insertOutput
= (not win
.insertOutput
)
276 title
= ['Append Output', 'Insert Output'][win
.insertOutput
]
277 win
.editmenu
.setitem(win
.insertOutputNum
, title
)
279 def do_insertErrorOption(win
):
280 win
.insertError
= (not win
.insertError
)
281 title
= ['Error Dialog', 'Insert Error'][win
.insertError
]
282 win
.editmenu
.setitem(win
.insertErrorNum
, title
)
285 # Extract a command from the editor and execute it, or pass input to
286 # an interpreter waiting for it.
287 # Incomplete commands are merely placed in the window's command buffer.
288 # All exceptions occurring during the execution are caught and reported.
289 # (Tracebacks are currently not possible, as the interpreter does not
290 # save the traceback pointer until it reaches its outermost level.)
294 if win
not in inputwindows
:
295 stdwin
.message('Can\'t run recursive commands')
297 if win
<> inputwindows
[0]:
298 stdwin
.message('Please complete recursive input first')
301 # Set text to the string to execute.
302 a
, b
= win
.editor
.getfocus()
303 alltext
= win
.editor
.gettext()
306 # There is no selected text, just an insert point;
307 # so execute the current line.
308 while 0 < a
and alltext
[a
-1] <> '\n': # Find beginning of line
310 while b
< n
and alltext
[b
] <> '\n': # Find end of line after b
312 text
= alltext
[a
:b
] + '\n'
314 # Execute exactly the selected text.
315 text
= win
.editor
.getfocustext()
316 if text
[-1:] <> '\n': # Make sure text ends with \n
318 while b
< n
and alltext
[b
] <> '\n': # Find end of line after b
321 # Set the focus to expect the output, since there is always something.
322 # Output will be inserted at end of line after current focus,
323 # or appended to the end of the text.
324 b
= [n
, b
][win
.insertOutput
]
325 win
.editor
.setfocus(b
, b
)
327 # Make sure there is a preceeding newline.
328 if alltext
[b
-1:b
] <> '\n':
329 win
.editor
.replace('\n')
333 # Send it to raw_input() below
334 raise InputAvailable
, text
336 # Like the real Python interpreter, we want to execute
337 # single-line commands immediately, but save multi-line
338 # commands until they are terminated by a blank line.
339 # Unlike the real Python interpreter, we don't do any syntax
340 # checking while saving up parts of a multi-line command.
342 # The current heuristic to determine whether a command is
343 # the first line of a multi-line command simply checks whether
344 # the command ends in a colon (followed by a newline).
345 # This is not very robust (comments and continuations will
346 # confuse it), but it is usable, and simple to implement.
347 # (It even has the advantage that single-line loops etc.
348 # don't need te be terminated by a blank line.)
352 win
.command
= win
.command
+ text
353 if win
.command
[-2:] <> '\n\n':
354 win
.settitle('Unfinished command...')
355 return # Need more...
359 if text
[-2:] == ':\n':
360 win
.settitle('Unfinished command...')
362 command
= win
.command
364 win
.settitle('Executing command...')
367 # - The standard files are replaced by an IOWindow instance.
368 # - A 2nd argument to exec() is used to specify the directory
369 # holding the user's global variables. (If this wasn't done,
370 # the exec would be executed in the current local environment,
371 # and the user's assignments to globals would be lost...)
373 save_stdin
= sys
.stdin
374 save_stdout
= sys
.stdout
375 save_stderr
= sys
.stderr
377 sys
.stdin
= sys
.stdout
= sys
.stderr
= IOWindow(win
)
380 exec(command
, win
.globals)
381 except KeyboardInterrupt:
384 if type(sys
.exc_type
) == type(''):
386 else: msg
= sys
.exc_type
.__name
__
387 if sys
.exc_value
<> None:
388 msg
= msg
+ ': ' + `sys
.exc_value`
391 replace(win
, msg
+ '\n')
393 win
.settitle('Unhandled exception')
396 # Restore redirected I/O in *all* cases
398 sys
.stderr
= save_stderr
399 sys
.stdout
= save_stdout
400 sys
.stdin
= save_stdin
404 # Class emulating file I/O from/to a window
408 def __init__(self
, win
):
411 def readline(self
, *unused_args
):
412 n
= len(inputwindows
)
413 save_title
= self
.win
.gettitle()
414 title
= n
*'(' + 'Requesting input...' + ')'*n
415 self
.win
.settitle(title
)
416 inputwindows
.insert(0, self
.win
)
422 self
.win
.settitle(save_title
)
423 except InputAvailable
, val
: # See do_exec above
425 except KeyboardInterrupt:
426 raise EOFError # Until we have a "send EOF" key
427 # If we didn't catch InputAvailable, something's wrong...
430 def write(self
, text
):
432 replace(self
.win
, text
)
436 # Currently unused function to test a command's syntax without executing it
440 lines
= string
.splitfields(s
, '\n')
441 for i
in range(len(lines
)): lines
[i
] = '\t' + lines
[i
]
442 lines
.insert(0, 'if 0:')
444 exec(string
.joinfields(lines
, '\n'))
447 # Call the main program