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