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