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