jpayne@69: #! /usr/bin/env python3 jpayne@69: jpayne@69: import sys jpayne@69: if __name__ == "__main__": jpayne@69: sys.modules['idlelib.pyshell'] = sys.modules['__main__'] jpayne@69: jpayne@69: try: jpayne@69: from tkinter import * jpayne@69: except ImportError: jpayne@69: print("** IDLE can't import Tkinter.\n" jpayne@69: "Your Python may not be configured for Tk. **", file=sys.__stderr__) jpayne@69: raise SystemExit(1) jpayne@69: jpayne@69: # Valid arguments for the ...Awareness call below are defined in the following. jpayne@69: # https://msdn.microsoft.com/en-us/library/windows/desktop/dn280512(v=vs.85).aspx jpayne@69: if sys.platform == 'win32': jpayne@69: try: jpayne@69: import ctypes jpayne@69: PROCESS_SYSTEM_DPI_AWARE = 1 jpayne@69: ctypes.OleDLL('shcore').SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE) jpayne@69: except (ImportError, AttributeError, OSError): jpayne@69: pass jpayne@69: jpayne@69: import tkinter.messagebox as tkMessageBox jpayne@69: if TkVersion < 8.5: jpayne@69: root = Tk() # otherwise create root in main jpayne@69: root.withdraw() jpayne@69: from idlelib.run import fix_scaling jpayne@69: fix_scaling(root) jpayne@69: tkMessageBox.showerror("Idle Cannot Start", jpayne@69: "Idle requires tcl/tk 8.5+, not %s." % TkVersion, jpayne@69: parent=root) jpayne@69: raise SystemExit(1) jpayne@69: jpayne@69: from code import InteractiveInterpreter jpayne@69: import linecache jpayne@69: import os jpayne@69: import os.path jpayne@69: from platform import python_version jpayne@69: import re jpayne@69: import socket jpayne@69: import subprocess jpayne@69: from textwrap import TextWrapper jpayne@69: import threading jpayne@69: import time jpayne@69: import tokenize jpayne@69: import warnings jpayne@69: jpayne@69: from idlelib.colorizer import ColorDelegator jpayne@69: from idlelib.config import idleConf jpayne@69: from idlelib import debugger jpayne@69: from idlelib import debugger_r jpayne@69: from idlelib.editor import EditorWindow, fixwordbreaks jpayne@69: from idlelib.filelist import FileList jpayne@69: from idlelib.outwin import OutputWindow jpayne@69: from idlelib import rpc jpayne@69: from idlelib.run import idle_formatwarning, StdInputFile, StdOutputFile jpayne@69: from idlelib.undo import UndoDelegator jpayne@69: jpayne@69: HOST = '127.0.0.1' # python execution server on localhost loopback jpayne@69: PORT = 0 # someday pass in host, port for remote debug capability jpayne@69: jpayne@69: # Override warnings module to write to warning_stream. Initialize to send IDLE jpayne@69: # internal warnings to the console. ScriptBinding.check_syntax() will jpayne@69: # temporarily redirect the stream to the shell window to display warnings when jpayne@69: # checking user's code. jpayne@69: warning_stream = sys.__stderr__ # None, at least on Windows, if no console. jpayne@69: jpayne@69: def idle_showwarning( jpayne@69: message, category, filename, lineno, file=None, line=None): jpayne@69: """Show Idle-format warning (after replacing warnings.showwarning). jpayne@69: jpayne@69: The differences are the formatter called, the file=None replacement, jpayne@69: which can be None, the capture of the consequence AttributeError, jpayne@69: and the output of a hard-coded prompt. jpayne@69: """ jpayne@69: if file is None: jpayne@69: file = warning_stream jpayne@69: try: jpayne@69: file.write(idle_formatwarning( jpayne@69: message, category, filename, lineno, line=line)) jpayne@69: file.write(">>> ") jpayne@69: except (AttributeError, OSError): jpayne@69: pass # if file (probably __stderr__) is invalid, skip warning. jpayne@69: jpayne@69: _warnings_showwarning = None jpayne@69: jpayne@69: def capture_warnings(capture): jpayne@69: "Replace warning.showwarning with idle_showwarning, or reverse." jpayne@69: jpayne@69: global _warnings_showwarning jpayne@69: if capture: jpayne@69: if _warnings_showwarning is None: jpayne@69: _warnings_showwarning = warnings.showwarning jpayne@69: warnings.showwarning = idle_showwarning jpayne@69: else: jpayne@69: if _warnings_showwarning is not None: jpayne@69: warnings.showwarning = _warnings_showwarning jpayne@69: _warnings_showwarning = None jpayne@69: jpayne@69: capture_warnings(True) jpayne@69: jpayne@69: def extended_linecache_checkcache(filename=None, jpayne@69: orig_checkcache=linecache.checkcache): jpayne@69: """Extend linecache.checkcache to preserve the entries jpayne@69: jpayne@69: Rather than repeating the linecache code, patch it to save the jpayne@69: entries, call the original linecache.checkcache() jpayne@69: (skipping them), and then restore the saved entries. jpayne@69: jpayne@69: orig_checkcache is bound at definition time to the original jpayne@69: method, allowing it to be patched. jpayne@69: """ jpayne@69: cache = linecache.cache jpayne@69: save = {} jpayne@69: for key in list(cache): jpayne@69: if key[:1] + key[-1:] == '<>': jpayne@69: save[key] = cache.pop(key) jpayne@69: orig_checkcache(filename) jpayne@69: cache.update(save) jpayne@69: jpayne@69: # Patch linecache.checkcache(): jpayne@69: linecache.checkcache = extended_linecache_checkcache jpayne@69: jpayne@69: jpayne@69: class PyShellEditorWindow(EditorWindow): jpayne@69: "Regular text edit window in IDLE, supports breakpoints" jpayne@69: jpayne@69: def __init__(self, *args): jpayne@69: self.breakpoints = [] jpayne@69: EditorWindow.__init__(self, *args) jpayne@69: self.text.bind("<>", self.set_breakpoint_here) jpayne@69: self.text.bind("<>", self.clear_breakpoint_here) jpayne@69: self.text.bind("<>", self.flist.open_shell) jpayne@69: jpayne@69: #TODO: don't read/write this from/to .idlerc when testing jpayne@69: self.breakpointPath = os.path.join( jpayne@69: idleConf.userdir, 'breakpoints.lst') jpayne@69: # whenever a file is changed, restore breakpoints jpayne@69: def filename_changed_hook(old_hook=self.io.filename_change_hook, jpayne@69: self=self): jpayne@69: self.restore_file_breaks() jpayne@69: old_hook() jpayne@69: self.io.set_filename_change_hook(filename_changed_hook) jpayne@69: if self.io.filename: jpayne@69: self.restore_file_breaks() jpayne@69: self.color_breakpoint_text() jpayne@69: jpayne@69: rmenu_specs = [ jpayne@69: ("Cut", "<>", "rmenu_check_cut"), jpayne@69: ("Copy", "<>", "rmenu_check_copy"), jpayne@69: ("Paste", "<>", "rmenu_check_paste"), jpayne@69: (None, None, None), jpayne@69: ("Set Breakpoint", "<>", None), jpayne@69: ("Clear Breakpoint", "<>", None) jpayne@69: ] jpayne@69: jpayne@69: def color_breakpoint_text(self, color=True): jpayne@69: "Turn colorizing of breakpoint text on or off" jpayne@69: if self.io is None: jpayne@69: # possible due to update in restore_file_breaks jpayne@69: return jpayne@69: if color: jpayne@69: theme = idleConf.CurrentTheme() jpayne@69: cfg = idleConf.GetHighlight(theme, "break") jpayne@69: else: jpayne@69: cfg = {'foreground': '', 'background': ''} jpayne@69: self.text.tag_config('BREAK', cfg) jpayne@69: jpayne@69: def set_breakpoint(self, lineno): jpayne@69: text = self.text jpayne@69: filename = self.io.filename jpayne@69: text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1)) jpayne@69: try: jpayne@69: self.breakpoints.index(lineno) jpayne@69: except ValueError: # only add if missing, i.e. do once jpayne@69: self.breakpoints.append(lineno) jpayne@69: try: # update the subprocess debugger jpayne@69: debug = self.flist.pyshell.interp.debugger jpayne@69: debug.set_breakpoint_here(filename, lineno) jpayne@69: except: # but debugger may not be active right now.... jpayne@69: pass jpayne@69: jpayne@69: def set_breakpoint_here(self, event=None): jpayne@69: text = self.text jpayne@69: filename = self.io.filename jpayne@69: if not filename: jpayne@69: text.bell() jpayne@69: return jpayne@69: lineno = int(float(text.index("insert"))) jpayne@69: self.set_breakpoint(lineno) jpayne@69: jpayne@69: def clear_breakpoint_here(self, event=None): jpayne@69: text = self.text jpayne@69: filename = self.io.filename jpayne@69: if not filename: jpayne@69: text.bell() jpayne@69: return jpayne@69: lineno = int(float(text.index("insert"))) jpayne@69: try: jpayne@69: self.breakpoints.remove(lineno) jpayne@69: except: jpayne@69: pass jpayne@69: text.tag_remove("BREAK", "insert linestart",\ jpayne@69: "insert lineend +1char") jpayne@69: try: jpayne@69: debug = self.flist.pyshell.interp.debugger jpayne@69: debug.clear_breakpoint_here(filename, lineno) jpayne@69: except: jpayne@69: pass jpayne@69: jpayne@69: def clear_file_breaks(self): jpayne@69: if self.breakpoints: jpayne@69: text = self.text jpayne@69: filename = self.io.filename jpayne@69: if not filename: jpayne@69: text.bell() jpayne@69: return jpayne@69: self.breakpoints = [] jpayne@69: text.tag_remove("BREAK", "1.0", END) jpayne@69: try: jpayne@69: debug = self.flist.pyshell.interp.debugger jpayne@69: debug.clear_file_breaks(filename) jpayne@69: except: jpayne@69: pass jpayne@69: jpayne@69: def store_file_breaks(self): jpayne@69: "Save breakpoints when file is saved" jpayne@69: # XXX 13 Dec 2002 KBK Currently the file must be saved before it can jpayne@69: # be run. The breaks are saved at that time. If we introduce jpayne@69: # a temporary file save feature the save breaks functionality jpayne@69: # needs to be re-verified, since the breaks at the time the jpayne@69: # temp file is created may differ from the breaks at the last jpayne@69: # permanent save of the file. Currently, a break introduced jpayne@69: # after a save will be effective, but not persistent. jpayne@69: # This is necessary to keep the saved breaks synched with the jpayne@69: # saved file. jpayne@69: # jpayne@69: # Breakpoints are set as tagged ranges in the text. jpayne@69: # Since a modified file has to be saved before it is jpayne@69: # run, and since self.breakpoints (from which the subprocess jpayne@69: # debugger is loaded) is updated during the save, the visible jpayne@69: # breaks stay synched with the subprocess even if one of these jpayne@69: # unexpected breakpoint deletions occurs. jpayne@69: breaks = self.breakpoints jpayne@69: filename = self.io.filename jpayne@69: try: jpayne@69: with open(self.breakpointPath, "r") as fp: jpayne@69: lines = fp.readlines() jpayne@69: except OSError: jpayne@69: lines = [] jpayne@69: try: jpayne@69: with open(self.breakpointPath, "w") as new_file: jpayne@69: for line in lines: jpayne@69: if not line.startswith(filename + '='): jpayne@69: new_file.write(line) jpayne@69: self.update_breakpoints() jpayne@69: breaks = self.breakpoints jpayne@69: if breaks: jpayne@69: new_file.write(filename + '=' + str(breaks) + '\n') jpayne@69: except OSError as err: jpayne@69: if not getattr(self.root, "breakpoint_error_displayed", False): jpayne@69: self.root.breakpoint_error_displayed = True jpayne@69: tkMessageBox.showerror(title='IDLE Error', jpayne@69: message='Unable to update breakpoint list:\n%s' jpayne@69: % str(err), jpayne@69: parent=self.text) jpayne@69: jpayne@69: def restore_file_breaks(self): jpayne@69: self.text.update() # this enables setting "BREAK" tags to be visible jpayne@69: if self.io is None: jpayne@69: # can happen if IDLE closes due to the .update() call jpayne@69: return jpayne@69: filename = self.io.filename jpayne@69: if filename is None: jpayne@69: return jpayne@69: if os.path.isfile(self.breakpointPath): jpayne@69: with open(self.breakpointPath, "r") as fp: jpayne@69: lines = fp.readlines() jpayne@69: for line in lines: jpayne@69: if line.startswith(filename + '='): jpayne@69: breakpoint_linenumbers = eval(line[len(filename)+1:]) jpayne@69: for breakpoint_linenumber in breakpoint_linenumbers: jpayne@69: self.set_breakpoint(breakpoint_linenumber) jpayne@69: jpayne@69: def update_breakpoints(self): jpayne@69: "Retrieves all the breakpoints in the current window" jpayne@69: text = self.text jpayne@69: ranges = text.tag_ranges("BREAK") jpayne@69: linenumber_list = self.ranges_to_linenumbers(ranges) jpayne@69: self.breakpoints = linenumber_list jpayne@69: jpayne@69: def ranges_to_linenumbers(self, ranges): jpayne@69: lines = [] jpayne@69: for index in range(0, len(ranges), 2): jpayne@69: lineno = int(float(ranges[index].string)) jpayne@69: end = int(float(ranges[index+1].string)) jpayne@69: while lineno < end: jpayne@69: lines.append(lineno) jpayne@69: lineno += 1 jpayne@69: return lines jpayne@69: jpayne@69: # XXX 13 Dec 2002 KBK Not used currently jpayne@69: # def saved_change_hook(self): jpayne@69: # "Extend base method - clear breaks if module is modified" jpayne@69: # if not self.get_saved(): jpayne@69: # self.clear_file_breaks() jpayne@69: # EditorWindow.saved_change_hook(self) jpayne@69: jpayne@69: def _close(self): jpayne@69: "Extend base method - clear breaks when module is closed" jpayne@69: self.clear_file_breaks() jpayne@69: EditorWindow._close(self) jpayne@69: jpayne@69: jpayne@69: class PyShellFileList(FileList): jpayne@69: "Extend base class: IDLE supports a shell and breakpoints" jpayne@69: jpayne@69: # override FileList's class variable, instances return PyShellEditorWindow jpayne@69: # instead of EditorWindow when new edit windows are created. jpayne@69: EditorWindow = PyShellEditorWindow jpayne@69: jpayne@69: pyshell = None jpayne@69: jpayne@69: def open_shell(self, event=None): jpayne@69: if self.pyshell: jpayne@69: self.pyshell.top.wakeup() jpayne@69: else: jpayne@69: self.pyshell = PyShell(self) jpayne@69: if self.pyshell: jpayne@69: if not self.pyshell.begin(): jpayne@69: return None jpayne@69: return self.pyshell jpayne@69: jpayne@69: jpayne@69: class ModifiedColorDelegator(ColorDelegator): jpayne@69: "Extend base class: colorizer for the shell window itself" jpayne@69: jpayne@69: def __init__(self): jpayne@69: ColorDelegator.__init__(self) jpayne@69: self.LoadTagDefs() jpayne@69: jpayne@69: def recolorize_main(self): jpayne@69: self.tag_remove("TODO", "1.0", "iomark") jpayne@69: self.tag_add("SYNC", "1.0", "iomark") jpayne@69: ColorDelegator.recolorize_main(self) jpayne@69: jpayne@69: def LoadTagDefs(self): jpayne@69: ColorDelegator.LoadTagDefs(self) jpayne@69: theme = idleConf.CurrentTheme() jpayne@69: self.tagdefs.update({ jpayne@69: "stdin": {'background':None,'foreground':None}, jpayne@69: "stdout": idleConf.GetHighlight(theme, "stdout"), jpayne@69: "stderr": idleConf.GetHighlight(theme, "stderr"), jpayne@69: "console": idleConf.GetHighlight(theme, "console"), jpayne@69: }) jpayne@69: jpayne@69: def removecolors(self): jpayne@69: # Don't remove shell color tags before "iomark" jpayne@69: for tag in self.tagdefs: jpayne@69: self.tag_remove(tag, "iomark", "end") jpayne@69: jpayne@69: class ModifiedUndoDelegator(UndoDelegator): jpayne@69: "Extend base class: forbid insert/delete before the I/O mark" jpayne@69: jpayne@69: def insert(self, index, chars, tags=None): jpayne@69: try: jpayne@69: if self.delegate.compare(index, "<", "iomark"): jpayne@69: self.delegate.bell() jpayne@69: return jpayne@69: except TclError: jpayne@69: pass jpayne@69: UndoDelegator.insert(self, index, chars, tags) jpayne@69: jpayne@69: def delete(self, index1, index2=None): jpayne@69: try: jpayne@69: if self.delegate.compare(index1, "<", "iomark"): jpayne@69: self.delegate.bell() jpayne@69: return jpayne@69: except TclError: jpayne@69: pass jpayne@69: UndoDelegator.delete(self, index1, index2) jpayne@69: jpayne@69: jpayne@69: class MyRPCClient(rpc.RPCClient): jpayne@69: jpayne@69: def handle_EOF(self): jpayne@69: "Override the base class - just re-raise EOFError" jpayne@69: raise EOFError jpayne@69: jpayne@69: def restart_line(width, filename): # See bpo-38141. jpayne@69: """Return width long restart line formatted with filename. jpayne@69: jpayne@69: Fill line with balanced '='s, with any extras and at least one at jpayne@69: the beginning. Do not end with a trailing space. jpayne@69: """ jpayne@69: tag = f"= RESTART: {filename or 'Shell'} =" jpayne@69: if width >= len(tag): jpayne@69: div, mod = divmod((width -len(tag)), 2) jpayne@69: return f"{(div+mod)*'='}{tag}{div*'='}" jpayne@69: else: jpayne@69: return tag[:-2] # Remove ' ='. jpayne@69: jpayne@69: jpayne@69: class ModifiedInterpreter(InteractiveInterpreter): jpayne@69: jpayne@69: def __init__(self, tkconsole): jpayne@69: self.tkconsole = tkconsole jpayne@69: locals = sys.modules['__main__'].__dict__ jpayne@69: InteractiveInterpreter.__init__(self, locals=locals) jpayne@69: self.restarting = False jpayne@69: self.subprocess_arglist = None jpayne@69: self.port = PORT jpayne@69: self.original_compiler_flags = self.compile.compiler.flags jpayne@69: jpayne@69: _afterid = None jpayne@69: rpcclt = None jpayne@69: rpcsubproc = None jpayne@69: jpayne@69: def spawn_subprocess(self): jpayne@69: if self.subprocess_arglist is None: jpayne@69: self.subprocess_arglist = self.build_subprocess_arglist() jpayne@69: self.rpcsubproc = subprocess.Popen(self.subprocess_arglist) jpayne@69: jpayne@69: def build_subprocess_arglist(self): jpayne@69: assert (self.port!=0), ( jpayne@69: "Socket should have been assigned a port number.") jpayne@69: w = ['-W' + s for s in sys.warnoptions] jpayne@69: # Maybe IDLE is installed and is being accessed via sys.path, jpayne@69: # or maybe it's not installed and the idle.py script is being jpayne@69: # run from the IDLE source directory. jpayne@69: del_exitf = idleConf.GetOption('main', 'General', 'delete-exitfunc', jpayne@69: default=False, type='bool') jpayne@69: command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,) jpayne@69: return [sys.executable] + w + ["-c", command, str(self.port)] jpayne@69: jpayne@69: def start_subprocess(self): jpayne@69: addr = (HOST, self.port) jpayne@69: # GUI makes several attempts to acquire socket, listens for connection jpayne@69: for i in range(3): jpayne@69: time.sleep(i) jpayne@69: try: jpayne@69: self.rpcclt = MyRPCClient(addr) jpayne@69: break jpayne@69: except OSError: jpayne@69: pass jpayne@69: else: jpayne@69: self.display_port_binding_error() jpayne@69: return None jpayne@69: # if PORT was 0, system will assign an 'ephemeral' port. Find it out: jpayne@69: self.port = self.rpcclt.listening_sock.getsockname()[1] jpayne@69: # if PORT was not 0, probably working with a remote execution server jpayne@69: if PORT != 0: jpayne@69: # To allow reconnection within the 2MSL wait (cf. Stevens TCP jpayne@69: # V1, 18.6), set SO_REUSEADDR. Note that this can be problematic jpayne@69: # on Windows since the implementation allows two active sockets on jpayne@69: # the same address! jpayne@69: self.rpcclt.listening_sock.setsockopt(socket.SOL_SOCKET, jpayne@69: socket.SO_REUSEADDR, 1) jpayne@69: self.spawn_subprocess() jpayne@69: #time.sleep(20) # test to simulate GUI not accepting connection jpayne@69: # Accept the connection from the Python execution server jpayne@69: self.rpcclt.listening_sock.settimeout(10) jpayne@69: try: jpayne@69: self.rpcclt.accept() jpayne@69: except socket.timeout: jpayne@69: self.display_no_subprocess_error() jpayne@69: return None jpayne@69: self.rpcclt.register("console", self.tkconsole) jpayne@69: self.rpcclt.register("stdin", self.tkconsole.stdin) jpayne@69: self.rpcclt.register("stdout", self.tkconsole.stdout) jpayne@69: self.rpcclt.register("stderr", self.tkconsole.stderr) jpayne@69: self.rpcclt.register("flist", self.tkconsole.flist) jpayne@69: self.rpcclt.register("linecache", linecache) jpayne@69: self.rpcclt.register("interp", self) jpayne@69: self.transfer_path(with_cwd=True) jpayne@69: self.poll_subprocess() jpayne@69: return self.rpcclt jpayne@69: jpayne@69: def restart_subprocess(self, with_cwd=False, filename=''): jpayne@69: if self.restarting: jpayne@69: return self.rpcclt jpayne@69: self.restarting = True jpayne@69: # close only the subprocess debugger jpayne@69: debug = self.getdebugger() jpayne@69: if debug: jpayne@69: try: jpayne@69: # Only close subprocess debugger, don't unregister gui_adap! jpayne@69: debugger_r.close_subprocess_debugger(self.rpcclt) jpayne@69: except: jpayne@69: pass jpayne@69: # Kill subprocess, spawn a new one, accept connection. jpayne@69: self.rpcclt.close() jpayne@69: self.terminate_subprocess() jpayne@69: console = self.tkconsole jpayne@69: was_executing = console.executing jpayne@69: console.executing = False jpayne@69: self.spawn_subprocess() jpayne@69: try: jpayne@69: self.rpcclt.accept() jpayne@69: except socket.timeout: jpayne@69: self.display_no_subprocess_error() jpayne@69: return None jpayne@69: self.transfer_path(with_cwd=with_cwd) jpayne@69: console.stop_readline() jpayne@69: # annotate restart in shell window and mark it jpayne@69: console.text.delete("iomark", "end-1c") jpayne@69: console.write('\n') jpayne@69: console.write(restart_line(console.width, filename)) jpayne@69: console.text.mark_set("restart", "end-1c") jpayne@69: console.text.mark_gravity("restart", "left") jpayne@69: if not filename: jpayne@69: console.showprompt() jpayne@69: # restart subprocess debugger jpayne@69: if debug: jpayne@69: # Restarted debugger connects to current instance of debug GUI jpayne@69: debugger_r.restart_subprocess_debugger(self.rpcclt) jpayne@69: # reload remote debugger breakpoints for all PyShellEditWindows jpayne@69: debug.load_breakpoints() jpayne@69: self.compile.compiler.flags = self.original_compiler_flags jpayne@69: self.restarting = False jpayne@69: return self.rpcclt jpayne@69: jpayne@69: def __request_interrupt(self): jpayne@69: self.rpcclt.remotecall("exec", "interrupt_the_server", (), {}) jpayne@69: jpayne@69: def interrupt_subprocess(self): jpayne@69: threading.Thread(target=self.__request_interrupt).start() jpayne@69: jpayne@69: def kill_subprocess(self): jpayne@69: if self._afterid is not None: jpayne@69: self.tkconsole.text.after_cancel(self._afterid) jpayne@69: try: jpayne@69: self.rpcclt.listening_sock.close() jpayne@69: except AttributeError: # no socket jpayne@69: pass jpayne@69: try: jpayne@69: self.rpcclt.close() jpayne@69: except AttributeError: # no socket jpayne@69: pass jpayne@69: self.terminate_subprocess() jpayne@69: self.tkconsole.executing = False jpayne@69: self.rpcclt = None jpayne@69: jpayne@69: def terminate_subprocess(self): jpayne@69: "Make sure subprocess is terminated" jpayne@69: try: jpayne@69: self.rpcsubproc.kill() jpayne@69: except OSError: jpayne@69: # process already terminated jpayne@69: return jpayne@69: else: jpayne@69: try: jpayne@69: self.rpcsubproc.wait() jpayne@69: except OSError: jpayne@69: return jpayne@69: jpayne@69: def transfer_path(self, with_cwd=False): jpayne@69: if with_cwd: # Issue 13506 jpayne@69: path = [''] # include Current Working Directory jpayne@69: path.extend(sys.path) jpayne@69: else: jpayne@69: path = sys.path jpayne@69: jpayne@69: self.runcommand("""if 1: jpayne@69: import sys as _sys jpayne@69: _sys.path = %r jpayne@69: del _sys jpayne@69: \n""" % (path,)) jpayne@69: jpayne@69: active_seq = None jpayne@69: jpayne@69: def poll_subprocess(self): jpayne@69: clt = self.rpcclt jpayne@69: if clt is None: jpayne@69: return jpayne@69: try: jpayne@69: response = clt.pollresponse(self.active_seq, wait=0.05) jpayne@69: except (EOFError, OSError, KeyboardInterrupt): jpayne@69: # lost connection or subprocess terminated itself, restart jpayne@69: # [the KBI is from rpc.SocketIO.handle_EOF()] jpayne@69: if self.tkconsole.closing: jpayne@69: return jpayne@69: response = None jpayne@69: self.restart_subprocess() jpayne@69: if response: jpayne@69: self.tkconsole.resetoutput() jpayne@69: self.active_seq = None jpayne@69: how, what = response jpayne@69: console = self.tkconsole.console jpayne@69: if how == "OK": jpayne@69: if what is not None: jpayne@69: print(repr(what), file=console) jpayne@69: elif how == "EXCEPTION": jpayne@69: if self.tkconsole.getvar("<>"): jpayne@69: self.remote_stack_viewer() jpayne@69: elif how == "ERROR": jpayne@69: errmsg = "pyshell.ModifiedInterpreter: Subprocess ERROR:\n" jpayne@69: print(errmsg, what, file=sys.__stderr__) jpayne@69: print(errmsg, what, file=console) jpayne@69: # we received a response to the currently active seq number: jpayne@69: try: jpayne@69: self.tkconsole.endexecuting() jpayne@69: except AttributeError: # shell may have closed jpayne@69: pass jpayne@69: # Reschedule myself jpayne@69: if not self.tkconsole.closing: jpayne@69: self._afterid = self.tkconsole.text.after( jpayne@69: self.tkconsole.pollinterval, self.poll_subprocess) jpayne@69: jpayne@69: debugger = None jpayne@69: jpayne@69: def setdebugger(self, debugger): jpayne@69: self.debugger = debugger jpayne@69: jpayne@69: def getdebugger(self): jpayne@69: return self.debugger jpayne@69: jpayne@69: def open_remote_stack_viewer(self): jpayne@69: """Initiate the remote stack viewer from a separate thread. jpayne@69: jpayne@69: This method is called from the subprocess, and by returning from this jpayne@69: method we allow the subprocess to unblock. After a bit the shell jpayne@69: requests the subprocess to open the remote stack viewer which returns a jpayne@69: static object looking at the last exception. It is queried through jpayne@69: the RPC mechanism. jpayne@69: jpayne@69: """ jpayne@69: self.tkconsole.text.after(300, self.remote_stack_viewer) jpayne@69: return jpayne@69: jpayne@69: def remote_stack_viewer(self): jpayne@69: from idlelib import debugobj_r jpayne@69: oid = self.rpcclt.remotequeue("exec", "stackviewer", ("flist",), {}) jpayne@69: if oid is None: jpayne@69: self.tkconsole.root.bell() jpayne@69: return jpayne@69: item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid) jpayne@69: from idlelib.tree import ScrolledCanvas, TreeNode jpayne@69: top = Toplevel(self.tkconsole.root) jpayne@69: theme = idleConf.CurrentTheme() jpayne@69: background = idleConf.GetHighlight(theme, 'normal')['background'] jpayne@69: sc = ScrolledCanvas(top, bg=background, highlightthickness=0) jpayne@69: sc.frame.pack(expand=1, fill="both") jpayne@69: node = TreeNode(sc.canvas, None, item) jpayne@69: node.expand() jpayne@69: # XXX Should GC the remote tree when closing the window jpayne@69: jpayne@69: gid = 0 jpayne@69: jpayne@69: def execsource(self, source): jpayne@69: "Like runsource() but assumes complete exec source" jpayne@69: filename = self.stuffsource(source) jpayne@69: self.execfile(filename, source) jpayne@69: jpayne@69: def execfile(self, filename, source=None): jpayne@69: "Execute an existing file" jpayne@69: if source is None: jpayne@69: with tokenize.open(filename) as fp: jpayne@69: source = fp.read() jpayne@69: if use_subprocess: jpayne@69: source = (f"__file__ = r'''{os.path.abspath(filename)}'''\n" jpayne@69: + source + "\ndel __file__") jpayne@69: try: jpayne@69: code = compile(source, filename, "exec") jpayne@69: except (OverflowError, SyntaxError): jpayne@69: self.tkconsole.resetoutput() jpayne@69: print('*** Error in script or command!\n' jpayne@69: 'Traceback (most recent call last):', jpayne@69: file=self.tkconsole.stderr) jpayne@69: InteractiveInterpreter.showsyntaxerror(self, filename) jpayne@69: self.tkconsole.showprompt() jpayne@69: else: jpayne@69: self.runcode(code) jpayne@69: jpayne@69: def runsource(self, source): jpayne@69: "Extend base class method: Stuff the source in the line cache first" jpayne@69: filename = self.stuffsource(source) jpayne@69: self.more = 0 jpayne@69: # at the moment, InteractiveInterpreter expects str jpayne@69: assert isinstance(source, str) jpayne@69: # InteractiveInterpreter.runsource() calls its runcode() method, jpayne@69: # which is overridden (see below) jpayne@69: return InteractiveInterpreter.runsource(self, source, filename) jpayne@69: jpayne@69: def stuffsource(self, source): jpayne@69: "Stuff source in the filename cache" jpayne@69: filename = "" % self.gid jpayne@69: self.gid = self.gid + 1 jpayne@69: lines = source.split("\n") jpayne@69: linecache.cache[filename] = len(source)+1, 0, lines, filename jpayne@69: return filename jpayne@69: jpayne@69: def prepend_syspath(self, filename): jpayne@69: "Prepend sys.path with file's directory if not already included" jpayne@69: self.runcommand("""if 1: jpayne@69: _filename = %r jpayne@69: import sys as _sys jpayne@69: from os.path import dirname as _dirname jpayne@69: _dir = _dirname(_filename) jpayne@69: if not _dir in _sys.path: jpayne@69: _sys.path.insert(0, _dir) jpayne@69: del _filename, _sys, _dirname, _dir jpayne@69: \n""" % (filename,)) jpayne@69: jpayne@69: def showsyntaxerror(self, filename=None): jpayne@69: """Override Interactive Interpreter method: Use Colorizing jpayne@69: jpayne@69: Color the offending position instead of printing it and pointing at it jpayne@69: with a caret. jpayne@69: jpayne@69: """ jpayne@69: tkconsole = self.tkconsole jpayne@69: text = tkconsole.text jpayne@69: text.tag_remove("ERROR", "1.0", "end") jpayne@69: type, value, tb = sys.exc_info() jpayne@69: msg = getattr(value, 'msg', '') or value or "" jpayne@69: lineno = getattr(value, 'lineno', '') or 1 jpayne@69: offset = getattr(value, 'offset', '') or 0 jpayne@69: if offset == 0: jpayne@69: lineno += 1 #mark end of offending line jpayne@69: if lineno == 1: jpayne@69: pos = "iomark + %d chars" % (offset-1) jpayne@69: else: jpayne@69: pos = "iomark linestart + %d lines + %d chars" % \ jpayne@69: (lineno-1, offset-1) jpayne@69: tkconsole.colorize_syntax_error(text, pos) jpayne@69: tkconsole.resetoutput() jpayne@69: self.write("SyntaxError: %s\n" % msg) jpayne@69: tkconsole.showprompt() jpayne@69: jpayne@69: def showtraceback(self): jpayne@69: "Extend base class method to reset output properly" jpayne@69: self.tkconsole.resetoutput() jpayne@69: self.checklinecache() jpayne@69: InteractiveInterpreter.showtraceback(self) jpayne@69: if self.tkconsole.getvar("<>"): jpayne@69: self.tkconsole.open_stack_viewer() jpayne@69: jpayne@69: def checklinecache(self): jpayne@69: c = linecache.cache jpayne@69: for key in list(c.keys()): jpayne@69: if key[:1] + key[-1:] != "<>": jpayne@69: del c[key] jpayne@69: jpayne@69: def runcommand(self, code): jpayne@69: "Run the code without invoking the debugger" jpayne@69: # The code better not raise an exception! jpayne@69: if self.tkconsole.executing: jpayne@69: self.display_executing_dialog() jpayne@69: return 0 jpayne@69: if self.rpcclt: jpayne@69: self.rpcclt.remotequeue("exec", "runcode", (code,), {}) jpayne@69: else: jpayne@69: exec(code, self.locals) jpayne@69: return 1 jpayne@69: jpayne@69: def runcode(self, code): jpayne@69: "Override base class method" jpayne@69: if self.tkconsole.executing: jpayne@69: self.interp.restart_subprocess() jpayne@69: self.checklinecache() jpayne@69: debugger = self.debugger jpayne@69: try: jpayne@69: self.tkconsole.beginexecuting() jpayne@69: if not debugger and self.rpcclt is not None: jpayne@69: self.active_seq = self.rpcclt.asyncqueue("exec", "runcode", jpayne@69: (code,), {}) jpayne@69: elif debugger: jpayne@69: debugger.run(code, self.locals) jpayne@69: else: jpayne@69: exec(code, self.locals) jpayne@69: except SystemExit: jpayne@69: if not self.tkconsole.closing: jpayne@69: if tkMessageBox.askyesno( jpayne@69: "Exit?", jpayne@69: "Do you want to exit altogether?", jpayne@69: default="yes", jpayne@69: parent=self.tkconsole.text): jpayne@69: raise jpayne@69: else: jpayne@69: self.showtraceback() jpayne@69: else: jpayne@69: raise jpayne@69: except: jpayne@69: if use_subprocess: jpayne@69: print("IDLE internal error in runcode()", jpayne@69: file=self.tkconsole.stderr) jpayne@69: self.showtraceback() jpayne@69: self.tkconsole.endexecuting() jpayne@69: else: jpayne@69: if self.tkconsole.canceled: jpayne@69: self.tkconsole.canceled = False jpayne@69: print("KeyboardInterrupt", file=self.tkconsole.stderr) jpayne@69: else: jpayne@69: self.showtraceback() jpayne@69: finally: jpayne@69: if not use_subprocess: jpayne@69: try: jpayne@69: self.tkconsole.endexecuting() jpayne@69: except AttributeError: # shell may have closed jpayne@69: pass jpayne@69: jpayne@69: def write(self, s): jpayne@69: "Override base class method" jpayne@69: return self.tkconsole.stderr.write(s) jpayne@69: jpayne@69: def display_port_binding_error(self): jpayne@69: tkMessageBox.showerror( jpayne@69: "Port Binding Error", jpayne@69: "IDLE can't bind to a TCP/IP port, which is necessary to " jpayne@69: "communicate with its Python execution server. This might be " jpayne@69: "because no networking is installed on this computer. " jpayne@69: "Run IDLE with the -n command line switch to start without a " jpayne@69: "subprocess and refer to Help/IDLE Help 'Running without a " jpayne@69: "subprocess' for further details.", jpayne@69: parent=self.tkconsole.text) jpayne@69: jpayne@69: def display_no_subprocess_error(self): jpayne@69: tkMessageBox.showerror( jpayne@69: "Subprocess Connection Error", jpayne@69: "IDLE's subprocess didn't make connection.\n" jpayne@69: "See the 'Startup failure' section of the IDLE doc, online at\n" jpayne@69: "https://docs.python.org/3/library/idle.html#startup-failure", jpayne@69: parent=self.tkconsole.text) jpayne@69: jpayne@69: def display_executing_dialog(self): jpayne@69: tkMessageBox.showerror( jpayne@69: "Already executing", jpayne@69: "The Python Shell window is already executing a command; " jpayne@69: "please wait until it is finished.", jpayne@69: parent=self.tkconsole.text) jpayne@69: jpayne@69: jpayne@69: class PyShell(OutputWindow): jpayne@69: jpayne@69: shell_title = "Python " + python_version() + " Shell" jpayne@69: jpayne@69: # Override classes jpayne@69: ColorDelegator = ModifiedColorDelegator jpayne@69: UndoDelegator = ModifiedUndoDelegator jpayne@69: jpayne@69: # Override menus jpayne@69: menu_specs = [ jpayne@69: ("file", "_File"), jpayne@69: ("edit", "_Edit"), jpayne@69: ("debug", "_Debug"), jpayne@69: ("options", "_Options"), jpayne@69: ("window", "_Window"), jpayne@69: ("help", "_Help"), jpayne@69: ] jpayne@69: jpayne@69: # Extend right-click context menu jpayne@69: rmenu_specs = OutputWindow.rmenu_specs + [ jpayne@69: ("Squeeze", "<>"), jpayne@69: ] jpayne@69: jpayne@69: allow_line_numbers = False jpayne@69: jpayne@69: # New classes jpayne@69: from idlelib.history import History jpayne@69: jpayne@69: def __init__(self, flist=None): jpayne@69: if use_subprocess: jpayne@69: ms = self.menu_specs jpayne@69: if ms[2][0] != "shell": jpayne@69: ms.insert(2, ("shell", "She_ll")) jpayne@69: self.interp = ModifiedInterpreter(self) jpayne@69: if flist is None: jpayne@69: root = Tk() jpayne@69: fixwordbreaks(root) jpayne@69: root.withdraw() jpayne@69: flist = PyShellFileList(root) jpayne@69: jpayne@69: OutputWindow.__init__(self, flist, None, None) jpayne@69: jpayne@69: self.usetabs = True jpayne@69: # indentwidth must be 8 when using tabs. See note in EditorWindow: jpayne@69: self.indentwidth = 8 jpayne@69: jpayne@69: self.sys_ps1 = sys.ps1 if hasattr(sys, 'ps1') else '>>> ' jpayne@69: self.prompt_last_line = self.sys_ps1.split('\n')[-1] jpayne@69: self.prompt = self.sys_ps1 # Changes when debug active jpayne@69: jpayne@69: text = self.text jpayne@69: text.configure(wrap="char") jpayne@69: text.bind("<>", self.enter_callback) jpayne@69: text.bind("<>", self.linefeed_callback) jpayne@69: text.bind("<>", self.cancel_callback) jpayne@69: text.bind("<>", self.eof_callback) jpayne@69: text.bind("<>", self.open_stack_viewer) jpayne@69: text.bind("<>", self.toggle_debugger) jpayne@69: text.bind("<>", self.toggle_jit_stack_viewer) jpayne@69: if use_subprocess: jpayne@69: text.bind("<>", self.view_restart_mark) jpayne@69: text.bind("<>", self.restart_shell) jpayne@69: squeezer = self.Squeezer(self) jpayne@69: text.bind("<>", jpayne@69: squeezer.squeeze_current_text_event) jpayne@69: jpayne@69: self.save_stdout = sys.stdout jpayne@69: self.save_stderr = sys.stderr jpayne@69: self.save_stdin = sys.stdin jpayne@69: from idlelib import iomenu jpayne@69: self.stdin = StdInputFile(self, "stdin", jpayne@69: iomenu.encoding, iomenu.errors) jpayne@69: self.stdout = StdOutputFile(self, "stdout", jpayne@69: iomenu.encoding, iomenu.errors) jpayne@69: self.stderr = StdOutputFile(self, "stderr", jpayne@69: iomenu.encoding, "backslashreplace") jpayne@69: self.console = StdOutputFile(self, "console", jpayne@69: iomenu.encoding, iomenu.errors) jpayne@69: if not use_subprocess: jpayne@69: sys.stdout = self.stdout jpayne@69: sys.stderr = self.stderr jpayne@69: sys.stdin = self.stdin jpayne@69: try: jpayne@69: # page help() text to shell. jpayne@69: import pydoc # import must be done here to capture i/o rebinding. jpayne@69: # XXX KBK 27Dec07 use text viewer someday, but must work w/o subproc jpayne@69: pydoc.pager = pydoc.plainpager jpayne@69: except: jpayne@69: sys.stderr = sys.__stderr__ jpayne@69: raise jpayne@69: # jpayne@69: self.history = self.History(self.text) jpayne@69: # jpayne@69: self.pollinterval = 50 # millisec jpayne@69: jpayne@69: def get_standard_extension_names(self): jpayne@69: return idleConf.GetExtensions(shell_only=True) jpayne@69: jpayne@69: reading = False jpayne@69: executing = False jpayne@69: canceled = False jpayne@69: endoffile = False jpayne@69: closing = False jpayne@69: _stop_readline_flag = False jpayne@69: jpayne@69: def set_warning_stream(self, stream): jpayne@69: global warning_stream jpayne@69: warning_stream = stream jpayne@69: jpayne@69: def get_warning_stream(self): jpayne@69: return warning_stream jpayne@69: jpayne@69: def toggle_debugger(self, event=None): jpayne@69: if self.executing: jpayne@69: tkMessageBox.showerror("Don't debug now", jpayne@69: "You can only toggle the debugger when idle", jpayne@69: parent=self.text) jpayne@69: self.set_debugger_indicator() jpayne@69: return "break" jpayne@69: else: jpayne@69: db = self.interp.getdebugger() jpayne@69: if db: jpayne@69: self.close_debugger() jpayne@69: else: jpayne@69: self.open_debugger() jpayne@69: jpayne@69: def set_debugger_indicator(self): jpayne@69: db = self.interp.getdebugger() jpayne@69: self.setvar("<>", not not db) jpayne@69: jpayne@69: def toggle_jit_stack_viewer(self, event=None): jpayne@69: pass # All we need is the variable jpayne@69: jpayne@69: def close_debugger(self): jpayne@69: db = self.interp.getdebugger() jpayne@69: if db: jpayne@69: self.interp.setdebugger(None) jpayne@69: db.close() jpayne@69: if self.interp.rpcclt: jpayne@69: debugger_r.close_remote_debugger(self.interp.rpcclt) jpayne@69: self.resetoutput() jpayne@69: self.console.write("[DEBUG OFF]\n") jpayne@69: self.prompt = self.sys_ps1 jpayne@69: self.showprompt() jpayne@69: self.set_debugger_indicator() jpayne@69: jpayne@69: def open_debugger(self): jpayne@69: if self.interp.rpcclt: jpayne@69: dbg_gui = debugger_r.start_remote_debugger(self.interp.rpcclt, jpayne@69: self) jpayne@69: else: jpayne@69: dbg_gui = debugger.Debugger(self) jpayne@69: self.interp.setdebugger(dbg_gui) jpayne@69: dbg_gui.load_breakpoints() jpayne@69: self.prompt = "[DEBUG ON]\n" + self.sys_ps1 jpayne@69: self.showprompt() jpayne@69: self.set_debugger_indicator() jpayne@69: jpayne@69: def beginexecuting(self): jpayne@69: "Helper for ModifiedInterpreter" jpayne@69: self.resetoutput() jpayne@69: self.executing = 1 jpayne@69: jpayne@69: def endexecuting(self): jpayne@69: "Helper for ModifiedInterpreter" jpayne@69: self.executing = 0 jpayne@69: self.canceled = 0 jpayne@69: self.showprompt() jpayne@69: jpayne@69: def close(self): jpayne@69: "Extend EditorWindow.close()" jpayne@69: if self.executing: jpayne@69: response = tkMessageBox.askokcancel( jpayne@69: "Kill?", jpayne@69: "Your program is still running!\n Do you want to kill it?", jpayne@69: default="ok", jpayne@69: parent=self.text) jpayne@69: if response is False: jpayne@69: return "cancel" jpayne@69: self.stop_readline() jpayne@69: self.canceled = True jpayne@69: self.closing = True jpayne@69: return EditorWindow.close(self) jpayne@69: jpayne@69: def _close(self): jpayne@69: "Extend EditorWindow._close(), shut down debugger and execution server" jpayne@69: self.close_debugger() jpayne@69: if use_subprocess: jpayne@69: self.interp.kill_subprocess() jpayne@69: # Restore std streams jpayne@69: sys.stdout = self.save_stdout jpayne@69: sys.stderr = self.save_stderr jpayne@69: sys.stdin = self.save_stdin jpayne@69: # Break cycles jpayne@69: self.interp = None jpayne@69: self.console = None jpayne@69: self.flist.pyshell = None jpayne@69: self.history = None jpayne@69: EditorWindow._close(self) jpayne@69: jpayne@69: def ispythonsource(self, filename): jpayne@69: "Override EditorWindow method: never remove the colorizer" jpayne@69: return True jpayne@69: jpayne@69: def short_title(self): jpayne@69: return self.shell_title jpayne@69: jpayne@69: COPYRIGHT = \ jpayne@69: 'Type "help", "copyright", "credits" or "license()" for more information.' jpayne@69: jpayne@69: def begin(self): jpayne@69: self.text.mark_set("iomark", "insert") jpayne@69: self.resetoutput() jpayne@69: if use_subprocess: jpayne@69: nosub = '' jpayne@69: client = self.interp.start_subprocess() jpayne@69: if not client: jpayne@69: self.close() jpayne@69: return False jpayne@69: else: jpayne@69: nosub = ("==== No Subprocess ====\n\n" + jpayne@69: "WARNING: Running IDLE without a Subprocess is deprecated\n" + jpayne@69: "and will be removed in a later version. See Help/IDLE Help\n" + jpayne@69: "for details.\n\n") jpayne@69: sys.displayhook = rpc.displayhook jpayne@69: jpayne@69: self.write("Python %s on %s\n%s\n%s" % jpayne@69: (sys.version, sys.platform, self.COPYRIGHT, nosub)) jpayne@69: self.text.focus_force() jpayne@69: self.showprompt() jpayne@69: import tkinter jpayne@69: tkinter._default_root = None # 03Jan04 KBK What's this? jpayne@69: return True jpayne@69: jpayne@69: def stop_readline(self): jpayne@69: if not self.reading: # no nested mainloop to exit. jpayne@69: return jpayne@69: self._stop_readline_flag = True jpayne@69: self.top.quit() jpayne@69: jpayne@69: def readline(self): jpayne@69: save = self.reading jpayne@69: try: jpayne@69: self.reading = 1 jpayne@69: self.top.mainloop() # nested mainloop() jpayne@69: finally: jpayne@69: self.reading = save jpayne@69: if self._stop_readline_flag: jpayne@69: self._stop_readline_flag = False jpayne@69: return "" jpayne@69: line = self.text.get("iomark", "end-1c") jpayne@69: if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C jpayne@69: line = "\n" jpayne@69: self.resetoutput() jpayne@69: if self.canceled: jpayne@69: self.canceled = 0 jpayne@69: if not use_subprocess: jpayne@69: raise KeyboardInterrupt jpayne@69: if self.endoffile: jpayne@69: self.endoffile = 0 jpayne@69: line = "" jpayne@69: return line jpayne@69: jpayne@69: def isatty(self): jpayne@69: return True jpayne@69: jpayne@69: def cancel_callback(self, event=None): jpayne@69: try: jpayne@69: if self.text.compare("sel.first", "!=", "sel.last"): jpayne@69: return # Active selection -- always use default binding jpayne@69: except: jpayne@69: pass jpayne@69: if not (self.executing or self.reading): jpayne@69: self.resetoutput() jpayne@69: self.interp.write("KeyboardInterrupt\n") jpayne@69: self.showprompt() jpayne@69: return "break" jpayne@69: self.endoffile = 0 jpayne@69: self.canceled = 1 jpayne@69: if (self.executing and self.interp.rpcclt): jpayne@69: if self.interp.getdebugger(): jpayne@69: self.interp.restart_subprocess() jpayne@69: else: jpayne@69: self.interp.interrupt_subprocess() jpayne@69: if self.reading: jpayne@69: self.top.quit() # exit the nested mainloop() in readline() jpayne@69: return "break" jpayne@69: jpayne@69: def eof_callback(self, event): jpayne@69: if self.executing and not self.reading: jpayne@69: return # Let the default binding (delete next char) take over jpayne@69: if not (self.text.compare("iomark", "==", "insert") and jpayne@69: self.text.compare("insert", "==", "end-1c")): jpayne@69: return # Let the default binding (delete next char) take over jpayne@69: if not self.executing: jpayne@69: self.resetoutput() jpayne@69: self.close() jpayne@69: else: jpayne@69: self.canceled = 0 jpayne@69: self.endoffile = 1 jpayne@69: self.top.quit() jpayne@69: return "break" jpayne@69: jpayne@69: def linefeed_callback(self, event): jpayne@69: # Insert a linefeed without entering anything (still autoindented) jpayne@69: if self.reading: jpayne@69: self.text.insert("insert", "\n") jpayne@69: self.text.see("insert") jpayne@69: else: jpayne@69: self.newline_and_indent_event(event) jpayne@69: return "break" jpayne@69: jpayne@69: def enter_callback(self, event): jpayne@69: if self.executing and not self.reading: jpayne@69: return # Let the default binding (insert '\n') take over jpayne@69: # If some text is selected, recall the selection jpayne@69: # (but only if this before the I/O mark) jpayne@69: try: jpayne@69: sel = self.text.get("sel.first", "sel.last") jpayne@69: if sel: jpayne@69: if self.text.compare("sel.last", "<=", "iomark"): jpayne@69: self.recall(sel, event) jpayne@69: return "break" jpayne@69: except: jpayne@69: pass jpayne@69: # If we're strictly before the line containing iomark, recall jpayne@69: # the current line, less a leading prompt, less leading or jpayne@69: # trailing whitespace jpayne@69: if self.text.compare("insert", "<", "iomark linestart"): jpayne@69: # Check if there's a relevant stdin range -- if so, use it jpayne@69: prev = self.text.tag_prevrange("stdin", "insert") jpayne@69: if prev and self.text.compare("insert", "<", prev[1]): jpayne@69: self.recall(self.text.get(prev[0], prev[1]), event) jpayne@69: return "break" jpayne@69: next = self.text.tag_nextrange("stdin", "insert") jpayne@69: if next and self.text.compare("insert lineend", ">=", next[0]): jpayne@69: self.recall(self.text.get(next[0], next[1]), event) jpayne@69: return "break" jpayne@69: # No stdin mark -- just get the current line, less any prompt jpayne@69: indices = self.text.tag_nextrange("console", "insert linestart") jpayne@69: if indices and \ jpayne@69: self.text.compare(indices[0], "<=", "insert linestart"): jpayne@69: self.recall(self.text.get(indices[1], "insert lineend"), event) jpayne@69: else: jpayne@69: self.recall(self.text.get("insert linestart", "insert lineend"), event) jpayne@69: return "break" jpayne@69: # If we're between the beginning of the line and the iomark, i.e. jpayne@69: # in the prompt area, move to the end of the prompt jpayne@69: if self.text.compare("insert", "<", "iomark"): jpayne@69: self.text.mark_set("insert", "iomark") jpayne@69: # If we're in the current input and there's only whitespace jpayne@69: # beyond the cursor, erase that whitespace first jpayne@69: s = self.text.get("insert", "end-1c") jpayne@69: if s and not s.strip(): jpayne@69: self.text.delete("insert", "end-1c") jpayne@69: # If we're in the current input before its last line, jpayne@69: # insert a newline right at the insert point jpayne@69: if self.text.compare("insert", "<", "end-1c linestart"): jpayne@69: self.newline_and_indent_event(event) jpayne@69: return "break" jpayne@69: # We're in the last line; append a newline and submit it jpayne@69: self.text.mark_set("insert", "end-1c") jpayne@69: if self.reading: jpayne@69: self.text.insert("insert", "\n") jpayne@69: self.text.see("insert") jpayne@69: else: jpayne@69: self.newline_and_indent_event(event) jpayne@69: self.text.tag_add("stdin", "iomark", "end-1c") jpayne@69: self.text.update_idletasks() jpayne@69: if self.reading: jpayne@69: self.top.quit() # Break out of recursive mainloop() jpayne@69: else: jpayne@69: self.runit() jpayne@69: return "break" jpayne@69: jpayne@69: def recall(self, s, event): jpayne@69: # remove leading and trailing empty or whitespace lines jpayne@69: s = re.sub(r'^\s*\n', '' , s) jpayne@69: s = re.sub(r'\n\s*$', '', s) jpayne@69: lines = s.split('\n') jpayne@69: self.text.undo_block_start() jpayne@69: try: jpayne@69: self.text.tag_remove("sel", "1.0", "end") jpayne@69: self.text.mark_set("insert", "end-1c") jpayne@69: prefix = self.text.get("insert linestart", "insert") jpayne@69: if prefix.rstrip().endswith(':'): jpayne@69: self.newline_and_indent_event(event) jpayne@69: prefix = self.text.get("insert linestart", "insert") jpayne@69: self.text.insert("insert", lines[0].strip()) jpayne@69: if len(lines) > 1: jpayne@69: orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0) jpayne@69: new_base_indent = re.search(r'^([ \t]*)', prefix).group(0) jpayne@69: for line in lines[1:]: jpayne@69: if line.startswith(orig_base_indent): jpayne@69: # replace orig base indentation with new indentation jpayne@69: line = new_base_indent + line[len(orig_base_indent):] jpayne@69: self.text.insert('insert', '\n'+line.rstrip()) jpayne@69: finally: jpayne@69: self.text.see("insert") jpayne@69: self.text.undo_block_stop() jpayne@69: jpayne@69: def runit(self): jpayne@69: line = self.text.get("iomark", "end-1c") jpayne@69: # Strip off last newline and surrounding whitespace. jpayne@69: # (To allow you to hit return twice to end a statement.) jpayne@69: i = len(line) jpayne@69: while i > 0 and line[i-1] in " \t": jpayne@69: i = i-1 jpayne@69: if i > 0 and line[i-1] == "\n": jpayne@69: i = i-1 jpayne@69: while i > 0 and line[i-1] in " \t": jpayne@69: i = i-1 jpayne@69: line = line[:i] jpayne@69: self.interp.runsource(line) jpayne@69: jpayne@69: def open_stack_viewer(self, event=None): jpayne@69: if self.interp.rpcclt: jpayne@69: return self.interp.remote_stack_viewer() jpayne@69: try: jpayne@69: sys.last_traceback jpayne@69: except: jpayne@69: tkMessageBox.showerror("No stack trace", jpayne@69: "There is no stack trace yet.\n" jpayne@69: "(sys.last_traceback is not defined)", jpayne@69: parent=self.text) jpayne@69: return jpayne@69: from idlelib.stackviewer import StackBrowser jpayne@69: StackBrowser(self.root, self.flist) jpayne@69: jpayne@69: def view_restart_mark(self, event=None): jpayne@69: self.text.see("iomark") jpayne@69: self.text.see("restart") jpayne@69: jpayne@69: def restart_shell(self, event=None): jpayne@69: "Callback for Run/Restart Shell Cntl-F6" jpayne@69: self.interp.restart_subprocess(with_cwd=True) jpayne@69: jpayne@69: def showprompt(self): jpayne@69: self.resetoutput() jpayne@69: self.console.write(self.prompt) jpayne@69: self.text.mark_set("insert", "end-1c") jpayne@69: self.set_line_and_column() jpayne@69: self.io.reset_undo() jpayne@69: jpayne@69: def show_warning(self, msg): jpayne@69: width = self.interp.tkconsole.width jpayne@69: wrapper = TextWrapper(width=width, tabsize=8, expand_tabs=True) jpayne@69: wrapped_msg = '\n'.join(wrapper.wrap(msg)) jpayne@69: if not wrapped_msg.endswith('\n'): jpayne@69: wrapped_msg += '\n' jpayne@69: self.per.bottom.insert("iomark linestart", wrapped_msg, "stderr") jpayne@69: jpayne@69: def resetoutput(self): jpayne@69: source = self.text.get("iomark", "end-1c") jpayne@69: if self.history: jpayne@69: self.history.store(source) jpayne@69: if self.text.get("end-2c") != "\n": jpayne@69: self.text.insert("end-1c", "\n") jpayne@69: self.text.mark_set("iomark", "end-1c") jpayne@69: self.set_line_and_column() jpayne@69: jpayne@69: def write(self, s, tags=()): jpayne@69: try: jpayne@69: self.text.mark_gravity("iomark", "right") jpayne@69: count = OutputWindow.write(self, s, tags, "iomark") jpayne@69: self.text.mark_gravity("iomark", "left") jpayne@69: except: jpayne@69: raise ###pass # ### 11Aug07 KBK if we are expecting exceptions jpayne@69: # let's find out what they are and be specific. jpayne@69: if self.canceled: jpayne@69: self.canceled = 0 jpayne@69: if not use_subprocess: jpayne@69: raise KeyboardInterrupt jpayne@69: return count jpayne@69: jpayne@69: def rmenu_check_cut(self): jpayne@69: try: jpayne@69: if self.text.compare('sel.first', '<', 'iomark'): jpayne@69: return 'disabled' jpayne@69: except TclError: # no selection, so the index 'sel.first' doesn't exist jpayne@69: return 'disabled' jpayne@69: return super().rmenu_check_cut() jpayne@69: jpayne@69: def rmenu_check_paste(self): jpayne@69: if self.text.compare('insert','<','iomark'): jpayne@69: return 'disabled' jpayne@69: return super().rmenu_check_paste() jpayne@69: jpayne@69: jpayne@69: def fix_x11_paste(root): jpayne@69: "Make paste replace selection on x11. See issue #5124." jpayne@69: if root._windowingsystem == 'x11': jpayne@69: for cls in 'Text', 'Entry', 'Spinbox': jpayne@69: root.bind_class( jpayne@69: cls, jpayne@69: '<>', jpayne@69: 'catch {%W delete sel.first sel.last}\n' + jpayne@69: root.bind_class(cls, '<>')) jpayne@69: jpayne@69: jpayne@69: usage_msg = """\ jpayne@69: jpayne@69: USAGE: idle [-deins] [-t title] [file]* jpayne@69: idle [-dns] [-t title] (-c cmd | -r file) [arg]* jpayne@69: idle [-dns] [-t title] - [arg]* jpayne@69: jpayne@69: -h print this help message and exit jpayne@69: -n run IDLE without a subprocess (DEPRECATED, jpayne@69: see Help/IDLE Help for details) jpayne@69: jpayne@69: The following options will override the IDLE 'settings' configuration: jpayne@69: jpayne@69: -e open an edit window jpayne@69: -i open a shell window jpayne@69: jpayne@69: The following options imply -i and will open a shell: jpayne@69: jpayne@69: -c cmd run the command in a shell, or jpayne@69: -r file run script from file jpayne@69: jpayne@69: -d enable the debugger jpayne@69: -s run $IDLESTARTUP or $PYTHONSTARTUP before anything else jpayne@69: -t title set title of shell window jpayne@69: jpayne@69: A default edit window will be bypassed when -c, -r, or - are used. jpayne@69: jpayne@69: [arg]* are passed to the command (-c) or script (-r) in sys.argv[1:]. jpayne@69: jpayne@69: Examples: jpayne@69: jpayne@69: idle jpayne@69: Open an edit window or shell depending on IDLE's configuration. jpayne@69: jpayne@69: idle foo.py foobar.py jpayne@69: Edit the files, also open a shell if configured to start with shell. jpayne@69: jpayne@69: idle -est "Baz" foo.py jpayne@69: Run $IDLESTARTUP or $PYTHONSTARTUP, edit foo.py, and open a shell jpayne@69: window with the title "Baz". jpayne@69: jpayne@69: idle -c "import sys; print(sys.argv)" "foo" jpayne@69: Open a shell window and run the command, passing "-c" in sys.argv[0] jpayne@69: and "foo" in sys.argv[1]. jpayne@69: jpayne@69: idle -d -s -r foo.py "Hello World" jpayne@69: Open a shell window, run a startup script, enable the debugger, and jpayne@69: run foo.py, passing "foo.py" in sys.argv[0] and "Hello World" in jpayne@69: sys.argv[1]. jpayne@69: jpayne@69: echo "import sys; print(sys.argv)" | idle - "foobar" jpayne@69: Open a shell window, run the script piped in, passing '' in sys.argv[0] jpayne@69: and "foobar" in sys.argv[1]. jpayne@69: """ jpayne@69: jpayne@69: def main(): jpayne@69: import getopt jpayne@69: from platform import system jpayne@69: from idlelib import testing # bool value jpayne@69: from idlelib import macosx jpayne@69: jpayne@69: global flist, root, use_subprocess jpayne@69: jpayne@69: capture_warnings(True) jpayne@69: use_subprocess = True jpayne@69: enable_shell = False jpayne@69: enable_edit = False jpayne@69: debug = False jpayne@69: cmd = None jpayne@69: script = None jpayne@69: startup = False jpayne@69: try: jpayne@69: opts, args = getopt.getopt(sys.argv[1:], "c:deihnr:st:") jpayne@69: except getopt.error as msg: jpayne@69: print("Error: %s\n%s" % (msg, usage_msg), file=sys.stderr) jpayne@69: sys.exit(2) jpayne@69: for o, a in opts: jpayne@69: if o == '-c': jpayne@69: cmd = a jpayne@69: enable_shell = True jpayne@69: if o == '-d': jpayne@69: debug = True jpayne@69: enable_shell = True jpayne@69: if o == '-e': jpayne@69: enable_edit = True jpayne@69: if o == '-h': jpayne@69: sys.stdout.write(usage_msg) jpayne@69: sys.exit() jpayne@69: if o == '-i': jpayne@69: enable_shell = True jpayne@69: if o == '-n': jpayne@69: print(" Warning: running IDLE without a subprocess is deprecated.", jpayne@69: file=sys.stderr) jpayne@69: use_subprocess = False jpayne@69: if o == '-r': jpayne@69: script = a jpayne@69: if os.path.isfile(script): jpayne@69: pass jpayne@69: else: jpayne@69: print("No script file: ", script) jpayne@69: sys.exit() jpayne@69: enable_shell = True jpayne@69: if o == '-s': jpayne@69: startup = True jpayne@69: enable_shell = True jpayne@69: if o == '-t': jpayne@69: PyShell.shell_title = a jpayne@69: enable_shell = True jpayne@69: if args and args[0] == '-': jpayne@69: cmd = sys.stdin.read() jpayne@69: enable_shell = True jpayne@69: # process sys.argv and sys.path: jpayne@69: for i in range(len(sys.path)): jpayne@69: sys.path[i] = os.path.abspath(sys.path[i]) jpayne@69: if args and args[0] == '-': jpayne@69: sys.argv = [''] + args[1:] jpayne@69: elif cmd: jpayne@69: sys.argv = ['-c'] + args jpayne@69: elif script: jpayne@69: sys.argv = [script] + args jpayne@69: elif args: jpayne@69: enable_edit = True jpayne@69: pathx = [] jpayne@69: for filename in args: jpayne@69: pathx.append(os.path.dirname(filename)) jpayne@69: for dir in pathx: jpayne@69: dir = os.path.abspath(dir) jpayne@69: if not dir in sys.path: jpayne@69: sys.path.insert(0, dir) jpayne@69: else: jpayne@69: dir = os.getcwd() jpayne@69: if dir not in sys.path: jpayne@69: sys.path.insert(0, dir) jpayne@69: # check the IDLE settings configuration (but command line overrides) jpayne@69: edit_start = idleConf.GetOption('main', 'General', jpayne@69: 'editor-on-startup', type='bool') jpayne@69: enable_edit = enable_edit or edit_start jpayne@69: enable_shell = enable_shell or not enable_edit jpayne@69: jpayne@69: # Setup root. Don't break user code run in IDLE process. jpayne@69: # Don't change environment when testing. jpayne@69: if use_subprocess and not testing: jpayne@69: NoDefaultRoot() jpayne@69: root = Tk(className="Idle") jpayne@69: root.withdraw() jpayne@69: from idlelib.run import fix_scaling jpayne@69: fix_scaling(root) jpayne@69: jpayne@69: # set application icon jpayne@69: icondir = os.path.join(os.path.dirname(__file__), 'Icons') jpayne@69: if system() == 'Windows': jpayne@69: iconfile = os.path.join(icondir, 'idle.ico') jpayne@69: root.wm_iconbitmap(default=iconfile) jpayne@69: elif not macosx.isAquaTk(): jpayne@69: ext = '.png' if TkVersion >= 8.6 else '.gif' jpayne@69: iconfiles = [os.path.join(icondir, 'idle_%d%s' % (size, ext)) jpayne@69: for size in (16, 32, 48)] jpayne@69: icons = [PhotoImage(master=root, file=iconfile) jpayne@69: for iconfile in iconfiles] jpayne@69: root.wm_iconphoto(True, *icons) jpayne@69: jpayne@69: # start editor and/or shell windows: jpayne@69: fixwordbreaks(root) jpayne@69: fix_x11_paste(root) jpayne@69: flist = PyShellFileList(root) jpayne@69: macosx.setupApp(root, flist) jpayne@69: jpayne@69: if enable_edit: jpayne@69: if not (cmd or script): jpayne@69: for filename in args[:]: jpayne@69: if flist.open(filename) is None: jpayne@69: # filename is a directory actually, disconsider it jpayne@69: args.remove(filename) jpayne@69: if not args: jpayne@69: flist.new() jpayne@69: jpayne@69: if enable_shell: jpayne@69: shell = flist.open_shell() jpayne@69: if not shell: jpayne@69: return # couldn't open shell jpayne@69: if macosx.isAquaTk() and flist.dict: jpayne@69: # On OSX: when the user has double-clicked on a file that causes jpayne@69: # IDLE to be launched the shell window will open just in front of jpayne@69: # the file she wants to see. Lower the interpreter window when jpayne@69: # there are open files. jpayne@69: shell.top.lower() jpayne@69: else: jpayne@69: shell = flist.pyshell jpayne@69: jpayne@69: # Handle remaining options. If any of these are set, enable_shell jpayne@69: # was set also, so shell must be true to reach here. jpayne@69: if debug: jpayne@69: shell.open_debugger() jpayne@69: if startup: jpayne@69: filename = os.environ.get("IDLESTARTUP") or \ jpayne@69: os.environ.get("PYTHONSTARTUP") jpayne@69: if filename and os.path.isfile(filename): jpayne@69: shell.interp.execfile(filename) jpayne@69: if cmd or script: jpayne@69: shell.interp.runcommand("""if 1: jpayne@69: import sys as _sys jpayne@69: _sys.argv = %r jpayne@69: del _sys jpayne@69: \n""" % (sys.argv,)) jpayne@69: if cmd: jpayne@69: shell.interp.execsource(cmd) jpayne@69: elif script: jpayne@69: shell.interp.prepend_syspath(script) jpayne@69: shell.interp.execfile(script) jpayne@69: elif shell: jpayne@69: # If there is a shell window and no cmd or script in progress, jpayne@69: # check for problematic issues and print warning message(s) in jpayne@69: # the IDLE shell window; this is less intrusive than always jpayne@69: # opening a separate window. jpayne@69: jpayne@69: # Warn if using a problematic OS X Tk version. jpayne@69: tkversionwarning = macosx.tkVersionWarning(root) jpayne@69: if tkversionwarning: jpayne@69: shell.show_warning(tkversionwarning) jpayne@69: jpayne@69: # Warn if the "Prefer tabs when opening documents" system jpayne@69: # preference is set to "Always". jpayne@69: prefer_tabs_preference_warning = macosx.preferTabsPreferenceWarning() jpayne@69: if prefer_tabs_preference_warning: jpayne@69: shell.show_warning(prefer_tabs_preference_warning) jpayne@69: jpayne@69: while flist.inversedict: # keep IDLE running while files are open. jpayne@69: root.mainloop() jpayne@69: root.destroy() jpayne@69: capture_warnings(False) jpayne@69: jpayne@69: if __name__ == "__main__": jpayne@69: main() jpayne@69: jpayne@69: capture_warnings(False) # Make sure turned off; see issue 18081