source: python/trunk/Lib/idlelib/PyShell.py@ 348

Last change on this file since 348 was 2, checked in by Yuri Dario, 15 years ago

Initial import for vendor code.

  • Property svn:eol-style set to native
File size: 49.7 KB
Line 
1#! /usr/bin/env python
2
3import os
4import os.path
5import sys
6import string
7import getopt
8import re
9import socket
10import time
11import threading
12import traceback
13import types
14import macosxSupport
15
16import linecache
17from code import InteractiveInterpreter
18
19try:
20 from Tkinter import *
21except ImportError:
22 print>>sys.__stderr__, "** IDLE can't import Tkinter. " \
23 "Your Python may not be configured for Tk. **"
24 sys.exit(1)
25import tkMessageBox
26
27from EditorWindow import EditorWindow, fixwordbreaks
28from FileList import FileList
29from ColorDelegator import ColorDelegator
30from UndoDelegator import UndoDelegator
31from OutputWindow import OutputWindow
32from configHandler import idleConf
33import idlever
34
35import rpc
36import Debugger
37import RemoteDebugger
38
39IDENTCHARS = string.ascii_letters + string.digits + "_"
40LOCALHOST = '127.0.0.1'
41
42try:
43 from signal import SIGTERM
44except ImportError:
45 SIGTERM = 15
46
47# Override warnings module to write to warning_stream. Initialize to send IDLE
48# internal warnings to the console. ScriptBinding.check_syntax() will
49# temporarily redirect the stream to the shell window to display warnings when
50# checking user's code.
51global warning_stream
52warning_stream = sys.__stderr__
53try:
54 import warnings
55except ImportError:
56 pass
57else:
58 def idle_showwarning(message, category, filename, lineno,
59 file=None, line=None):
60 file = warning_stream
61 try:
62 file.write(warnings.formatwarning(message, category, filename,\
63 lineno, file=file, line=line))
64 except IOError:
65 pass ## file (probably __stderr__) is invalid, warning dropped.
66 warnings.showwarning = idle_showwarning
67 def idle_formatwarning(message, category, filename, lineno,
68 file=None, line=None):
69 """Format warnings the IDLE way"""
70 s = "\nWarning (from warnings module):\n"
71 s += ' File \"%s\", line %s\n' % (filename, lineno)
72 line = linecache.getline(filename, lineno).strip() \
73 if line is None else line
74 if line:
75 s += " %s\n" % line
76 s += "%s: %s\n>>> " % (category.__name__, message)
77 return s
78 warnings.formatwarning = idle_formatwarning
79
80def extended_linecache_checkcache(filename=None,
81 orig_checkcache=linecache.checkcache):
82 """Extend linecache.checkcache to preserve the <pyshell#...> entries
83
84 Rather than repeating the linecache code, patch it to save the
85 <pyshell#...> entries, call the original linecache.checkcache()
86 (which destroys them), and then restore the saved entries.
87
88 orig_checkcache is bound at definition time to the original
89 method, allowing it to be patched.
90
91 """
92 cache = linecache.cache
93 save = {}
94 for filename in cache.keys():
95 if filename[:1] + filename[-1:] == '<>':
96 save[filename] = cache[filename]
97 orig_checkcache()
98 cache.update(save)
99
100# Patch linecache.checkcache():
101linecache.checkcache = extended_linecache_checkcache
102
103
104class PyShellEditorWindow(EditorWindow):
105 "Regular text edit window in IDLE, supports breakpoints"
106
107 def __init__(self, *args):
108 self.breakpoints = []
109 EditorWindow.__init__(self, *args)
110 self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
111 self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
112 self.text.bind("<<open-python-shell>>", self.flist.open_shell)
113
114 self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
115 'breakpoints.lst')
116 # whenever a file is changed, restore breakpoints
117 if self.io.filename: self.restore_file_breaks()
118 def filename_changed_hook(old_hook=self.io.filename_change_hook,
119 self=self):
120 self.restore_file_breaks()
121 old_hook()
122 self.io.set_filename_change_hook(filename_changed_hook)
123
124 rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
125 ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
126
127 def set_breakpoint(self, lineno):
128 text = self.text
129 filename = self.io.filename
130 text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
131 try:
132 i = self.breakpoints.index(lineno)
133 except ValueError: # only add if missing, i.e. do once
134 self.breakpoints.append(lineno)
135 try: # update the subprocess debugger
136 debug = self.flist.pyshell.interp.debugger
137 debug.set_breakpoint_here(filename, lineno)
138 except: # but debugger may not be active right now....
139 pass
140
141 def set_breakpoint_here(self, event=None):
142 text = self.text
143 filename = self.io.filename
144 if not filename:
145 text.bell()
146 return
147 lineno = int(float(text.index("insert")))
148 self.set_breakpoint(lineno)
149
150 def clear_breakpoint_here(self, event=None):
151 text = self.text
152 filename = self.io.filename
153 if not filename:
154 text.bell()
155 return
156 lineno = int(float(text.index("insert")))
157 try:
158 self.breakpoints.remove(lineno)
159 except:
160 pass
161 text.tag_remove("BREAK", "insert linestart",\
162 "insert lineend +1char")
163 try:
164 debug = self.flist.pyshell.interp.debugger
165 debug.clear_breakpoint_here(filename, lineno)
166 except:
167 pass
168
169 def clear_file_breaks(self):
170 if self.breakpoints:
171 text = self.text
172 filename = self.io.filename
173 if not filename:
174 text.bell()
175 return
176 self.breakpoints = []
177 text.tag_remove("BREAK", "1.0", END)
178 try:
179 debug = self.flist.pyshell.interp.debugger
180 debug.clear_file_breaks(filename)
181 except:
182 pass
183
184 def store_file_breaks(self):
185 "Save breakpoints when file is saved"
186 # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
187 # be run. The breaks are saved at that time. If we introduce
188 # a temporary file save feature the save breaks functionality
189 # needs to be re-verified, since the breaks at the time the
190 # temp file is created may differ from the breaks at the last
191 # permanent save of the file. Currently, a break introduced
192 # after a save will be effective, but not persistent.
193 # This is necessary to keep the saved breaks synched with the
194 # saved file.
195 #
196 # Breakpoints are set as tagged ranges in the text. Certain
197 # kinds of edits cause these ranges to be deleted: Inserting
198 # or deleting a line just before a breakpoint, and certain
199 # deletions prior to a breakpoint. These issues need to be
200 # investigated and understood. It's not clear if they are
201 # Tk issues or IDLE issues, or whether they can actually
202 # be fixed. Since a modified file has to be saved before it is
203 # run, and since self.breakpoints (from which the subprocess
204 # debugger is loaded) is updated during the save, the visible
205 # breaks stay synched with the subprocess even if one of these
206 # unexpected breakpoint deletions occurs.
207 breaks = self.breakpoints
208 filename = self.io.filename
209 try:
210 lines = open(self.breakpointPath,"r").readlines()
211 except IOError:
212 lines = []
213 new_file = open(self.breakpointPath,"w")
214 for line in lines:
215 if not line.startswith(filename + '='):
216 new_file.write(line)
217 self.update_breakpoints()
218 breaks = self.breakpoints
219 if breaks:
220 new_file.write(filename + '=' + str(breaks) + '\n')
221 new_file.close()
222
223 def restore_file_breaks(self):
224 self.text.update() # this enables setting "BREAK" tags to be visible
225 filename = self.io.filename
226 if filename is None:
227 return
228 if os.path.isfile(self.breakpointPath):
229 lines = open(self.breakpointPath,"r").readlines()
230 for line in lines:
231 if line.startswith(filename + '='):
232 breakpoint_linenumbers = eval(line[len(filename)+1:])
233 for breakpoint_linenumber in breakpoint_linenumbers:
234 self.set_breakpoint(breakpoint_linenumber)
235
236 def update_breakpoints(self):
237 "Retrieves all the breakpoints in the current window"
238 text = self.text
239 ranges = text.tag_ranges("BREAK")
240 linenumber_list = self.ranges_to_linenumbers(ranges)
241 self.breakpoints = linenumber_list
242
243 def ranges_to_linenumbers(self, ranges):
244 lines = []
245 for index in range(0, len(ranges), 2):
246 lineno = int(float(ranges[index]))
247 end = int(float(ranges[index+1]))
248 while lineno < end:
249 lines.append(lineno)
250 lineno += 1
251 return lines
252
253# XXX 13 Dec 2002 KBK Not used currently
254# def saved_change_hook(self):
255# "Extend base method - clear breaks if module is modified"
256# if not self.get_saved():
257# self.clear_file_breaks()
258# EditorWindow.saved_change_hook(self)
259
260 def _close(self):
261 "Extend base method - clear breaks when module is closed"
262 self.clear_file_breaks()
263 EditorWindow._close(self)
264
265
266class PyShellFileList(FileList):
267 "Extend base class: IDLE supports a shell and breakpoints"
268
269 # override FileList's class variable, instances return PyShellEditorWindow
270 # instead of EditorWindow when new edit windows are created.
271 EditorWindow = PyShellEditorWindow
272
273 pyshell = None
274
275 def open_shell(self, event=None):
276 if self.pyshell:
277 self.pyshell.top.wakeup()
278 else:
279 self.pyshell = PyShell(self)
280 if self.pyshell:
281 if not self.pyshell.begin():
282 return None
283 return self.pyshell
284
285
286class ModifiedColorDelegator(ColorDelegator):
287 "Extend base class: colorizer for the shell window itself"
288
289 def __init__(self):
290 ColorDelegator.__init__(self)
291 self.LoadTagDefs()
292
293 def recolorize_main(self):
294 self.tag_remove("TODO", "1.0", "iomark")
295 self.tag_add("SYNC", "1.0", "iomark")
296 ColorDelegator.recolorize_main(self)
297
298 def LoadTagDefs(self):
299 ColorDelegator.LoadTagDefs(self)
300 theme = idleConf.GetOption('main','Theme','name')
301 self.tagdefs.update({
302 "stdin": {'background':None,'foreground':None},
303 "stdout": idleConf.GetHighlight(theme, "stdout"),
304 "stderr": idleConf.GetHighlight(theme, "stderr"),
305 "console": idleConf.GetHighlight(theme, "console"),
306 })
307
308class ModifiedUndoDelegator(UndoDelegator):
309 "Extend base class: forbid insert/delete before the I/O mark"
310
311 def insert(self, index, chars, tags=None):
312 try:
313 if self.delegate.compare(index, "<", "iomark"):
314 self.delegate.bell()
315 return
316 except TclError:
317 pass
318 UndoDelegator.insert(self, index, chars, tags)
319
320 def delete(self, index1, index2=None):
321 try:
322 if self.delegate.compare(index1, "<", "iomark"):
323 self.delegate.bell()
324 return
325 except TclError:
326 pass
327 UndoDelegator.delete(self, index1, index2)
328
329
330class MyRPCClient(rpc.RPCClient):
331
332 def handle_EOF(self):
333 "Override the base class - just re-raise EOFError"
334 raise EOFError
335
336
337class ModifiedInterpreter(InteractiveInterpreter):
338
339 def __init__(self, tkconsole):
340 self.tkconsole = tkconsole
341 locals = sys.modules['__main__'].__dict__
342 InteractiveInterpreter.__init__(self, locals=locals)
343 self.save_warnings_filters = None
344 self.restarting = False
345 self.subprocess_arglist = self.build_subprocess_arglist()
346
347 port = 8833
348 rpcclt = None
349 rpcpid = None
350
351 def spawn_subprocess(self):
352 args = self.subprocess_arglist
353 self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
354
355 def build_subprocess_arglist(self):
356 w = ['-W' + s for s in sys.warnoptions]
357 if 1/2 > 0: # account for new division
358 w.append('-Qnew')
359 # Maybe IDLE is installed and is being accessed via sys.path,
360 # or maybe it's not installed and the idle.py script is being
361 # run from the IDLE source directory.
362 del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc',
363 default=False, type='bool')
364 if __name__ == 'idlelib.PyShell':
365 command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
366 else:
367 command = "__import__('run').main(%r)" % (del_exitf,)
368 if sys.platform[:3] == 'win' and ' ' in sys.executable:
369 # handle embedded space in path by quoting the argument
370 decorated_exec = '"%s"' % sys.executable
371 else:
372 decorated_exec = sys.executable
373 return [decorated_exec] + w + ["-c", command, str(self.port)]
374
375 def start_subprocess(self):
376 # spawning first avoids passing a listening socket to the subprocess
377 self.spawn_subprocess()
378 #time.sleep(20) # test to simulate GUI not accepting connection
379 addr = (LOCALHOST, self.port)
380 # Idle starts listening for connection on localhost
381 for i in range(3):
382 time.sleep(i)
383 try:
384 self.rpcclt = MyRPCClient(addr)
385 break
386 except socket.error, err:
387 pass
388 else:
389 self.display_port_binding_error()
390 return None
391 # Accept the connection from the Python execution server
392 self.rpcclt.listening_sock.settimeout(10)
393 try:
394 self.rpcclt.accept()
395 except socket.timeout, err:
396 self.display_no_subprocess_error()
397 return None
398 self.rpcclt.register("stdin", self.tkconsole)
399 self.rpcclt.register("stdout", self.tkconsole.stdout)
400 self.rpcclt.register("stderr", self.tkconsole.stderr)
401 self.rpcclt.register("flist", self.tkconsole.flist)
402 self.rpcclt.register("linecache", linecache)
403 self.rpcclt.register("interp", self)
404 self.transfer_path()
405 self.poll_subprocess()
406 return self.rpcclt
407
408 def restart_subprocess(self):
409 if self.restarting:
410 return self.rpcclt
411 self.restarting = True
412 # close only the subprocess debugger
413 debug = self.getdebugger()
414 if debug:
415 try:
416 # Only close subprocess debugger, don't unregister gui_adap!
417 RemoteDebugger.close_subprocess_debugger(self.rpcclt)
418 except:
419 pass
420 # Kill subprocess, spawn a new one, accept connection.
421 self.rpcclt.close()
422 self.unix_terminate()
423 console = self.tkconsole
424 was_executing = console.executing
425 console.executing = False
426 self.spawn_subprocess()
427 try:
428 self.rpcclt.accept()
429 except socket.timeout, err:
430 self.display_no_subprocess_error()
431 return None
432 self.transfer_path()
433 # annotate restart in shell window and mark it
434 console.text.delete("iomark", "end-1c")
435 if was_executing:
436 console.write('\n')
437 console.showprompt()
438 halfbar = ((int(console.width) - 16) // 2) * '='
439 console.write(halfbar + ' RESTART ' + halfbar)
440 console.text.mark_set("restart", "end-1c")
441 console.text.mark_gravity("restart", "left")
442 console.showprompt()
443 # restart subprocess debugger
444 if debug:
445 # Restarted debugger connects to current instance of debug GUI
446 gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
447 # reload remote debugger breakpoints for all PyShellEditWindows
448 debug.load_breakpoints()
449 self.restarting = False
450 return self.rpcclt
451
452 def __request_interrupt(self):
453 self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
454
455 def interrupt_subprocess(self):
456 threading.Thread(target=self.__request_interrupt).start()
457
458 def kill_subprocess(self):
459 try:
460 self.rpcclt.close()
461 except AttributeError: # no socket
462 pass
463 self.unix_terminate()
464 self.tkconsole.executing = False
465 self.rpcclt = None
466
467 def unix_terminate(self):
468 "UNIX: make sure subprocess is terminated and collect status"
469 if hasattr(os, 'kill'):
470 try:
471 os.kill(self.rpcpid, SIGTERM)
472 except OSError:
473 # process already terminated:
474 return
475 else:
476 try:
477 os.waitpid(self.rpcpid, 0)
478 except OSError:
479 return
480
481 def transfer_path(self):
482 self.runcommand("""if 1:
483 import sys as _sys
484 _sys.path = %r
485 del _sys
486 \n""" % (sys.path,))
487
488 active_seq = None
489
490 def poll_subprocess(self):
491 clt = self.rpcclt
492 if clt is None:
493 return
494 try:
495 response = clt.pollresponse(self.active_seq, wait=0.05)
496 except (EOFError, IOError, KeyboardInterrupt):
497 # lost connection or subprocess terminated itself, restart
498 # [the KBI is from rpc.SocketIO.handle_EOF()]
499 if self.tkconsole.closing:
500 return
501 response = None
502 self.restart_subprocess()
503 if response:
504 self.tkconsole.resetoutput()
505 self.active_seq = None
506 how, what = response
507 console = self.tkconsole.console
508 if how == "OK":
509 if what is not None:
510 print >>console, repr(what)
511 elif how == "EXCEPTION":
512 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
513 self.remote_stack_viewer()
514 elif how == "ERROR":
515 errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
516 print >>sys.__stderr__, errmsg, what
517 print >>console, errmsg, what
518 # we received a response to the currently active seq number:
519 try:
520 self.tkconsole.endexecuting()
521 except AttributeError: # shell may have closed
522 pass
523 # Reschedule myself
524 if not self.tkconsole.closing:
525 self.tkconsole.text.after(self.tkconsole.pollinterval,
526 self.poll_subprocess)
527
528 debugger = None
529
530 def setdebugger(self, debugger):
531 self.debugger = debugger
532
533 def getdebugger(self):
534 return self.debugger
535
536 def open_remote_stack_viewer(self):
537 """Initiate the remote stack viewer from a separate thread.
538
539 This method is called from the subprocess, and by returning from this
540 method we allow the subprocess to unblock. After a bit the shell
541 requests the subprocess to open the remote stack viewer which returns a
542 static object looking at the last exceptiopn. It is queried through
543 the RPC mechanism.
544
545 """
546 self.tkconsole.text.after(300, self.remote_stack_viewer)
547 return
548
549 def remote_stack_viewer(self):
550 import RemoteObjectBrowser
551 oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {})
552 if oid is None:
553 self.tkconsole.root.bell()
554 return
555 item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
556 from TreeWidget import ScrolledCanvas, TreeNode
557 top = Toplevel(self.tkconsole.root)
558 theme = idleConf.GetOption('main','Theme','name')
559 background = idleConf.GetHighlight(theme, 'normal')['background']
560 sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
561 sc.frame.pack(expand=1, fill="both")
562 node = TreeNode(sc.canvas, None, item)
563 node.expand()
564 # XXX Should GC the remote tree when closing the window
565
566 gid = 0
567
568 def execsource(self, source):
569 "Like runsource() but assumes complete exec source"
570 filename = self.stuffsource(source)
571 self.execfile(filename, source)
572
573 def execfile(self, filename, source=None):
574 "Execute an existing file"
575 if source is None:
576 source = open(filename, "r").read()
577 try:
578 code = compile(source, filename, "exec")
579 except (OverflowError, SyntaxError):
580 self.tkconsole.resetoutput()
581 tkerr = self.tkconsole.stderr
582 print>>tkerr, '*** Error in script or command!\n'
583 print>>tkerr, 'Traceback (most recent call last):'
584 InteractiveInterpreter.showsyntaxerror(self, filename)
585 self.tkconsole.showprompt()
586 else:
587 self.runcode(code)
588
589 def runsource(self, source):
590 "Extend base class method: Stuff the source in the line cache first"
591 filename = self.stuffsource(source)
592 self.more = 0
593 self.save_warnings_filters = warnings.filters[:]
594 warnings.filterwarnings(action="error", category=SyntaxWarning)
595 if isinstance(source, types.UnicodeType):
596 import IOBinding
597 try:
598 source = source.encode(IOBinding.encoding)
599 except UnicodeError:
600 self.tkconsole.resetoutput()
601 self.write("Unsupported characters in input\n")
602 return
603 try:
604 # InteractiveInterpreter.runsource() calls its runcode() method,
605 # which is overridden (see below)
606 return InteractiveInterpreter.runsource(self, source, filename)
607 finally:
608 if self.save_warnings_filters is not None:
609 warnings.filters[:] = self.save_warnings_filters
610 self.save_warnings_filters = None
611
612 def stuffsource(self, source):
613 "Stuff source in the filename cache"
614 filename = "<pyshell#%d>" % self.gid
615 self.gid = self.gid + 1
616 lines = source.split("\n")
617 linecache.cache[filename] = len(source)+1, 0, lines, filename
618 return filename
619
620 def prepend_syspath(self, filename):
621 "Prepend sys.path with file's directory if not already included"
622 self.runcommand("""if 1:
623 _filename = %r
624 import sys as _sys
625 from os.path import dirname as _dirname
626 _dir = _dirname(_filename)
627 if not _dir in _sys.path:
628 _sys.path.insert(0, _dir)
629 del _filename, _sys, _dirname, _dir
630 \n""" % (filename,))
631
632 def showsyntaxerror(self, filename=None):
633 """Extend base class method: Add Colorizing
634
635 Color the offending position instead of printing it and pointing at it
636 with a caret.
637
638 """
639 text = self.tkconsole.text
640 stuff = self.unpackerror()
641 if stuff:
642 msg, lineno, offset, line = stuff
643 if lineno == 1:
644 pos = "iomark + %d chars" % (offset-1)
645 else:
646 pos = "iomark linestart + %d lines + %d chars" % \
647 (lineno-1, offset-1)
648 text.tag_add("ERROR", pos)
649 text.see(pos)
650 char = text.get(pos)
651 if char and char in IDENTCHARS:
652 text.tag_add("ERROR", pos + " wordstart", pos)
653 self.tkconsole.resetoutput()
654 self.write("SyntaxError: %s\n" % str(msg))
655 else:
656 self.tkconsole.resetoutput()
657 InteractiveInterpreter.showsyntaxerror(self, filename)
658 self.tkconsole.showprompt()
659
660 def unpackerror(self):
661 type, value, tb = sys.exc_info()
662 ok = type is SyntaxError
663 if ok:
664 try:
665 msg, (dummy_filename, lineno, offset, line) = value
666 if not offset:
667 offset = 0
668 except:
669 ok = 0
670 if ok:
671 return msg, lineno, offset, line
672 else:
673 return None
674
675 def showtraceback(self):
676 "Extend base class method to reset output properly"
677 self.tkconsole.resetoutput()
678 self.checklinecache()
679 InteractiveInterpreter.showtraceback(self)
680 if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
681 self.tkconsole.open_stack_viewer()
682
683 def checklinecache(self):
684 c = linecache.cache
685 for key in c.keys():
686 if key[:1] + key[-1:] != "<>":
687 del c[key]
688
689 def runcommand(self, code):
690 "Run the code without invoking the debugger"
691 # The code better not raise an exception!
692 if self.tkconsole.executing:
693 self.display_executing_dialog()
694 return 0
695 if self.rpcclt:
696 self.rpcclt.remotequeue("exec", "runcode", (code,), {})
697 else:
698 exec code in self.locals
699 return 1
700
701 def runcode(self, code):
702 "Override base class method"
703 if self.tkconsole.executing:
704 self.interp.restart_subprocess()
705 self.checklinecache()
706 if self.save_warnings_filters is not None:
707 warnings.filters[:] = self.save_warnings_filters
708 self.save_warnings_filters = None
709 debugger = self.debugger
710 try:
711 self.tkconsole.beginexecuting()
712 if not debugger and self.rpcclt is not None:
713 self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
714 (code,), {})
715 elif debugger:
716 debugger.run(code, self.locals)
717 else:
718 exec code in self.locals
719 except SystemExit:
720 if not self.tkconsole.closing:
721 if tkMessageBox.askyesno(
722 "Exit?",
723 "Do you want to exit altogether?",
724 default="yes",
725 master=self.tkconsole.text):
726 raise
727 else:
728 self.showtraceback()
729 else:
730 raise
731 except:
732 if use_subprocess:
733 print >>self.tkconsole.stderr, \
734 "IDLE internal error in runcode()"
735 self.showtraceback()
736 self.tkconsole.endexecuting()
737 else:
738 if self.tkconsole.canceled:
739 self.tkconsole.canceled = False
740 print >>self.tkconsole.stderr, "KeyboardInterrupt"
741 else:
742 self.showtraceback()
743 finally:
744 if not use_subprocess:
745 try:
746 self.tkconsole.endexecuting()
747 except AttributeError: # shell may have closed
748 pass
749
750 def write(self, s):
751 "Override base class method"
752 self.tkconsole.stderr.write(s)
753
754 def display_port_binding_error(self):
755 tkMessageBox.showerror(
756 "Port Binding Error",
757 "IDLE can't bind TCP/IP port 8833, which is necessary to "
758 "communicate with its Python execution server. Either "
759 "no networking is installed on this computer or another "
760 "process (another IDLE?) is using the port. Run IDLE with the -n "
761 "command line switch to start without a subprocess and refer to "
762 "Help/IDLE Help 'Running without a subprocess' for further "
763 "details.",
764 master=self.tkconsole.text)
765
766 def display_no_subprocess_error(self):
767 tkMessageBox.showerror(
768 "Subprocess Startup Error",
769 "IDLE's subprocess didn't make connection. Either IDLE can't "
770 "start a subprocess or personal firewall software is blocking "
771 "the connection.",
772 master=self.tkconsole.text)
773
774 def display_executing_dialog(self):
775 tkMessageBox.showerror(
776 "Already executing",
777 "The Python Shell window is already executing a command; "
778 "please wait until it is finished.",
779 master=self.tkconsole.text)
780
781
782class PyShell(OutputWindow):
783
784 shell_title = "Python Shell"
785
786 # Override classes
787 ColorDelegator = ModifiedColorDelegator
788 UndoDelegator = ModifiedUndoDelegator
789
790 # Override menus
791 menu_specs = [
792 ("file", "_File"),
793 ("edit", "_Edit"),
794 ("debug", "_Debug"),
795 ("options", "_Options"),
796 ("windows", "_Windows"),
797 ("help", "_Help"),
798 ]
799
800 if macosxSupport.runningAsOSXApp():
801 del menu_specs[-3]
802 menu_specs[-2] = ("windows", "_Window")
803
804
805 # New classes
806 from IdleHistory import History
807
808 def __init__(self, flist=None):
809 if use_subprocess:
810 ms = self.menu_specs
811 if ms[2][0] != "shell":
812 ms.insert(2, ("shell", "She_ll"))
813 self.interp = ModifiedInterpreter(self)
814 if flist is None:
815 root = Tk()
816 fixwordbreaks(root)
817 root.withdraw()
818 flist = PyShellFileList(root)
819 #
820 OutputWindow.__init__(self, flist, None, None)
821 #
822## self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
823 self.usetabs = True
824 # indentwidth must be 8 when using tabs. See note in EditorWindow:
825 self.indentwidth = 8
826 self.context_use_ps1 = True
827 #
828 text = self.text
829 text.configure(wrap="char")
830 text.bind("<<newline-and-indent>>", self.enter_callback)
831 text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
832 text.bind("<<interrupt-execution>>", self.cancel_callback)
833 text.bind("<<end-of-file>>", self.eof_callback)
834 text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
835 text.bind("<<toggle-debugger>>", self.toggle_debugger)
836 text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
837 if use_subprocess:
838 text.bind("<<view-restart>>", self.view_restart_mark)
839 text.bind("<<restart-shell>>", self.restart_shell)
840 #
841 self.save_stdout = sys.stdout
842 self.save_stderr = sys.stderr
843 self.save_stdin = sys.stdin
844 import IOBinding
845 self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
846 self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
847 self.console = PseudoFile(self, "console", IOBinding.encoding)
848 if not use_subprocess:
849 sys.stdout = self.stdout
850 sys.stderr = self.stderr
851 sys.stdin = self
852 #
853 self.history = self.History(self.text)
854 #
855 self.pollinterval = 50 # millisec
856
857 def get_standard_extension_names(self):
858 return idleConf.GetExtensions(shell_only=True)
859
860 reading = False
861 executing = False
862 canceled = False
863 endoffile = False
864 closing = False
865
866 def set_warning_stream(self, stream):
867 global warning_stream
868 warning_stream = stream
869
870 def get_warning_stream(self):
871 return warning_stream
872
873 def toggle_debugger(self, event=None):
874 if self.executing:
875 tkMessageBox.showerror("Don't debug now",
876 "You can only toggle the debugger when idle",
877 master=self.text)
878 self.set_debugger_indicator()
879 return "break"
880 else:
881 db = self.interp.getdebugger()
882 if db:
883 self.close_debugger()
884 else:
885 self.open_debugger()
886
887 def set_debugger_indicator(self):
888 db = self.interp.getdebugger()
889 self.setvar("<<toggle-debugger>>", not not db)
890
891 def toggle_jit_stack_viewer(self, event=None):
892 pass # All we need is the variable
893
894 def close_debugger(self):
895 db = self.interp.getdebugger()
896 if db:
897 self.interp.setdebugger(None)
898 db.close()
899 if self.interp.rpcclt:
900 RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
901 self.resetoutput()
902 self.console.write("[DEBUG OFF]\n")
903 sys.ps1 = ">>> "
904 self.showprompt()
905 self.set_debugger_indicator()
906
907 def open_debugger(self):
908 if self.interp.rpcclt:
909 dbg_gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt,
910 self)
911 else:
912 dbg_gui = Debugger.Debugger(self)
913 self.interp.setdebugger(dbg_gui)
914 dbg_gui.load_breakpoints()
915 sys.ps1 = "[DEBUG ON]\n>>> "
916 self.showprompt()
917 self.set_debugger_indicator()
918
919 def beginexecuting(self):
920 "Helper for ModifiedInterpreter"
921 self.resetoutput()
922 self.executing = 1
923
924 def endexecuting(self):
925 "Helper for ModifiedInterpreter"
926 self.executing = 0
927 self.canceled = 0
928 self.showprompt()
929
930 def close(self):
931 "Extend EditorWindow.close()"
932 if self.executing:
933 response = tkMessageBox.askokcancel(
934 "Kill?",
935 "The program is still running!\n Do you want to kill it?",
936 default="ok",
937 parent=self.text)
938 if response is False:
939 return "cancel"
940 if self.reading:
941 self.top.quit()
942 self.canceled = True
943 self.closing = True
944 # Wait for poll_subprocess() rescheduling to stop
945 self.text.after(2 * self.pollinterval, self.close2)
946
947 def close2(self):
948 return EditorWindow.close(self)
949
950 def _close(self):
951 "Extend EditorWindow._close(), shut down debugger and execution server"
952 self.close_debugger()
953 if use_subprocess:
954 self.interp.kill_subprocess()
955 # Restore std streams
956 sys.stdout = self.save_stdout
957 sys.stderr = self.save_stderr
958 sys.stdin = self.save_stdin
959 # Break cycles
960 self.interp = None
961 self.console = None
962 self.flist.pyshell = None
963 self.history = None
964 EditorWindow._close(self)
965
966 def ispythonsource(self, filename):
967 "Override EditorWindow method: never remove the colorizer"
968 return True
969
970 def short_title(self):
971 return self.shell_title
972
973 COPYRIGHT = \
974 'Type "copyright", "credits" or "license()" for more information.'
975
976 firewallmessage = """
977 ****************************************************************
978 Personal firewall software may warn about the connection IDLE
979 makes to its subprocess using this computer's internal loopback
980 interface. This connection is not visible on any external
981 interface and no data is sent to or received from the Internet.
982 ****************************************************************
983 """
984
985 def begin(self):
986 self.resetoutput()
987 if use_subprocess:
988 nosub = ''
989 client = self.interp.start_subprocess()
990 if not client:
991 self.close()
992 return False
993 else:
994 nosub = "==== No Subprocess ===="
995 self.write("Python %s on %s\n%s\n%s\nIDLE %s %s\n" %
996 (sys.version, sys.platform, self.COPYRIGHT,
997 self.firewallmessage, idlever.IDLE_VERSION, nosub))
998 self.showprompt()
999 import Tkinter
1000 Tkinter._default_root = None # 03Jan04 KBK What's this?
1001 return True
1002
1003 def readline(self):
1004 save = self.reading
1005 try:
1006 self.reading = 1
1007 self.top.mainloop() # nested mainloop()
1008 finally:
1009 self.reading = save
1010 line = self.text.get("iomark", "end-1c")
1011 if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C
1012 line = "\n"
1013 if isinstance(line, unicode):
1014 import IOBinding
1015 try:
1016 line = line.encode(IOBinding.encoding)
1017 except UnicodeError:
1018 pass
1019 self.resetoutput()
1020 if self.canceled:
1021 self.canceled = 0
1022 if not use_subprocess:
1023 raise KeyboardInterrupt
1024 if self.endoffile:
1025 self.endoffile = 0
1026 line = ""
1027 return line
1028
1029 def isatty(self):
1030 return True
1031
1032 def cancel_callback(self, event=None):
1033 try:
1034 if self.text.compare("sel.first", "!=", "sel.last"):
1035 return # Active selection -- always use default binding
1036 except:
1037 pass
1038 if not (self.executing or self.reading):
1039 self.resetoutput()
1040 self.interp.write("KeyboardInterrupt\n")
1041 self.showprompt()
1042 return "break"
1043 self.endoffile = 0
1044 self.canceled = 1
1045 if (self.executing and self.interp.rpcclt):
1046 if self.interp.getdebugger():
1047 self.interp.restart_subprocess()
1048 else:
1049 self.interp.interrupt_subprocess()
1050 if self.reading:
1051 self.top.quit() # exit the nested mainloop() in readline()
1052 return "break"
1053
1054 def eof_callback(self, event):
1055 if self.executing and not self.reading:
1056 return # Let the default binding (delete next char) take over
1057 if not (self.text.compare("iomark", "==", "insert") and
1058 self.text.compare("insert", "==", "end-1c")):
1059 return # Let the default binding (delete next char) take over
1060 if not self.executing:
1061 self.resetoutput()
1062 self.close()
1063 else:
1064 self.canceled = 0
1065 self.endoffile = 1
1066 self.top.quit()
1067 return "break"
1068
1069 def linefeed_callback(self, event):
1070 # Insert a linefeed without entering anything (still autoindented)
1071 if self.reading:
1072 self.text.insert("insert", "\n")
1073 self.text.see("insert")
1074 else:
1075 self.newline_and_indent_event(event)
1076 return "break"
1077
1078 def enter_callback(self, event):
1079 if self.executing and not self.reading:
1080 return # Let the default binding (insert '\n') take over
1081 # If some text is selected, recall the selection
1082 # (but only if this before the I/O mark)
1083 try:
1084 sel = self.text.get("sel.first", "sel.last")
1085 if sel:
1086 if self.text.compare("sel.last", "<=", "iomark"):
1087 self.recall(sel, event)
1088 return "break"
1089 except:
1090 pass
1091 # If we're strictly before the line containing iomark, recall
1092 # the current line, less a leading prompt, less leading or
1093 # trailing whitespace
1094 if self.text.compare("insert", "<", "iomark linestart"):
1095 # Check if there's a relevant stdin range -- if so, use it
1096 prev = self.text.tag_prevrange("stdin", "insert")
1097 if prev and self.text.compare("insert", "<", prev[1]):
1098 self.recall(self.text.get(prev[0], prev[1]), event)
1099 return "break"
1100 next = self.text.tag_nextrange("stdin", "insert")
1101 if next and self.text.compare("insert lineend", ">=", next[0]):
1102 self.recall(self.text.get(next[0], next[1]), event)
1103 return "break"
1104 # No stdin mark -- just get the current line, less any prompt
1105 indices = self.text.tag_nextrange("console", "insert linestart")
1106 if indices and \
1107 self.text.compare(indices[0], "<=", "insert linestart"):
1108 self.recall(self.text.get(indices[1], "insert lineend"), event)
1109 else:
1110 self.recall(self.text.get("insert linestart", "insert lineend"), event)
1111 return "break"
1112 # If we're between the beginning of the line and the iomark, i.e.
1113 # in the prompt area, move to the end of the prompt
1114 if self.text.compare("insert", "<", "iomark"):
1115 self.text.mark_set("insert", "iomark")
1116 # If we're in the current input and there's only whitespace
1117 # beyond the cursor, erase that whitespace first
1118 s = self.text.get("insert", "end-1c")
1119 if s and not s.strip():
1120 self.text.delete("insert", "end-1c")
1121 # If we're in the current input before its last line,
1122 # insert a newline right at the insert point
1123 if self.text.compare("insert", "<", "end-1c linestart"):
1124 self.newline_and_indent_event(event)
1125 return "break"
1126 # We're in the last line; append a newline and submit it
1127 self.text.mark_set("insert", "end-1c")
1128 if self.reading:
1129 self.text.insert("insert", "\n")
1130 self.text.see("insert")
1131 else:
1132 self.newline_and_indent_event(event)
1133 self.text.tag_add("stdin", "iomark", "end-1c")
1134 self.text.update_idletasks()
1135 if self.reading:
1136 self.top.quit() # Break out of recursive mainloop() in raw_input()
1137 else:
1138 self.runit()
1139 return "break"
1140
1141 def recall(self, s, event):
1142 # remove leading and trailing empty or whitespace lines
1143 s = re.sub(r'^\s*\n', '' , s)
1144 s = re.sub(r'\n\s*$', '', s)
1145 lines = s.split('\n')
1146 self.text.undo_block_start()
1147 try:
1148 self.text.tag_remove("sel", "1.0", "end")
1149 self.text.mark_set("insert", "end-1c")
1150 prefix = self.text.get("insert linestart", "insert")
1151 if prefix.rstrip().endswith(':'):
1152 self.newline_and_indent_event(event)
1153 prefix = self.text.get("insert linestart", "insert")
1154 self.text.insert("insert", lines[0].strip())
1155 if len(lines) > 1:
1156 orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
1157 new_base_indent = re.search(r'^([ \t]*)', prefix).group(0)
1158 for line in lines[1:]:
1159 if line.startswith(orig_base_indent):
1160 # replace orig base indentation with new indentation
1161 line = new_base_indent + line[len(orig_base_indent):]
1162 self.text.insert('insert', '\n'+line.rstrip())
1163 finally:
1164 self.text.see("insert")
1165 self.text.undo_block_stop()
1166
1167 def runit(self):
1168 line = self.text.get("iomark", "end-1c")
1169 # Strip off last newline and surrounding whitespace.
1170 # (To allow you to hit return twice to end a statement.)
1171 i = len(line)
1172 while i > 0 and line[i-1] in " \t":
1173 i = i-1
1174 if i > 0 and line[i-1] == "\n":
1175 i = i-1
1176 while i > 0 and line[i-1] in " \t":
1177 i = i-1
1178 line = line[:i]
1179 more = self.interp.runsource(line)
1180
1181 def open_stack_viewer(self, event=None):
1182 if self.interp.rpcclt:
1183 return self.interp.remote_stack_viewer()
1184 try:
1185 sys.last_traceback
1186 except:
1187 tkMessageBox.showerror("No stack trace",
1188 "There is no stack trace yet.\n"
1189 "(sys.last_traceback is not defined)",
1190 master=self.text)
1191 return
1192 from StackViewer import StackBrowser
1193 sv = StackBrowser(self.root, self.flist)
1194
1195 def view_restart_mark(self, event=None):
1196 self.text.see("iomark")
1197 self.text.see("restart")
1198
1199 def restart_shell(self, event=None):
1200 self.interp.restart_subprocess()
1201
1202 def showprompt(self):
1203 self.resetoutput()
1204 try:
1205 s = str(sys.ps1)
1206 except:
1207 s = ""
1208 self.console.write(s)
1209 self.text.mark_set("insert", "end-1c")
1210 self.set_line_and_column()
1211 self.io.reset_undo()
1212
1213 def resetoutput(self):
1214 source = self.text.get("iomark", "end-1c")
1215 if self.history:
1216 self.history.history_store(source)
1217 if self.text.get("end-2c") != "\n":
1218 self.text.insert("end-1c", "\n")
1219 self.text.mark_set("iomark", "end-1c")
1220 self.set_line_and_column()
1221 sys.stdout.softspace = 0
1222
1223 def write(self, s, tags=()):
1224 try:
1225 self.text.mark_gravity("iomark", "right")
1226 OutputWindow.write(self, s, tags, "iomark")
1227 self.text.mark_gravity("iomark", "left")
1228 except:
1229 pass
1230 if self.canceled:
1231 self.canceled = 0
1232 if not use_subprocess:
1233 raise KeyboardInterrupt
1234
1235class PseudoFile(object):
1236
1237 def __init__(self, shell, tags, encoding=None):
1238 self.shell = shell
1239 self.tags = tags
1240 self.softspace = 0
1241 self.encoding = encoding
1242
1243 def write(self, s):
1244 self.shell.write(s, self.tags)
1245
1246 def writelines(self, l):
1247 map(self.write, l)
1248
1249 def flush(self):
1250 pass
1251
1252 def isatty(self):
1253 return True
1254
1255
1256usage_msg = """\
1257
1258USAGE: idle [-deins] [-t title] [file]*
1259 idle [-dns] [-t title] (-c cmd | -r file) [arg]*
1260 idle [-dns] [-t title] - [arg]*
1261
1262 -h print this help message and exit
1263 -n run IDLE without a subprocess (see Help/IDLE Help for details)
1264
1265The following options will override the IDLE 'settings' configuration:
1266
1267 -e open an edit window
1268 -i open a shell window
1269
1270The following options imply -i and will open a shell:
1271
1272 -c cmd run the command in a shell, or
1273 -r file run script from file
1274
1275 -d enable the debugger
1276 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
1277 -t title set title of shell window
1278
1279A default edit window will be bypassed when -c, -r, or - are used.
1280
1281[arg]* are passed to the command (-c) or script (-r) in sys.argv[1:].
1282
1283Examples:
1284
1285idle
1286 Open an edit window or shell depending on IDLE's configuration.
1287
1288idle foo.py foobar.py
1289 Edit the files, also open a shell if configured to start with shell.
1290
1291idle -est "Baz" foo.py
1292 Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell
1293 window with the title "Baz".
1294
1295idle -c "import sys; print sys.argv" "foo"
1296 Open a shell window and run the command, passing "-c" in sys.argv[0]
1297 and "foo" in sys.argv[1].
1298
1299idle -d -s -r foo.py "Hello World"
1300 Open a shell window, run a startup script, enable the debugger, and
1301 run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in
1302 sys.argv[1].
1303
1304echo "import sys; print sys.argv" | idle - "foobar"
1305 Open a shell window, run the script piped in, passing '' in sys.argv[0]
1306 and "foobar" in sys.argv[1].
1307"""
1308
1309def main():
1310 global flist, root, use_subprocess
1311
1312 use_subprocess = True
1313 enable_shell = False
1314 enable_edit = False
1315 debug = False
1316 cmd = None
1317 script = None
1318 startup = False
1319 try:
1320 opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:")
1321 except getopt.error, msg:
1322 sys.stderr.write("Error: %s\n" % str(msg))
1323 sys.stderr.write(usage_msg)
1324 sys.exit(2)
1325 for o, a in opts:
1326 if o == '-c':
1327 cmd = a
1328 enable_shell = True
1329 if o == '-d':
1330 debug = True
1331 enable_shell = True
1332 if o == '-e':
1333 enable_edit = True
1334 if o == '-h':
1335 sys.stdout.write(usage_msg)
1336 sys.exit()
1337 if o == '-i':
1338 enable_shell = True
1339 if o == '-n':
1340 use_subprocess = False
1341 if o == '-r':
1342 script = a
1343 if os.path.isfile(script):
1344 pass
1345 else:
1346 print "No script file: ", script
1347 sys.exit()
1348 enable_shell = True
1349 if o == '-s':
1350 startup = True
1351 enable_shell = True
1352 if o == '-t':
1353 PyShell.shell_title = a
1354 enable_shell = True
1355 if args and args[0] == '-':
1356 cmd = sys.stdin.read()
1357 enable_shell = True
1358 # process sys.argv and sys.path:
1359 for i in range(len(sys.path)):
1360 sys.path[i] = os.path.abspath(sys.path[i])
1361 if args and args[0] == '-':
1362 sys.argv = [''] + args[1:]
1363 elif cmd:
1364 sys.argv = ['-c'] + args
1365 elif script:
1366 sys.argv = [script] + args
1367 elif args:
1368 enable_edit = True
1369 pathx = []
1370 for filename in args:
1371 pathx.append(os.path.dirname(filename))
1372 for dir in pathx:
1373 dir = os.path.abspath(dir)
1374 if not dir in sys.path:
1375 sys.path.insert(0, dir)
1376 else:
1377 dir = os.getcwd()
1378 if not dir in sys.path:
1379 sys.path.insert(0, dir)
1380 # check the IDLE settings configuration (but command line overrides)
1381 edit_start = idleConf.GetOption('main', 'General',
1382 'editor-on-startup', type='bool')
1383 enable_edit = enable_edit or edit_start
1384 enable_shell = enable_shell or not edit_start
1385 # start editor and/or shell windows:
1386 root = Tk(className="Idle")
1387
1388 fixwordbreaks(root)
1389 root.withdraw()
1390 flist = PyShellFileList(root)
1391 macosxSupport.setupApp(root, flist)
1392
1393 if enable_edit:
1394 if not (cmd or script):
1395 for filename in args:
1396 flist.open(filename)
1397 if not args:
1398 flist.new()
1399 if enable_shell:
1400 shell = flist.open_shell()
1401 if not shell:
1402 return # couldn't open shell
1403
1404 if macosxSupport.runningAsOSXApp() and flist.dict:
1405 # On OSX: when the user has double-clicked on a file that causes
1406 # IDLE to be launched the shell window will open just in front of
1407 # the file she wants to see. Lower the interpreter window when
1408 # there are open files.
1409 shell.top.lower()
1410
1411 shell = flist.pyshell
1412 # handle remaining options:
1413 if debug:
1414 shell.open_debugger()
1415 if startup:
1416 filename = os.environ.get("IDLESTARTUP") or \
1417 os.environ.get("PYTHONSTARTUP")
1418 if filename and os.path.isfile(filename):
1419 shell.interp.execfile(filename)
1420 if shell and cmd or script:
1421 shell.interp.runcommand("""if 1:
1422 import sys as _sys
1423 _sys.argv = %r
1424 del _sys
1425 \n""" % (sys.argv,))
1426 if cmd:
1427 shell.interp.execsource(cmd)
1428 elif script:
1429 shell.interp.prepend_syspath(script)
1430 shell.interp.execfile(script)
1431
1432 root.mainloop()
1433 root.destroy()
1434
1435if __name__ == "__main__":
1436 sys.modules['PyShell'] = sys.modules['__main__']
1437 main()
Note: See TracBrowser for help on using the repository browser.