jpayne@68: """Editor window that can serve as an output file. jpayne@68: """ jpayne@68: jpayne@68: import re jpayne@68: jpayne@68: from tkinter import messagebox jpayne@68: jpayne@68: from idlelib.editor import EditorWindow jpayne@68: from idlelib import iomenu jpayne@68: jpayne@68: jpayne@68: file_line_pats = [ jpayne@68: # order of patterns matters jpayne@68: r'file "([^"]*)", line (\d+)', jpayne@68: r'([^\s]+)\((\d+)\)', jpayne@68: r'^(\s*\S.*?):\s*(\d+):', # Win filename, maybe starting with spaces jpayne@68: r'([^\s]+):\s*(\d+):', # filename or path, ltrim jpayne@68: r'^\s*(\S.*?):\s*(\d+):', # Win abs path with embedded spaces, ltrim jpayne@68: ] jpayne@68: jpayne@68: file_line_progs = None jpayne@68: jpayne@68: jpayne@68: def compile_progs(): jpayne@68: "Compile the patterns for matching to file name and line number." jpayne@68: global file_line_progs jpayne@68: file_line_progs = [re.compile(pat, re.IGNORECASE) jpayne@68: for pat in file_line_pats] jpayne@68: jpayne@68: jpayne@68: def file_line_helper(line): jpayne@68: """Extract file name and line number from line of text. jpayne@68: jpayne@68: Check if line of text contains one of the file/line patterns. jpayne@68: If it does and if the file and line are valid, return jpayne@68: a tuple of the file name and line number. If it doesn't match jpayne@68: or if the file or line is invalid, return None. jpayne@68: """ jpayne@68: if not file_line_progs: jpayne@68: compile_progs() jpayne@68: for prog in file_line_progs: jpayne@68: match = prog.search(line) jpayne@68: if match: jpayne@68: filename, lineno = match.group(1, 2) jpayne@68: try: jpayne@68: f = open(filename, "r") jpayne@68: f.close() jpayne@68: break jpayne@68: except OSError: jpayne@68: continue jpayne@68: else: jpayne@68: return None jpayne@68: try: jpayne@68: return filename, int(lineno) jpayne@68: except TypeError: jpayne@68: return None jpayne@68: jpayne@68: jpayne@68: class OutputWindow(EditorWindow): jpayne@68: """An editor window that can serve as an output file. jpayne@68: jpayne@68: Also the future base class for the Python shell window. jpayne@68: This class has no input facilities. jpayne@68: jpayne@68: Adds binding to open a file at a line to the text widget. jpayne@68: """ jpayne@68: jpayne@68: # Our own right-button menu 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: ("Go to file/line", "<>", None), jpayne@68: ] jpayne@68: jpayne@68: allow_code_context = False jpayne@68: jpayne@68: def __init__(self, *args): jpayne@68: EditorWindow.__init__(self, *args) jpayne@68: self.text.bind("<>", self.goto_file_line) jpayne@68: jpayne@68: # Customize EditorWindow jpayne@68: def ispythonsource(self, filename): jpayne@68: "Python source is only part of output: do not colorize." jpayne@68: return False jpayne@68: jpayne@68: def short_title(self): jpayne@68: "Customize EditorWindow title." jpayne@68: return "Output" jpayne@68: jpayne@68: def maybesave(self): jpayne@68: "Customize EditorWindow to not display save file messagebox." jpayne@68: return 'yes' if self.get_saved() else 'no' jpayne@68: jpayne@68: # Act as output file jpayne@68: def write(self, s, tags=(), mark="insert"): jpayne@68: """Write text to text widget. jpayne@68: jpayne@68: The text is inserted at the given index with the provided jpayne@68: tags. The text widget is then scrolled to make it visible jpayne@68: and updated to display it, giving the effect of seeing each jpayne@68: line as it is added. jpayne@68: jpayne@68: Args: jpayne@68: s: Text to insert into text widget. jpayne@68: tags: Tuple of tag strings to apply on the insert. jpayne@68: mark: Index for the insert. jpayne@68: jpayne@68: Return: jpayne@68: Length of text inserted. jpayne@68: """ jpayne@68: if isinstance(s, bytes): jpayne@68: s = s.decode(iomenu.encoding, "replace") jpayne@68: self.text.insert(mark, s, tags) jpayne@68: self.text.see(mark) jpayne@68: self.text.update() jpayne@68: return len(s) jpayne@68: jpayne@68: def writelines(self, lines): jpayne@68: "Write each item in lines iterable." jpayne@68: for line in lines: jpayne@68: self.write(line) jpayne@68: jpayne@68: def flush(self): jpayne@68: "No flushing needed as write() directly writes to widget." jpayne@68: pass jpayne@68: jpayne@68: def showerror(self, *args, **kwargs): jpayne@68: messagebox.showerror(*args, **kwargs) jpayne@68: jpayne@68: def goto_file_line(self, event=None): jpayne@68: """Handle request to open file/line. jpayne@68: jpayne@68: If the selected or previous line in the output window jpayne@68: contains a file name and line number, then open that file jpayne@68: name in a new window and position on the line number. jpayne@68: jpayne@68: Otherwise, display an error messagebox. jpayne@68: """ jpayne@68: line = self.text.get("insert linestart", "insert lineend") jpayne@68: result = file_line_helper(line) jpayne@68: if not result: jpayne@68: # Try the previous line. This is handy e.g. in tracebacks, jpayne@68: # where you tend to right-click on the displayed source line jpayne@68: line = self.text.get("insert -1line linestart", jpayne@68: "insert -1line lineend") jpayne@68: result = file_line_helper(line) jpayne@68: if not result: jpayne@68: self.showerror( jpayne@68: "No special line", jpayne@68: "The line you point at doesn't look like " jpayne@68: "a valid file name followed by a line number.", jpayne@68: parent=self.text) jpayne@68: return jpayne@68: filename, lineno = result jpayne@68: self.flist.gotofileline(filename, lineno) jpayne@68: jpayne@68: jpayne@68: # These classes are currently not used but might come in handy jpayne@68: class OnDemandOutputWindow: jpayne@68: jpayne@68: tagdefs = { jpayne@68: # XXX Should use IdlePrefs.ColorPrefs jpayne@68: "stdout": {"foreground": "blue"}, jpayne@68: "stderr": {"foreground": "#007700"}, jpayne@68: } jpayne@68: jpayne@68: def __init__(self, flist): jpayne@68: self.flist = flist jpayne@68: self.owin = None jpayne@68: jpayne@68: def write(self, s, tags, mark): jpayne@68: if not self.owin: jpayne@68: self.setup() jpayne@68: self.owin.write(s, tags, mark) jpayne@68: jpayne@68: def setup(self): jpayne@68: self.owin = owin = OutputWindow(self.flist) jpayne@68: text = owin.text jpayne@68: for tag, cnf in self.tagdefs.items(): jpayne@68: if cnf: jpayne@68: text.tag_configure(tag, **cnf) jpayne@68: text.tag_raise('sel') jpayne@68: self.write = self.owin.write jpayne@68: jpayne@68: if __name__ == '__main__': jpayne@68: from unittest import main jpayne@68: main('idlelib.idle_test.test_outwin', verbosity=2, exit=False)