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