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