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