1 import io 2 import linecache 3 import queue 4 import sys 5 import time 6 import traceback 7 import _thread as thread 8 import threading 9 import warnings 10 11 import tkinter # Tcl, deletions, messagebox if startup fails 12 13 from idlelib import autocomplete # AutoComplete, fetch_encodings 14 from idlelib import calltips # CallTips 15 from idlelib import debugger_r # start_debugger 16 from idlelib import debugobj_r # remote_object_tree_item 17 from idlelib import iomenu # encoding 18 from idlelib import rpc # multiple objects 19 from idlelib import stackviewer # StackTreeItem 20 import __main__ 21 22 for mod in ('simpledialog', 'messagebox', 'font', 23 'dialog', 'filedialog', 'commondialog', 24 'ttk'): 25 delattr(tkinter, mod) 26 del sys.modules['tkinter.' + mod] 27 28 LOCALHOST = '127.0.0.1' 29 30 31 def idle_formatwarning(message, category, filename, lineno, line=None): 32 """Format warnings the IDLE way.""" 33 34 s = "\nWarning (from warnings module):\n" 35 s += ' File \"%s\", line %s\n' % (filename, lineno) 36 if line is None: 37 line = linecache.getline(filename, lineno) 38 line = line.strip() 39 if line: 40 s += " %s\n" % line 41 s += "%s: %s\n" % (category.__name__, message) 42 return s 43 44 def idle_showwarning_subproc( 45 message, category, filename, lineno, file=None, line=None): 46 """Show Idle-format warning after replacing warnings.showwarning. 47 48 The only difference is the formatter called. 49 """ 50 if file is None: 51 file = sys.stderr 52 try: 53 file.write(idle_formatwarning( 54 message, category, filename, lineno, line)) 55 except IOError: 56 pass # the file (probably stderr) is invalid - this warning gets lost. 57 58 _warnings_showwarning = None 59 60 def capture_warnings(capture): 61 "Replace warning.showwarning with idle_showwarning_subproc, or reverse." 62 63 global _warnings_showwarning 64 if capture: 65 if _warnings_showwarning is None: 66 _warnings_showwarning = warnings.showwarning 67 warnings.showwarning = idle_showwarning_subproc 68 else: 69 if _warnings_showwarning is not None: 70 warnings.showwarning = _warnings_showwarning 71 _warnings_showwarning = None 72 73 capture_warnings(True) 74 tcl = tkinter.Tcl() 75 76 def handle_tk_events(tcl=tcl): 77 """Process any tk events that are ready to be dispatched if tkinter 78 has been imported, a tcl interpreter has been created and tk has been 79 loaded.""" 80 tcl.eval("update") 81 82 # Thread shared globals: Establish a queue between a subthread (which handles 83 # the socket) and the main thread (which runs user code), plus global 84 # completion, exit and interruptable (the main thread) flags: 85 86 exit_now = False 87 quitting = False 88 interruptable = False 89 90 def main(del_exitfunc=False): 91 """Start the Python execution server in a subprocess 92 93 In the Python subprocess, RPCServer is instantiated with handlerclass 94 MyHandler, which inherits register/unregister methods from RPCHandler via 95 the mix-in class SocketIO. 96 97 When the RPCServer 'server' is instantiated, the TCPServer initialization 98 creates an instance of run.MyHandler and calls its handle() method. 99 handle() instantiates a run.Executive object, passing it a reference to the 100 MyHandler object. That reference is saved as attribute rpchandler of the 101 Executive instance. The Executive methods have access to the reference and 102 can pass it on to entities that they command 103 (e.g. debugger_r.Debugger.start_debugger()). The latter, in turn, can 104 call MyHandler(SocketIO) register/unregister methods via the reference to 105 register and unregister themselves. 106 107 """ 108 global exit_now 109 global quitting 110 global no_exitfunc 111 no_exitfunc = del_exitfunc 112 #time.sleep(15) # test subprocess not responding 113 try: 114 assert(len(sys.argv) > 1) 115 port = int(sys.argv[-1]) 116 except: 117 print("IDLE Subprocess: no IP port passed in sys.argv.", 118 file=sys.__stderr__) 119 return 120 121 capture_warnings(True) 122 sys.argv[:] = [""] 123 sockthread = threading.Thread(target=manage_socket, 124 name='SockThread', 125 args=((LOCALHOST, port),)) 126 sockthread.daemon = True 127 sockthread.start() 128 while 1: 129 try: 130 if exit_now: 131 try: 132 exit() 133 except KeyboardInterrupt: 134 # exiting but got an extra KBI? Try again! 135 continue 136 try: 137 seq, request = rpc.request_queue.get(block=True, timeout=0.05) 138 except queue.Empty: 139 handle_tk_events() 140 continue 141 method, args, kwargs = request 142 ret = method(*args, **kwargs) 143 rpc.response_queue.put((seq, ret)) 144 except KeyboardInterrupt: 145 if quitting: 146 exit_now = True 147 continue 148 except SystemExit: 149 capture_warnings(False) 150 raise 151 except: 152 type, value, tb = sys.exc_info() 153 try: 154 print_exception() 155 rpc.response_queue.put((seq, None)) 156 except: 157 # Link didn't work, print same exception to __stderr__ 158 traceback.print_exception(type, value, tb, file=sys.__stderr__) 159 exit() 160 else: 161 continue 162 163 def manage_socket(address): 164 for i in range(3): 165 time.sleep(i) 166 try: 167 server = MyRPCServer(address, MyHandler) 168 break 169 except OSError as err: 170 print("IDLE Subprocess: OSError: " + err.args[1] + 171 ", retrying....", file=sys.__stderr__) 172 socket_error = err 173 else: 174 print("IDLE Subprocess: Connection to " 175 "IDLE GUI failed, exiting.", file=sys.__stderr__) 176 show_socket_error(socket_error, address) 177 global exit_now 178 exit_now = True 179 return 180 server.handle_request() # A single request only 181 182 def show_socket_error(err, address): 183 import tkinter 184 import tkinter.messagebox as tkMessageBox 185 root = tkinter.Tk() 186 root.withdraw() 187 if err.args[0] == 61: # connection refused 188 msg = "IDLE's subprocess can't connect to %s:%d. This may be due "\ 189 "to your personal firewall configuration. It is safe to "\ 190 "allow this internal connection because no data is visible on "\ 191 "external ports." % address 192 tkMessageBox.showerror("IDLE Subprocess Error", msg, parent=root) 193 else: 194 tkMessageBox.showerror("IDLE Subprocess Error", 195 "Socket Error: %s" % err.args[1], parent=root) 196 root.destroy() 197 198 def print_exception(): 199 import linecache 200 linecache.checkcache() 201 flush_stdout() 202 efile = sys.stderr 203 typ, val, tb = excinfo = sys.exc_info() 204 sys.last_type, sys.last_value, sys.last_traceback = excinfo 205 seen = set() 206 207 def print_exc(typ, exc, tb): 208 seen.add(exc) 209 context = exc.__context__ 210 cause = exc.__cause__ 211 if cause is not None and cause not in seen: 212 print_exc(type(cause), cause, cause.__traceback__) 213 print("\nThe above exception was the direct cause " 214 "of the following exception:\n", file=efile) 215 elif (context is not None and 216 not exc.__suppress_context__ and 217 context not in seen): 218 print_exc(type(context), context, context.__traceback__) 219 print("\nDuring handling of the above exception, " 220 "another exception occurred:\n", file=efile) 221 if tb: 222 tbe = traceback.extract_tb(tb) 223 print('Traceback (most recent call last):', file=efile) 224 exclude = ("run.py", "rpc.py", "threading.py", "queue.py", 225 "debugger_r.py", "bdb.py") 226 cleanup_traceback(tbe, exclude) 227 traceback.print_list(tbe, file=efile) 228 lines = traceback.format_exception_only(typ, exc) 229 for line in lines: 230 print(line, end='', file=efile) 231 232 print_exc(typ, val, tb) 233 234 def cleanup_traceback(tb, exclude): 235 "Remove excluded traces from beginning/end of tb; get cached lines" 236 orig_tb = tb[:] 237 while tb: 238 for rpcfile in exclude: 239 if tb[0][0].count(rpcfile): 240 break # found an exclude, break for: and delete tb[0] 241 else: 242 break # no excludes, have left RPC code, break while: 243 del tb[0] 244 while tb: 245 for rpcfile in exclude: 246 if tb[-1][0].count(rpcfile): 247 break 248 else: 249 break 250 del tb[-1] 251 if len(tb) == 0: 252 # exception was in IDLE internals, don't prune! 253 tb[:] = orig_tb[:] 254 print("** IDLE Internal Exception: ", file=sys.stderr) 255 rpchandler = rpc.objecttable['exec'].rpchandler 256 for i in range(len(tb)): 257 fn, ln, nm, line = tb[i] 258 if nm == '?': 259 nm = "-toplevel-" 260 if not line and fn.startswith("<pyshell#"): 261 line = rpchandler.remotecall('linecache', 'getline', 262 (fn, ln), {}) 263 tb[i] = fn, ln, nm, line 264 265 def flush_stdout(): 266 """XXX How to do this now?""" 267 268 def exit(): 269 """Exit subprocess, possibly after first clearing exit functions. 270 271 If config-main.cfg/.def 'General' 'delete-exitfunc' is True, then any 272 functions registered with atexit will be removed before exiting. 273 (VPython support) 274 275 """ 276 if no_exitfunc: 277 import atexit 278 atexit._clear() 279 capture_warnings(False) 280 sys.exit(0) 281 282 283 class MyRPCServer(rpc.RPCServer): 284 285 def handle_error(self, request, client_address): 286 """Override RPCServer method for IDLE 287 288 Interrupt the MainThread and exit server if link is dropped. 289 290 """ 291 global quitting 292 try: 293 raise 294 except SystemExit: 295 raise 296 except EOFError: 297 global exit_now 298 exit_now = True 299 thread.interrupt_main() 300 except: 301 erf = sys.__stderr__ 302 print('\n' + '-'*40, file=erf) 303 print('Unhandled server exception!', file=erf) 304 print('Thread: %s' % threading.current_thread().name, file=erf) 305 print('Client Address: ', client_address, file=erf) 306 print('Request: ', repr(request), file=erf) 307 traceback.print_exc(file=erf) 308 print('\n*** Unrecoverable, server exiting!', file=erf) 309 print('-'*40, file=erf) 310 quitting = True 311 thread.interrupt_main() 312 313 314 # Pseudofiles for shell-remote communication (also used in pyshell) 315 316 class PseudoFile(io.TextIOBase): 317 318 def __init__(self, shell, tags, encoding=None): 319 self.shell = shell 320 self.tags = tags 321 self._encoding = encoding 322 323 @property 324 def encoding(self): 325 return self._encoding 326 327 @property 328 def name(self): 329 return '<%s>' % self.tags 330 331 def isatty(self): 332 return True 333 334 335 class PseudoOutputFile(PseudoFile): 336 337 def writable(self): 338 return True 339 340 def write(self, s): 341 if self.closed: 342 raise ValueError("write to closed file") 343 if type(s) is not str: 344 if not isinstance(s, str): 345 raise TypeError('must be str, not ' + type(s).__name__) 346 # See issue #19481 347 s = str.__str__(s) 348 return self.shell.write(s, self.tags) 349 350 351 class PseudoInputFile(PseudoFile): 352 353 def __init__(self, shell, tags, encoding=None): 354 PseudoFile.__init__(self, shell, tags, encoding) 355 self._line_buffer = '' 356 357 def readable(self): 358 return True 359 360 def read(self, size=-1): 361 if self.closed: 362 raise ValueError("read from closed file") 363 if size is None: 364 size = -1 365 elif not isinstance(size, int): 366 raise TypeError('must be int, not ' + type(size).__name__) 367 result = self._line_buffer 368 self._line_buffer = '' 369 if size < 0: 370 while True: 371 line = self.shell.readline() 372 if not line: break 373 result += line 374 else: 375 while len(result) < size: 376 line = self.shell.readline() 377 if not line: break 378 result += line 379 self._line_buffer = result[size:] 380 result = result[:size] 381 return result 382 383 def readline(self, size=-1): 384 if self.closed: 385 raise ValueError("read from closed file") 386 if size is None: 387 size = -1 388 elif not isinstance(size, int): 389 raise TypeError('must be int, not ' + type(size).__name__) 390 line = self._line_buffer or self.shell.readline() 391 if size < 0: 392 size = len(line) 393 eol = line.find('\n', 0, size) 394 if eol >= 0: 395 size = eol + 1 396 self._line_buffer = line[size:] 397 return line[:size] 398 399 def close(self): 400 self.shell.close() 401 402 403 class MyHandler(rpc.RPCHandler): 404 405 def handle(self): 406 """Override base method""" 407 executive = Executive(self) 408 self.register("exec", executive) 409 self.console = self.get_remote_proxy("console") 410 sys.stdin = PseudoInputFile(self.console, "stdin", 411 iomenu.encoding) 412 sys.stdout = PseudoOutputFile(self.console, "stdout", 413 iomenu.encoding) 414 sys.stderr = PseudoOutputFile(self.console, "stderr", 415 iomenu.encoding) 416 417 sys.displayhook = rpc.displayhook 418 # page help() text to shell. 419 import pydoc # import must be done here to capture i/o binding 420 pydoc.pager = pydoc.plainpager 421 422 # Keep a reference to stdin so that it won't try to exit IDLE if 423 # sys.stdin gets changed from within IDLE's shell. See issue17838. 424 self._keep_stdin = sys.stdin 425 426 self.interp = self.get_remote_proxy("interp") 427 rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) 428 429 def exithook(self): 430 "override SocketIO method - wait for MainThread to shut us down" 431 time.sleep(10) 432 433 def EOFhook(self): 434 "Override SocketIO method - terminate wait on callback and exit thread" 435 global quitting 436 quitting = True 437 thread.interrupt_main() 438 439 def decode_interrupthook(self): 440 "interrupt awakened thread" 441 global quitting 442 quitting = True 443 thread.interrupt_main() 444 445 446 class Executive(object): 447 448 def __init__(self, rpchandler): 449 self.rpchandler = rpchandler 450 self.locals = __main__.__dict__ 451 self.calltip = calltips.CallTips() 452 self.autocomplete = autocomplete.AutoComplete() 453 454 def runcode(self, code): 455 global interruptable 456 try: 457 self.usr_exc_info = None 458 interruptable = True 459 try: 460 exec(code, self.locals) 461 finally: 462 interruptable = False 463 except SystemExit: 464 # Scripts that raise SystemExit should just 465 # return to the interactive prompt 466 pass 467 except: 468 self.usr_exc_info = sys.exc_info() 469 if quitting: 470 exit() 471 # even print a user code SystemExit exception, continue 472 print_exception() 473 jit = self.rpchandler.console.getvar("<<toggle-jit-stack-viewer>>") 474 if jit: 475 self.rpchandler.interp.open_remote_stack_viewer() 476 else: 477 flush_stdout() 478 479 def interrupt_the_server(self): 480 if interruptable: 481 thread.interrupt_main() 482 483 def start_the_debugger(self, gui_adap_oid): 484 return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) 485 486 def stop_the_debugger(self, idb_adap_oid): 487 "Unregister the Idb Adapter. Link objects and Idb then subject to GC" 488 self.rpchandler.unregister(idb_adap_oid) 489 490 def get_the_calltip(self, name): 491 return self.calltip.fetch_tip(name) 492 493 def get_the_completion_list(self, what, mode): 494 return self.autocomplete.fetch_completions(what, mode) 495 496 def stackviewer(self, flist_oid=None): 497 if self.usr_exc_info: 498 typ, val, tb = self.usr_exc_info 499 else: 500 return None 501 flist = None 502 if flist_oid is not None: 503 flist = self.rpchandler.get_remote_proxy(flist_oid) 504 while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]: 505 tb = tb.tb_next 506 sys.last_type = typ 507 sys.last_value = val 508 item = stackviewer.StackTreeItem(flist, tb) 509 return debugobj_r.remote_object_tree_item(item) 510 511 capture_warnings(False) # Make sure turned off; see issue 18081 512