3 # changes by dscherer@cmu.edu
5 # the main() function has been replaced by a whole class, in order to
6 # address the constraint that only one process can sit on the port
7 # hard-coded into the loader.
9 # It attempts to load the RPC protocol server and publish itself. If
10 # that fails, it assumes that some other copy of IDLE is already running
11 # on the port and attempts to contact it. It then uses the RPC mechanism
12 # to ask that copy to do whatever it was instructed (via the command
13 # line) to do. (Think netscape -remote). The handling of command line
14 # arguments for remotes is still very incomplete.
16 # default behavior (no command line options) is to NOT start the Python
17 # Shell. If files are specified, they are opened, otherwise a single
18 # blank editor window opens.
20 # If any command line -options are specified, a shell does appear. This
21 # is necessary to make the current semantics of the options make sense.
32 from code
import InteractiveInterpreter
37 from EditorWindow
import EditorWindow
, fixwordbreaks
38 from FileList
import FileList
39 from ColorDelegator
import ColorDelegator
40 from UndoDelegator
import UndoDelegator
41 from OutputWindow
import OutputWindow
, OnDemandOutputWindow
42 from IdleConf
import idleconf
45 # We need to patch linecache.checkcache, because we don't want it
46 # to throw away our <pyshell#...> entries.
47 # Rather than repeating its code here, we save those entries,
48 # then call the original function, and then restore the saved entries.
49 def linecache_checkcache(orig_checkcache
=linecache
.checkcache
):
50 cache
= linecache
.cache
52 for filename
in cache
.keys():
53 if filename
[:1] + filename
[-1:] == '<>':
54 save
[filename
] = cache
[filename
]
57 linecache
.checkcache
= linecache_checkcache
60 # Note: <<newline-and-indent>> event is defined in AutoIndent.py
62 #$ event <<plain-newline-and-indent>>
66 #$ event <<beginning-of-line>>
72 #$ event <<history-next>>
76 #$ event <<history-previous>>
80 #$ event <<interrupt-execution>>
84 #$ event <<end-of-file>>
88 #$ event <<open-stack-viewer>>
90 #$ event <<toggle-debugger>>
93 class PyShellEditorWindow(EditorWindow
):
95 # Regular text edit window when a shell is present
96 # XXX ought to merge with regular editor window
98 def __init__(self
, *args
):
99 apply(EditorWindow
.__init
__, (self
,) + args
)
100 self
.text
.bind("<<set-breakpoint-here>>", self
.set_breakpoint_here
)
101 self
.text
.bind("<<open-python-shell>>", self
.flist
.open_shell
)
104 ("Set breakpoint here", "<<set-breakpoint-here>>"),
107 def set_breakpoint_here(self
, event
=None):
108 if not self
.flist
.pyshell
or not self
.flist
.pyshell
.interp
.debugger
:
111 self
.flist
.pyshell
.interp
.debugger
.set_breakpoint_here(self
)
114 class PyShellFileList(FileList
):
116 # File list when a shell is present
118 EditorWindow
= PyShellEditorWindow
122 def open_shell(self
, event
=None):
124 self
.pyshell
.wakeup()
126 self
.pyshell
= PyShell(self
)
131 class ModifiedColorDelegator(ColorDelegator
):
133 # Colorizer for the shell window itself
135 def recolorize_main(self
):
136 self
.tag_remove("TODO", "1.0", "iomark")
137 self
.tag_add("SYNC", "1.0", "iomark")
138 ColorDelegator
.recolorize_main(self
)
140 tagdefs
= ColorDelegator
.tagdefs
.copy()
141 cconf
= idleconf
.getsection('Colors')
144 "stdin": cconf
.getcolor("stdin"),
145 "stdout": cconf
.getcolor("stdout"),
146 "stderr": cconf
.getcolor("stderr"),
147 "console": cconf
.getcolor("console"),
148 "ERROR": cconf
.getcolor("ERROR"),
149 None: cconf
.getcolor("normal"),
153 class ModifiedUndoDelegator(UndoDelegator
):
155 # Forbid insert/delete before the I/O mark
157 def insert(self
, index
, chars
, tags
=None):
159 if self
.delegate
.compare(index
, "<", "iomark"):
164 UndoDelegator
.insert(self
, index
, chars
, tags
)
166 def delete(self
, index1
, index2
=None):
168 if self
.delegate
.compare(index1
, "<", "iomark"):
173 UndoDelegator
.delete(self
, index1
, index2
)
175 class ModifiedInterpreter(InteractiveInterpreter
):
177 def __init__(self
, tkconsole
):
178 self
.tkconsole
= tkconsole
179 locals = sys
.modules
['__main__'].__dict
__
180 InteractiveInterpreter
.__init
__(self
, locals=locals)
184 def execsource(self
, source
):
185 # Like runsource() but assumes complete exec source
186 filename
= self
.stuffsource(source
)
187 self
.execfile(filename
, source
)
189 def execfile(self
, filename
, source
=None):
190 # Execute an existing file
192 source
= open(filename
, "r").read()
194 code
= compile(source
, filename
, "exec")
195 except (OverflowError, SyntaxError):
196 self
.tkconsole
.resetoutput()
197 InteractiveInterpreter
.showsyntaxerror(self
, filename
)
201 def runsource(self
, source
):
202 # Extend base class to stuff the source in the line cache first
203 filename
= self
.stuffsource(source
)
205 return InteractiveInterpreter
.runsource(self
, source
, filename
)
207 def stuffsource(self
, source
):
208 # Stuff source in the filename cache
209 filename
= "<pyshell#%d>" % self
.gid
210 self
.gid
= self
.gid
+ 1
211 lines
= string
.split(source
, "\n")
212 linecache
.cache
[filename
] = len(source
)+1, 0, lines
, filename
215 def showsyntaxerror(self
, filename
=None):
216 # Extend base class to color the offending position
217 # (instead of printing it and pointing at it with a caret)
218 text
= self
.tkconsole
.text
219 stuff
= self
.unpackerror()
221 self
.tkconsole
.resetoutput()
222 InteractiveInterpreter
.showsyntaxerror(self
, filename
)
224 msg
, lineno
, offset
, line
= stuff
226 pos
= "iomark + %d chars" % (offset
-1)
228 pos
= "iomark linestart + %d lines + %d chars" % (lineno
-1,
230 text
.tag_add("ERROR", pos
)
233 if char
and char
in string
.letters
+ string
.digits
+ "_":
234 text
.tag_add("ERROR", pos
+ " wordstart", pos
)
235 self
.tkconsole
.resetoutput()
236 self
.write("SyntaxError: %s\n" % str(msg
))
238 def unpackerror(self
):
239 type, value
, tb
= sys
.exc_info()
240 ok
= type is SyntaxError
243 msg
, (dummy_filename
, lineno
, offset
, line
) = value
247 return msg
, lineno
, offset
, line
251 def showtraceback(self
):
252 # Extend base class method to reset output properly
253 text
= self
.tkconsole
.text
254 self
.tkconsole
.resetoutput()
255 self
.checklinecache()
256 InteractiveInterpreter
.showtraceback(self
)
258 def checklinecache(self
):
261 if key
[:1] + key
[-1:] != "<>":
266 def setdebugger(self
, debugger
):
267 self
.debugger
= debugger
269 def getdebugger(self
):
272 def runcode(self
, code
):
273 # Override base class method
274 debugger
= self
.debugger
276 self
.tkconsole
.beginexecuting()
279 debugger
.run(code
, self
.locals)
281 exec code
in self
.locals
283 if tkMessageBox
.askyesno(
285 "Do you want to exit altogether?",
287 master
=self
.tkconsole
.text
):
291 if self
.tkconsole
.getvar("<<toggle-jit-stack-viewer>>"):
292 self
.tkconsole
.open_stack_viewer()
295 if self
.tkconsole
.getvar("<<toggle-jit-stack-viewer>>"):
296 self
.tkconsole
.open_stack_viewer()
299 self
.tkconsole
.endexecuting()
302 # Override base class write
303 self
.tkconsole
.console
.write(s
)
306 class PyShell(OutputWindow
):
308 shell_title
= "Python Shell"
311 ColorDelegator
= ModifiedColorDelegator
312 UndoDelegator
= ModifiedUndoDelegator
314 # Override menu bar specs
315 menu_specs
= PyShellEditorWindow
.menu_specs
[:]
316 menu_specs
.insert(len(menu_specs
)-2, ("debug", "_Debug"))
319 from IdleHistory
import History
321 def __init__(self
, flist
=None):
322 self
.interp
= ModifiedInterpreter(self
)
327 flist
= PyShellFileList(root
)
329 OutputWindow
.__init
__(self
, flist
, None, None)
332 __builtin__
.quit
= __builtin__
.exit
= "To exit, type Ctrl-D."
334 self
.auto
= self
.extensions
["AutoIndent"] # Required extension
335 self
.auto
.config(usetabs
=1, indentwidth
=8, context_use_ps1
=1)
338 text
.configure(wrap
="char")
339 text
.bind("<<newline-and-indent>>", self
.enter_callback
)
340 text
.bind("<<plain-newline-and-indent>>", self
.linefeed_callback
)
341 text
.bind("<<interrupt-execution>>", self
.cancel_callback
)
342 text
.bind("<<beginning-of-line>>", self
.home_callback
)
343 text
.bind("<<end-of-file>>", self
.eof_callback
)
344 text
.bind("<<open-stack-viewer>>", self
.open_stack_viewer
)
345 text
.bind("<<toggle-debugger>>", self
.toggle_debugger
)
346 text
.bind("<<open-python-shell>>", self
.flist
.open_shell
)
347 text
.bind("<<toggle-jit-stack-viewer>>", self
.toggle_jit_stack_viewer
)
349 self
.save_stdout
= sys
.stdout
350 self
.save_stderr
= sys
.stderr
351 self
.save_stdin
= sys
.stdin
352 sys
.stdout
= PseudoFile(self
, "stdout")
353 sys
.stderr
= PseudoFile(self
, "stderr")
355 self
.console
= PseudoFile(self
, "console")
357 self
.history
= self
.History(self
.text
)
364 def toggle_debugger(self
, event
=None):
366 tkMessageBox
.showerror("Don't debug now",
367 "You can only toggle the debugger when idle",
369 self
.set_debugger_indicator()
372 db
= self
.interp
.getdebugger()
374 self
.close_debugger()
378 def set_debugger_indicator(self
):
379 db
= self
.interp
.getdebugger()
380 self
.setvar("<<toggle-debugger>>", not not db
)
382 def toggle_jit_stack_viewer( self
, event
=None):
383 pass # All we need is the variable
385 def close_debugger(self
):
386 db
= self
.interp
.getdebugger()
388 self
.interp
.setdebugger(None)
391 self
.console
.write("[DEBUG OFF]\n")
394 self
.set_debugger_indicator()
396 def open_debugger(self
):
398 self
.interp
.setdebugger(Debugger
.Debugger(self
))
399 sys
.ps1
= "[DEBUG ON]\n>>> "
401 self
.set_debugger_indicator()
403 def beginexecuting(self
):
404 # Helper for ModifiedInterpreter
407 ##self._cancel_check = self.cancel_check
408 ##sys.settrace(self._cancel_check)
410 def endexecuting(self
):
411 # Helper for ModifiedInterpreter
413 ##self._cancel_check = None
418 # Extend base class method
420 # XXX Need to ask a question here
421 if not tkMessageBox
.askokcancel(
423 "The program is still running; do you want to kill it?",
431 return PyShellEditorWindow
.close(self
)
434 self
.close_debugger()
435 # Restore std streams
436 sys
.stdout
= self
.save_stdout
437 sys
.stderr
= self
.save_stderr
438 sys
.stdin
= self
.save_stdin
443 self
.flist
.pyshell
= None
445 OutputWindow
._close
(self
) # Really EditorWindow._close
447 def ispythonsource(self
, filename
):
448 # Override this so EditorWindow never removes the colorizer
451 def short_title(self
):
452 return self
.shell_title
456 self
.write("Python %s on %s\n%s\nIDLE %s -- press F1 for help\n" %
457 (sys
.version
, sys
.platform
, sys
.copyright
,
458 idlever
.IDLE_VERSION
))
461 except AttributeError:
465 Tkinter
._default
_root
= None
478 line
= self
.text
.get("iomark", "end-1c")
482 raise KeyboardInterrupt
491 def cancel_callback(self
, event
):
493 if self
.text
.compare("sel.first", "!=", "sel.last"):
494 return # Active selection -- always use default binding
497 if not (self
.executing
or self
.reading
):
499 self
.write("KeyboardInterrupt\n")
508 def eof_callback(self
, event
):
509 if self
.executing
and not self
.reading
:
510 return # Let the default binding (delete next char) take over
511 if not (self
.text
.compare("iomark", "==", "insert") and
512 self
.text
.compare("insert", "==", "end-1c")):
513 return # Let the default binding (delete next char) take over
514 if not self
.executing
:
515 ## if not tkMessageBox.askokcancel(
517 ## "Are you sure you want to exit?",
518 ## default="ok", master=self.text):
528 def home_callback(self
, event
):
529 if event
.state
!= 0 and event
.keysym
== "Home":
530 return # <Modifier-Home>; fall back to class binding
531 if self
.text
.compare("iomark", "<=", "insert") and \
532 self
.text
.compare("insert linestart", "<=", "iomark"):
533 self
.text
.mark_set("insert", "iomark")
534 self
.text
.tag_remove("sel", "1.0", "end")
535 self
.text
.see("insert")
538 def linefeed_callback(self
, event
):
539 # Insert a linefeed without entering anything (still autoindented)
541 self
.text
.insert("insert", "\n")
542 self
.text
.see("insert")
544 self
.auto
.auto_indent(event
)
547 def enter_callback(self
, event
):
548 if self
.executing
and not self
.reading
:
549 return # Let the default binding (insert '\n') take over
550 # If some text is selected, recall the selection
551 # (but only if this before the I/O mark)
553 sel
= self
.text
.get("sel.first", "sel.last")
555 if self
.text
.compare("sel.last", "<=", "iomark"):
560 # If we're strictly before the line containing iomark, recall
561 # the current line, less a leading prompt, less leading or
562 # trailing whitespace
563 if self
.text
.compare("insert", "<", "iomark linestart"):
564 # Check if there's a relevant stdin range -- if so, use it
565 prev
= self
.text
.tag_prevrange("stdin", "insert")
566 if prev
and self
.text
.compare("insert", "<", prev
[1]):
567 self
.recall(self
.text
.get(prev
[0], prev
[1]))
569 next
= self
.text
.tag_nextrange("stdin", "insert")
570 if next
and self
.text
.compare("insert lineend", ">=", next
[0]):
571 self
.recall(self
.text
.get(next
[0], next
[1]))
573 # No stdin mark -- just get the current line
574 self
.recall(self
.text
.get("insert linestart", "insert lineend"))
576 # If we're in the current input and there's only whitespace
577 # beyond the cursor, erase that whitespace first
578 s
= self
.text
.get("insert", "end-1c")
579 if s
and not string
.strip(s
):
580 self
.text
.delete("insert", "end-1c")
581 # If we're in the current input before its last line,
582 # insert a newline right at the insert point
583 if self
.text
.compare("insert", "<", "end-1c linestart"):
584 self
.auto
.auto_indent(event
)
586 # We're in the last line; append a newline and submit it
587 self
.text
.mark_set("insert", "end-1c")
589 self
.text
.insert("insert", "\n")
590 self
.text
.see("insert")
592 self
.auto
.auto_indent(event
)
593 self
.text
.tag_add("stdin", "iomark", "end-1c")
594 self
.text
.update_idletasks()
596 self
.top
.quit() # Break out of recursive mainloop() in raw_input()
603 self
.history
.recall(s
)
606 line
= self
.text
.get("iomark", "end-1c")
607 # Strip off last newline and surrounding whitespace.
608 # (To allow you to hit return twice to end a statement.)
610 while i
> 0 and line
[i
-1] in " \t":
612 if i
> 0 and line
[i
-1] == "\n":
614 while i
> 0 and line
[i
-1] in " \t":
617 more
= self
.interp
.runsource(line
)
621 def cancel_check(self
, frame
, what
, args
,
622 dooneevent
=tkinter
.dooneevent
,
623 dontwait
=tkinter
.DONT_WAIT
):
624 # Hack -- use the debugger hooks to be able to handle events
625 # and interrupt execution at any time.
626 # This slows execution down quite a bit, so you may want to
627 # disable this (by not calling settrace() in runcode() above)
628 # for full-bore (uninterruptable) speed.
629 # XXX This should become a user option.
635 raise KeyboardInterrupt
636 return self
._cancel
_check
638 def open_stack_viewer(self
, event
=None):
642 tkMessageBox
.showerror("No stack trace",
643 "There is no stack trace yet.\n"
644 "(sys.last_traceback is not defined)",
647 from StackViewer
import StackBrowser
648 sv
= StackBrowser(self
.root
, self
.flist
)
650 def showprompt(self
):
656 self
.console
.write(s
)
657 self
.text
.mark_set("insert", "end-1c")
659 def resetoutput(self
):
660 source
= self
.text
.get("iomark", "end-1c")
662 self
.history
.history_store(source
)
663 if self
.text
.get("end-2c") != "\n":
664 self
.text
.insert("end-1c", "\n")
665 self
.text
.mark_set("iomark", "end-1c")
666 sys
.stdout
.softspace
= 0
668 def write(self
, s
, tags
=()):
669 self
.text
.mark_gravity("iomark", "right")
670 OutputWindow
.write(self
, s
, tags
, "iomark")
671 self
.text
.mark_gravity("iomark", "left")
674 raise KeyboardInterrupt
678 def __init__(self
, shell
, tags
):
683 self
.shell
.write(s
, self
.tags
)
685 def writelines(self
, l
):
695 usage: idle.py [-c command] [-d] [-e] [-s] [-t title] [arg] ...
697 -c command run this command
699 -e edit mode; arguments are files to be edited
700 -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
701 -t title set title of shell window
703 When neither -c nor -e is used, and there are arguments, and the first
704 argument is not '-', the first argument is run as a script. Remaining
705 arguments are arguments to the script or to the command run by -c.
709 def __init__(self
, string
): self
.string
= string
710 def __repr__(self
): return self
.string
715 self
.server
= protocol
.Server(connection_hook
= self
.address_ok
)
716 protocol
.publish( 'IDLE', self
.connect
)
717 self
.main( sys
.argv
[1:] )
719 except protocol
.connectionLost
:
721 client
= protocol
.Client()
722 IDLE
= client
.getobject('IDLE')
725 IDLE
.remote( sys
.argv
[1:] )
726 except usageError
, msg
:
727 sys
.stderr
.write("Error: %s\n" % str(msg
))
728 sys
.stderr
.write(usage_msg
)
730 except protocol
.connectionLost
:
733 # xxx Should scream via Tk()
734 print "Something already has our socket, but it won't open a window for me!"
735 print "Unable to proceed."
739 self
.server
.rpc_loop()
740 root
.after(25, self
.idle
)
742 # We permit connections from localhost only
743 def address_ok(self
, addr
):
744 return addr
[0] == '127.0.0.1'
746 def connect(self
, client
, addr
):
749 def remote( self
, argv
):
750 # xxx Should make this behavior match the behavior in main, or redo
751 # command line options entirely.
754 opts
, args
= getopt
.getopt(argv
, "c:deist:")
755 except getopt
.error
, msg
:
756 raise usageError(msg
)
758 for filename
in args
:
763 def main( self
, argv
):
772 opts
, args
= getopt
.getopt(argv
, "c:deist:")
773 except getopt
.error
, msg
:
774 sys
.stderr
.write("Error: %s\n" % str(msg
))
775 sys
.stderr
.write(usage_msg
)
789 PyShell
.shell_title
= a
795 sys
.argv
= ["-c"] + args
797 sys
.argv
= args
or [""]
799 for i
in range(len(sys
.path
)):
800 sys
.path
[i
] = os
.path
.abspath(sys
.path
[i
])
804 for filename
in args
:
805 pathx
.append(os
.path
.dirname(filename
))
806 elif args
and args
[0] != "-":
807 pathx
.append(os
.path
.dirname(args
[0]))
809 pathx
.append(os
.curdir
)
811 dir = os
.path
.abspath(dir)
812 if not dir in sys
.path
:
813 sys
.path
.insert(0, dir)
819 flist
= PyShellFileList(root
)
822 for filename
in args
:
827 #dbg=OnDemandOutputWindow(flist)
828 #dbg.set_title('Internal IDLE Problem')
829 #sys.stdout = PseudoFile(dbg,['stdout'])
830 #sys.stderr = PseudoFile(dbg,['stderr'])
835 shell
= PyShell(flist
)
836 interp
= shell
.interp
837 flist
.pyshell
= shell
840 filename
= os
.environ
.get("IDLESTARTUP") or \
841 os
.environ
.get("PYTHONSTARTUP")
842 if filename
and os
.path
.isfile(filename
):
843 interp
.execfile(filename
)
846 shell
.open_debugger()
848 interp
.execsource(cmd
)
849 elif not edit
and args
and args
[0] != "-":
850 interp
.execfile(args
[0])
859 if __name__
== "__main__":