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