17db96d56Sopenharmony_ci"""Editor window that can serve as an output file.
27db96d56Sopenharmony_ci"""
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciimport re
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_cifrom tkinter import messagebox
77db96d56Sopenharmony_ci
87db96d56Sopenharmony_cifrom idlelib.editor import EditorWindow
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_cifile_line_pats = [
127db96d56Sopenharmony_ci    # order of patterns matters
137db96d56Sopenharmony_ci    r'file "([^"]*)", line (\d+)',
147db96d56Sopenharmony_ci    r'([^\s]+)\((\d+)\)',
157db96d56Sopenharmony_ci    r'^(\s*\S.*?):\s*(\d+):',  # Win filename, maybe starting with spaces
167db96d56Sopenharmony_ci    r'([^\s]+):\s*(\d+):',     # filename or path, ltrim
177db96d56Sopenharmony_ci    r'^\s*(\S.*?):\s*(\d+):',  # Win abs path with embedded spaces, ltrim
187db96d56Sopenharmony_ci]
197db96d56Sopenharmony_ci
207db96d56Sopenharmony_cifile_line_progs = None
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_cidef compile_progs():
247db96d56Sopenharmony_ci    "Compile the patterns for matching to file name and line number."
257db96d56Sopenharmony_ci    global file_line_progs
267db96d56Sopenharmony_ci    file_line_progs = [re.compile(pat, re.IGNORECASE)
277db96d56Sopenharmony_ci                       for pat in file_line_pats]
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_cidef file_line_helper(line):
317db96d56Sopenharmony_ci    """Extract file name and line number from line of text.
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci    Check if line of text contains one of the file/line patterns.
347db96d56Sopenharmony_ci    If it does and if the file and line are valid, return
357db96d56Sopenharmony_ci    a tuple of the file name and line number.  If it doesn't match
367db96d56Sopenharmony_ci    or if the file or line is invalid, return None.
377db96d56Sopenharmony_ci    """
387db96d56Sopenharmony_ci    if not file_line_progs:
397db96d56Sopenharmony_ci        compile_progs()
407db96d56Sopenharmony_ci    for prog in file_line_progs:
417db96d56Sopenharmony_ci        match = prog.search(line)
427db96d56Sopenharmony_ci        if match:
437db96d56Sopenharmony_ci            filename, lineno = match.group(1, 2)
447db96d56Sopenharmony_ci            try:
457db96d56Sopenharmony_ci                f = open(filename)
467db96d56Sopenharmony_ci                f.close()
477db96d56Sopenharmony_ci                break
487db96d56Sopenharmony_ci            except OSError:
497db96d56Sopenharmony_ci                continue
507db96d56Sopenharmony_ci    else:
517db96d56Sopenharmony_ci        return None
527db96d56Sopenharmony_ci    try:
537db96d56Sopenharmony_ci        return filename, int(lineno)
547db96d56Sopenharmony_ci    except TypeError:
557db96d56Sopenharmony_ci        return None
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ci
587db96d56Sopenharmony_ciclass OutputWindow(EditorWindow):
597db96d56Sopenharmony_ci    """An editor window that can serve as an output file.
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci    Also the future base class for the Python shell window.
627db96d56Sopenharmony_ci    This class has no input facilities.
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci    Adds binding to open a file at a line to the text widget.
657db96d56Sopenharmony_ci    """
667db96d56Sopenharmony_ci
677db96d56Sopenharmony_ci    # Our own right-button menu
687db96d56Sopenharmony_ci    rmenu_specs = [
697db96d56Sopenharmony_ci        ("Cut", "<<cut>>", "rmenu_check_cut"),
707db96d56Sopenharmony_ci        ("Copy", "<<copy>>", "rmenu_check_copy"),
717db96d56Sopenharmony_ci        ("Paste", "<<paste>>", "rmenu_check_paste"),
727db96d56Sopenharmony_ci        (None, None, None),
737db96d56Sopenharmony_ci        ("Go to file/line", "<<goto-file-line>>", None),
747db96d56Sopenharmony_ci    ]
757db96d56Sopenharmony_ci
767db96d56Sopenharmony_ci    allow_code_context = False
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ci    def __init__(self, *args):
797db96d56Sopenharmony_ci        EditorWindow.__init__(self, *args)
807db96d56Sopenharmony_ci        self.text.bind("<<goto-file-line>>", self.goto_file_line)
817db96d56Sopenharmony_ci
827db96d56Sopenharmony_ci    # Customize EditorWindow
837db96d56Sopenharmony_ci    def ispythonsource(self, filename):
847db96d56Sopenharmony_ci        "Python source is only part of output: do not colorize."
857db96d56Sopenharmony_ci        return False
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci    def short_title(self):
887db96d56Sopenharmony_ci        "Customize EditorWindow title."
897db96d56Sopenharmony_ci        return "Output"
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_ci    def maybesave(self):
927db96d56Sopenharmony_ci        "Customize EditorWindow to not display save file messagebox."
937db96d56Sopenharmony_ci        return 'yes' if self.get_saved() else 'no'
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_ci    # Act as output file
967db96d56Sopenharmony_ci    def write(self, s, tags=(), mark="insert"):
977db96d56Sopenharmony_ci        """Write text to text widget.
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ci        The text is inserted at the given index with the provided
1007db96d56Sopenharmony_ci        tags.  The text widget is then scrolled to make it visible
1017db96d56Sopenharmony_ci        and updated to display it, giving the effect of seeing each
1027db96d56Sopenharmony_ci        line as it is added.
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci        Args:
1057db96d56Sopenharmony_ci            s: Text to insert into text widget.
1067db96d56Sopenharmony_ci            tags: Tuple of tag strings to apply on the insert.
1077db96d56Sopenharmony_ci            mark: Index for the insert.
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci        Return:
1107db96d56Sopenharmony_ci            Length of text inserted.
1117db96d56Sopenharmony_ci        """
1127db96d56Sopenharmony_ci        assert isinstance(s, str)
1137db96d56Sopenharmony_ci        self.text.insert(mark, s, tags)
1147db96d56Sopenharmony_ci        self.text.see(mark)
1157db96d56Sopenharmony_ci        self.text.update_idletasks()
1167db96d56Sopenharmony_ci        return len(s)
1177db96d56Sopenharmony_ci
1187db96d56Sopenharmony_ci    def writelines(self, lines):
1197db96d56Sopenharmony_ci        "Write each item in lines iterable."
1207db96d56Sopenharmony_ci        for line in lines:
1217db96d56Sopenharmony_ci            self.write(line)
1227db96d56Sopenharmony_ci
1237db96d56Sopenharmony_ci    def flush(self):
1247db96d56Sopenharmony_ci        "No flushing needed as write() directly writes to widget."
1257db96d56Sopenharmony_ci        pass
1267db96d56Sopenharmony_ci
1277db96d56Sopenharmony_ci    def showerror(self, *args, **kwargs):
1287db96d56Sopenharmony_ci        messagebox.showerror(*args, **kwargs)
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ci    def goto_file_line(self, event=None):
1317db96d56Sopenharmony_ci        """Handle request to open file/line.
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ci        If the selected or previous line in the output window
1347db96d56Sopenharmony_ci        contains a file name and line number, then open that file
1357db96d56Sopenharmony_ci        name in a new window and position on the line number.
1367db96d56Sopenharmony_ci
1377db96d56Sopenharmony_ci        Otherwise, display an error messagebox.
1387db96d56Sopenharmony_ci        """
1397db96d56Sopenharmony_ci        line = self.text.get("insert linestart", "insert lineend")
1407db96d56Sopenharmony_ci        result = file_line_helper(line)
1417db96d56Sopenharmony_ci        if not result:
1427db96d56Sopenharmony_ci            # Try the previous line.  This is handy e.g. in tracebacks,
1437db96d56Sopenharmony_ci            # where you tend to right-click on the displayed source line
1447db96d56Sopenharmony_ci            line = self.text.get("insert -1line linestart",
1457db96d56Sopenharmony_ci                                 "insert -1line lineend")
1467db96d56Sopenharmony_ci            result = file_line_helper(line)
1477db96d56Sopenharmony_ci            if not result:
1487db96d56Sopenharmony_ci                self.showerror(
1497db96d56Sopenharmony_ci                    "No special line",
1507db96d56Sopenharmony_ci                    "The line you point at doesn't look like "
1517db96d56Sopenharmony_ci                    "a valid file name followed by a line number.",
1527db96d56Sopenharmony_ci                    parent=self.text)
1537db96d56Sopenharmony_ci                return
1547db96d56Sopenharmony_ci        filename, lineno = result
1557db96d56Sopenharmony_ci        self.flist.gotofileline(filename, lineno)
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_ci
1587db96d56Sopenharmony_ci# These classes are currently not used but might come in handy
1597db96d56Sopenharmony_ciclass OnDemandOutputWindow:
1607db96d56Sopenharmony_ci
1617db96d56Sopenharmony_ci    tagdefs = {
1627db96d56Sopenharmony_ci        # XXX Should use IdlePrefs.ColorPrefs
1637db96d56Sopenharmony_ci        "stdout":  {"foreground": "blue"},
1647db96d56Sopenharmony_ci        "stderr":  {"foreground": "#007700"},
1657db96d56Sopenharmony_ci    }
1667db96d56Sopenharmony_ci
1677db96d56Sopenharmony_ci    def __init__(self, flist):
1687db96d56Sopenharmony_ci        self.flist = flist
1697db96d56Sopenharmony_ci        self.owin = None
1707db96d56Sopenharmony_ci
1717db96d56Sopenharmony_ci    def write(self, s, tags, mark):
1727db96d56Sopenharmony_ci        if not self.owin:
1737db96d56Sopenharmony_ci            self.setup()
1747db96d56Sopenharmony_ci        self.owin.write(s, tags, mark)
1757db96d56Sopenharmony_ci
1767db96d56Sopenharmony_ci    def setup(self):
1777db96d56Sopenharmony_ci        self.owin = owin = OutputWindow(self.flist)
1787db96d56Sopenharmony_ci        text = owin.text
1797db96d56Sopenharmony_ci        for tag, cnf in self.tagdefs.items():
1807db96d56Sopenharmony_ci            if cnf:
1817db96d56Sopenharmony_ci                text.tag_configure(tag, **cnf)
1827db96d56Sopenharmony_ci        text.tag_raise('sel')
1837db96d56Sopenharmony_ci        self.write = self.owin.write
1847db96d56Sopenharmony_ci
1857db96d56Sopenharmony_ciif __name__ == '__main__':
1867db96d56Sopenharmony_ci    from unittest import main
1877db96d56Sopenharmony_ci    main('idlelib.idle_test.test_outwin', verbosity=2, exit=False)
188