Home | History | Annotate | Download | only in idlelib
      1 """Editor window that can serve as an output file.
      2 """
      3 
      4 import re
      5 
      6 from tkinter import messagebox
      7 
      8 from idlelib.editor import EditorWindow
      9 from idlelib import iomenu
     10 
     11 
     12 file_line_pats = [
     13     # order of patterns matters
     14     r'file "([^"]*)", line (\d+)',
     15     r'([^\s]+)\((\d+)\)',
     16     r'^(\s*\S.*?):\s*(\d+):',  # Win filename, maybe starting with spaces
     17     r'([^\s]+):\s*(\d+):',     # filename or path, ltrim
     18     r'^\s*(\S.*?):\s*(\d+):',  # Win abs path with embedded spaces, ltrim
     19 ]
     20 
     21 file_line_progs = None
     22 
     23 
     24 def compile_progs():
     25     "Compile the patterns for matching to file name and line number."
     26     global file_line_progs
     27     file_line_progs = [re.compile(pat, re.IGNORECASE)
     28                        for pat in file_line_pats]
     29 
     30 
     31 def file_line_helper(line):
     32     """Extract file name and line number from line of text.
     33 
     34     Check if line of text contains one of the file/line patterns.
     35     If it does and if the file and line are valid, return
     36     a tuple of the file name and line number.  If it doesn't match
     37     or if the file or line is invalid, return None.
     38     """
     39     if not file_line_progs:
     40         compile_progs()
     41     for prog in file_line_progs:
     42         match = prog.search(line)
     43         if match:
     44             filename, lineno = match.group(1, 2)
     45             try:
     46                 f = open(filename, "r")
     47                 f.close()
     48                 break
     49             except OSError:
     50                 continue
     51     else:
     52         return None
     53     try:
     54         return filename, int(lineno)
     55     except TypeError:
     56         return None
     57 
     58 
     59 class OutputWindow(EditorWindow):
     60     """An editor window that can serve as an output file.
     61 
     62     Also the future base class for the Python shell window.
     63     This class has no input facilities.
     64 
     65     Adds binding to open a file at a line to the text widget.
     66     """
     67 
     68     # Our own right-button menu
     69     rmenu_specs = [
     70         ("Cut", "<<cut>>", "rmenu_check_cut"),
     71         ("Copy", "<<copy>>", "rmenu_check_copy"),
     72         ("Paste", "<<paste>>", "rmenu_check_paste"),
     73         (None, None, None),
     74         ("Go to file/line", "<<goto-file-line>>", None),
     75     ]
     76 
     77     def __init__(self, *args):
     78         EditorWindow.__init__(self, *args)
     79         self.text.bind("<<goto-file-line>>", self.goto_file_line)
     80         self.text.unbind("<<toggle-code-context>>")
     81         self.update_menu_state('options', '*Code Context', 'disabled')
     82 
     83     # Customize EditorWindow
     84     def ispythonsource(self, filename):
     85         "Python source is only part of output: do not colorize."
     86         return False
     87 
     88     def short_title(self):
     89         "Customize EditorWindow title."
     90         return "Output"
     91 
     92     def maybesave(self):
     93         "Customize EditorWindow to not display save file messagebox."
     94         return 'yes' if self.get_saved() else 'no'
     95 
     96     # Act as output file
     97     def write(self, s, tags=(), mark="insert"):
     98         """Write text to text widget.
     99 
    100         The text is inserted at the given index with the provided
    101         tags.  The text widget is then scrolled to make it visible
    102         and updated to display it, giving the effect of seeing each
    103         line as it is added.
    104 
    105         Args:
    106             s: Text to insert into text widget.
    107             tags: Tuple of tag strings to apply on the insert.
    108             mark: Index for the insert.
    109 
    110         Return:
    111             Length of text inserted.
    112         """
    113         if isinstance(s, bytes):
    114             s = s.decode(iomenu.encoding, "replace")
    115         self.text.insert(mark, s, tags)
    116         self.text.see(mark)
    117         self.text.update()
    118         return len(s)
    119 
    120     def writelines(self, lines):
    121         "Write each item in lines iterable."
    122         for line in lines:
    123             self.write(line)
    124 
    125     def flush(self):
    126         "No flushing needed as write() directly writes to widget."
    127         pass
    128 
    129     def showerror(self, *args, **kwargs):
    130         messagebox.showerror(*args, **kwargs)
    131 
    132     def goto_file_line(self, event=None):
    133         """Handle request to open file/line.
    134 
    135         If the selected or previous line in the output window
    136         contains a file name and line number, then open that file
    137         name in a new window and position on the line number.
    138 
    139         Otherwise, display an error messagebox.
    140         """
    141         line = self.text.get("insert linestart", "insert lineend")
    142         result = file_line_helper(line)
    143         if not result:
    144             # Try the previous line.  This is handy e.g. in tracebacks,
    145             # where you tend to right-click on the displayed source line
    146             line = self.text.get("insert -1line linestart",
    147                                  "insert -1line lineend")
    148             result = file_line_helper(line)
    149             if not result:
    150                 self.showerror(
    151                     "No special line",
    152                     "The line you point at doesn't look like "
    153                     "a valid file name followed by a line number.",
    154                     parent=self.text)
    155                 return
    156         filename, lineno = result
    157         self.flist.gotofileline(filename, lineno)
    158 
    159 
    160 # These classes are currently not used but might come in handy
    161 class OnDemandOutputWindow:
    162 
    163     tagdefs = {
    164         # XXX Should use IdlePrefs.ColorPrefs
    165         "stdout":  {"foreground": "blue"},
    166         "stderr":  {"foreground": "#007700"},
    167     }
    168 
    169     def __init__(self, flist):
    170         self.flist = flist
    171         self.owin = None
    172 
    173     def write(self, s, tags, mark):
    174         if not self.owin:
    175             self.setup()
    176         self.owin.write(s, tags, mark)
    177 
    178     def setup(self):
    179         self.owin = owin = OutputWindow(self.flist)
    180         text = owin.text
    181         for tag, cnf in self.tagdefs.items():
    182             if cnf:
    183                 text.tag_configure(tag, **cnf)
    184         text.tag_raise('sel')
    185         self.write = self.owin.write
    186 
    187 if __name__ == '__main__':
    188     from unittest import main
    189     main('idlelib.idle_test.test_outwin', verbosity=2, exit=False)
    190