summaryrefslogtreecommitdiff
path: root/Lib/idlelib/PyShell.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib/PyShell.py')
-rwxr-xr-x[-rw-r--r--]Lib/idlelib/PyShell.py197
1 files changed, 136 insertions, 61 deletions
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 865472eb19..2e5ebb233b 100644..100755
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -16,6 +16,7 @@ import io
import linecache
from code import InteractiveInterpreter
+from platform import python_version, system
try:
from tkinter import *
@@ -44,35 +45,55 @@ PORT = 0 # someday pass in host, port for remote debug capability
# internal warnings to the console. ScriptBinding.check_syntax() will
# temporarily redirect the stream to the shell window to display warnings when
# checking user's code.
-global warning_stream
-warning_stream = sys.__stderr__
-try:
- import warnings
-except ImportError:
- pass
-else:
- def idle_showwarning(message, category, filename, lineno,
- file=None, line=None):
- if file is None:
- file = warning_stream
- try:
- file.write(warnings.formatwarning(message, category, filename,
- lineno, line=line))
- except IOError:
- pass ## file (probably __stderr__) is invalid, warning dropped.
- warnings.showwarning = idle_showwarning
- def idle_formatwarning(message, category, filename, lineno, line=None):
- """Format warnings the IDLE way"""
- s = "\nWarning (from warnings module):\n"
- s += ' File \"%s\", line %s\n' % (filename, lineno)
- if line is None:
- line = linecache.getline(filename, lineno)
- line = line.strip()
- if line:
- s += " %s\n" % line
- s += "%s: %s\n>>> " % (category.__name__, message)
- return s
- warnings.formatwarning = idle_formatwarning
+warning_stream = sys.__stderr__ # None, at least on Windows, if no console.
+import warnings
+
+def idle_formatwarning(message, category, filename, lineno, line=None):
+ """Format warnings the IDLE way."""
+
+ s = "\nWarning (from warnings module):\n"
+ s += ' File \"%s\", line %s\n' % (filename, lineno)
+ if line is None:
+ line = linecache.getline(filename, lineno)
+ line = line.strip()
+ if line:
+ s += " %s\n" % line
+ s += "%s: %s\n" % (category.__name__, message)
+ return s
+
+def idle_showwarning(
+ message, category, filename, lineno, file=None, line=None):
+ """Show Idle-format warning (after replacing warnings.showwarning).
+
+ The differences are the formatter called, the file=None replacement,
+ which can be None, the capture of the consequence AttributeError,
+ and the output of a hard-coded prompt.
+ """
+ if file is None:
+ file = warning_stream
+ try:
+ file.write(idle_formatwarning(
+ message, category, filename, lineno, line=line))
+ file.write(">>> ")
+ except (AttributeError, OSError):
+ pass # if file (probably __stderr__) is invalid, skip warning.
+
+_warnings_showwarning = None
+
+def capture_warnings(capture):
+ "Replace warning.showwarning with idle_showwarning, or reverse."
+
+ global _warnings_showwarning
+ if capture:
+ if _warnings_showwarning is None:
+ _warnings_showwarning = warnings.showwarning
+ warnings.showwarning = idle_showwarning
+ else:
+ if _warnings_showwarning is not None:
+ warnings.showwarning = _warnings_showwarning
+ _warnings_showwarning = None
+
+capture_warnings(True)
def extended_linecache_checkcache(filename=None,
orig_checkcache=linecache.checkcache):
@@ -110,12 +131,13 @@ class PyShellEditorWindow(EditorWindow):
self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
'breakpoints.lst')
# whenever a file is changed, restore breakpoints
- if self.io.filename: self.restore_file_breaks()
def filename_changed_hook(old_hook=self.io.filename_change_hook,
self=self):
self.restore_file_breaks()
old_hook()
self.io.set_filename_change_hook(filename_changed_hook)
+ if self.io.filename:
+ self.restore_file_breaks()
rmenu_specs = [
("Cut", "<<cut>>", "rmenu_check_cut"),
@@ -211,7 +233,7 @@ class PyShellEditorWindow(EditorWindow):
try:
with open(self.breakpointPath, "r") as fp:
lines = fp.readlines()
- except IOError:
+ except OSError:
lines = []
try:
with open(self.breakpointPath, "w") as new_file:
@@ -222,7 +244,7 @@ class PyShellEditorWindow(EditorWindow):
breaks = self.breakpoints
if breaks:
new_file.write(filename + '=' + str(breaks) + '\n')
- except IOError as err:
+ except OSError as err:
if not getattr(self.root, "breakpoint_error_displayed", False):
self.root.breakpoint_error_displayed = True
tkMessageBox.showerror(title='IDLE Error',
@@ -232,6 +254,9 @@ class PyShellEditorWindow(EditorWindow):
def restore_file_breaks(self):
self.text.update() # this enables setting "BREAK" tags to be visible
+ if self.io is None:
+ # can happen if IDLE closes due to the .update() call
+ return
filename = self.io.filename
if filename is None:
return
@@ -362,6 +387,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.port = PORT
self.original_compiler_flags = self.compile.compiler.flags
+ _afterid = None
rpcclt = None
rpcsubproc = None
@@ -453,6 +479,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
self.display_no_subprocess_error()
return None
self.transfer_path(with_cwd=with_cwd)
+ console.stop_readline()
# annotate restart in shell window and mark it
console.text.delete("iomark", "end-1c")
if was_executing:
@@ -480,6 +507,12 @@ class ModifiedInterpreter(InteractiveInterpreter):
threading.Thread(target=self.__request_interrupt).start()
def kill_subprocess(self):
+ if self._afterid is not None:
+ self.tkconsole.text.after_cancel(self._afterid)
+ try:
+ self.rpcclt.listening_sock.close()
+ except AttributeError: # no socket
+ pass
try:
self.rpcclt.close()
except AttributeError: # no socket
@@ -522,7 +555,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
return
try:
response = clt.pollresponse(self.active_seq, wait=0.05)
- except (EOFError, IOError, KeyboardInterrupt):
+ except (EOFError, OSError, KeyboardInterrupt):
# lost connection or subprocess terminated itself, restart
# [the KBI is from rpc.SocketIO.handle_EOF()]
if self.tkconsole.closing:
@@ -551,8 +584,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
pass
# Reschedule myself
if not self.tkconsole.closing:
- self.tkconsole.text.after(self.tkconsole.pollinterval,
- self.poll_subprocess)
+ self._afterid = self.tkconsole.text.after(
+ self.tkconsole.pollinterval, self.poll_subprocess)
debugger = None
@@ -795,7 +828,7 @@ class ModifiedInterpreter(InteractiveInterpreter):
class PyShell(OutputWindow):
- shell_title = "Python Shell"
+ shell_title = "Python " + python_version() + " Shell"
# Override classes
ColorDelegator = ModifiedColorDelegator
@@ -812,7 +845,6 @@ class PyShell(OutputWindow):
]
if macosxSupport.runningAsOSXApp():
- del menu_specs[-3]
menu_specs[-2] = ("windows", "_Window")
@@ -848,8 +880,6 @@ class PyShell(OutputWindow):
text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
text.bind("<<toggle-debugger>>", self.toggle_debugger)
text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
- self.color = color = self.ColorDelegator()
- self.per.insertfilter(color)
if use_subprocess:
text.bind("<<view-restart>>", self.view_restart_mark)
text.bind("<<restart-shell>>", self.restart_shell)
@@ -887,6 +917,7 @@ class PyShell(OutputWindow):
canceled = False
endoffile = False
closing = False
+ _stop_readline_flag = False
def set_warning_stream(self, stream):
global warning_stream
@@ -962,14 +993,9 @@ class PyShell(OutputWindow):
parent=self.text)
if response is False:
return "cancel"
- if self.reading:
- self.top.quit()
+ self.stop_readline()
self.canceled = True
self.closing = True
- # Wait for poll_subprocess() rescheduling to stop
- self.text.after(2 * self.pollinterval, self.close2)
-
- def close2(self):
return EditorWindow.close(self)
def _close(self):
@@ -1009,6 +1035,8 @@ class PyShell(OutputWindow):
return False
else:
nosub = "==== No Subprocess ===="
+ sys.displayhook = rpc.displayhook
+
self.write("Python %s on %s\n%s\n%s" %
(sys.version, sys.platform, self.COPYRIGHT, nosub))
self.showprompt()
@@ -1016,6 +1044,12 @@ class PyShell(OutputWindow):
tkinter._default_root = None # 03Jan04 KBK What's this?
return True
+ def stop_readline(self):
+ if not self.reading: # no nested mainloop to exit.
+ return
+ self._stop_readline_flag = True
+ self.top.quit()
+
def readline(self):
save = self.reading
try:
@@ -1023,6 +1057,9 @@ class PyShell(OutputWindow):
self.top.mainloop() # nested mainloop()
finally:
self.reading = save
+ if self._stop_readline_flag:
+ self._stop_readline_flag = False
+ return ""
line = self.text.get("iomark", "end-1c")
if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C
line = "\n"
@@ -1224,13 +1261,23 @@ class PyShell(OutputWindow):
def resetoutput(self):
source = self.text.get("iomark", "end-1c")
if self.history:
- self.history.history_store(source)
+ self.history.store(source)
if self.text.get("end-2c") != "\n":
self.text.insert("end-1c", "\n")
self.text.mark_set("iomark", "end-1c")
self.set_line_and_column()
def write(self, s, tags=()):
+ if isinstance(s, str) and len(s) and max(s) > '\uffff':
+ # Tk doesn't support outputting non-BMP characters
+ # Let's assume what printed string is not very long,
+ # find first non-BMP character and construct informative
+ # UnicodeEncodeError exception.
+ for start, char in enumerate(s):
+ if char > '\uffff':
+ break
+ raise UnicodeEncodeError("UCS-2", char, start, start+1,
+ 'Non-BMP character not supported in Tk')
try:
self.text.mark_gravity("iomark", "right")
count = OutputWindow.write(self, s, tags, "iomark")
@@ -1284,8 +1331,11 @@ class PseudoOutputFile(PseudoFile):
def write(self, s):
if self.closed:
raise ValueError("write to closed file")
- if not isinstance(s, str):
- raise TypeError('must be str, not ' + type(s).__name__)
+ if type(s) is not str:
+ if not isinstance(s, str):
+ raise TypeError('must be str, not ' + type(s).__name__)
+ # See issue #19481
+ s = str.__str__(s)
return self.shell.write(s, self.tags)
@@ -1331,9 +1381,15 @@ class PseudoInputFile(PseudoFile):
line = self._line_buffer or self.shell.readline()
if size < 0:
size = len(line)
+ eol = line.find('\n', 0, size)
+ if eol >= 0:
+ size = eol + 1
self._line_buffer = line[size:]
return line[:size]
+ def close(self):
+ self.shell.close()
+
usage_msg = """\
@@ -1391,8 +1447,9 @@ echo "import sys; print(sys.argv)" | idle - "foobar"
def main():
global flist, root, use_subprocess
+ capture_warnings(True)
use_subprocess = True
- enable_shell = True
+ enable_shell = False
enable_edit = False
debug = False
cmd = None
@@ -1413,7 +1470,6 @@ def main():
enable_shell = True
if o == '-e':
enable_edit = True
- enable_shell = False
if o == '-h':
sys.stdout.write(usage_msg)
sys.exit()
@@ -1464,9 +1520,22 @@ def main():
edit_start = idleConf.GetOption('main', 'General',
'editor-on-startup', type='bool')
enable_edit = enable_edit or edit_start
+ enable_shell = enable_shell or not enable_edit
# start editor and/or shell windows:
root = Tk(className="Idle")
+ # set application icon
+ icondir = os.path.join(os.path.dirname(__file__), 'Icons')
+ if system() == 'Windows':
+ iconfile = os.path.join(icondir, 'idle.ico')
+ root.wm_iconbitmap(default=iconfile)
+ elif TkVersion >= 8.5:
+ ext = '.png' if TkVersion >= 8.6 else '.gif'
+ iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext))
+ for size in (16, 32, 48)]
+ icons = [PhotoImage(file=iconfile) for iconfile in iconfiles]
+ root.wm_iconphoto(True, *icons)
+
fixwordbreaks(root)
root.withdraw()
flist = PyShellFileList(root)
@@ -1480,20 +1549,22 @@ def main():
args.remove(filename)
if not args:
flist.new()
+
if enable_shell:
shell = flist.open_shell()
if not shell:
return # couldn't open shell
-
if macosxSupport.runningAsOSXApp() and flist.dict:
# On OSX: when the user has double-clicked on a file that causes
# IDLE to be launched the shell window will open just in front of
# the file she wants to see. Lower the interpreter window when
# there are open files.
shell.top.lower()
+ else:
+ shell = flist.pyshell
- shell = flist.pyshell
- # handle remaining options:
+ # Handle remaining options. If any of these are set, enable_shell
+ # was set also, so shell must be true to reach here.
if debug:
shell.open_debugger()
if startup:
@@ -1501,7 +1572,7 @@ def main():
os.environ.get("PYTHONSTARTUP")
if filename and os.path.isfile(filename):
shell.interp.execfile(filename)
- if shell and cmd or script:
+ if cmd or script:
shell.interp.runcommand("""if 1:
import sys as _sys
_sys.argv = %r
@@ -1512,18 +1583,22 @@ def main():
elif script:
shell.interp.prepend_syspath(script)
shell.interp.execfile(script)
-
- # Check for problematic OS X Tk versions and print a warning message
- # in the IDLE shell window; this is less intrusive than always opening
- # a separate window.
- tkversionwarning = macosxSupport.tkVersionWarning(root)
- if tkversionwarning:
- shell.interp.runcommand(''.join(("print('", tkversionwarning, "')")))
+ elif shell:
+ # If there is a shell window and no cmd or script in progress,
+ # check for problematic OS X Tk versions and print a warning
+ # message in the IDLE shell window; this is less intrusive
+ # than always opening a separate window.
+ tkversionwarning = macosxSupport.tkVersionWarning(root)
+ if tkversionwarning:
+ shell.interp.runcommand("print('%s')" % tkversionwarning)
while flist.inversedict: # keep IDLE running while files are open.
root.mainloop()
root.destroy()
+ capture_warnings(False)
if __name__ == "__main__":
sys.modules['PyShell'] = sys.modules['__main__']
main()
+
+capture_warnings(False) # Make sure turned off; see issue 18081