blob: ed6774b8755eaf6bcb19607e2987d8432472e7b2 [file] [log] [blame]
David Scherer7aced172000-08-15 01:13:23 +00001# changes by dscherer@cmu.edu
2# - OutputWindow and OnDemandOutputWindow have been hastily
3# extended to provide readline() support, an "iomark" separate
4# from the "insert" cursor, and scrolling to clear the window.
5# These changes are used by the ExecBinding module to provide
6# standard input and output for user programs. Many of the new
7# features are very similar to features of PyShell, which is a
8# subclass of OutputWindow. Someone should make some sense of
9# this.
10
11from Tkinter import *
12from EditorWindow import EditorWindow
13import re
14import tkMessageBox
15
16from UndoDelegator import UndoDelegator
17
18class OutputUndoDelegator(UndoDelegator):
19 reading = 0
20 # Forbid insert/delete before the I/O mark, in the blank lines after
21 # the output, or *anywhere* if we are not presently doing user input
22 def insert(self, index, chars, tags=None):
23 try:
24 if (self.delegate.compare(index, "<", "iomark") or
25 self.delegate.compare(index, ">", "endmark") or
26 (index!="iomark" and not self.reading)):
27 self.delegate.bell()
28 return
29 except TclError:
30 pass
31 UndoDelegator.insert(self, index, chars, tags)
32 def delete(self, index1, index2=None):
33 try:
34 if (self.delegate.compare(index1, "<", "iomark") or
35 self.delegate.compare(index1, ">", "endmark") or
36 (index2 and self.delegate.compare(index2, ">=", "endmark")) or
37 not self.reading):
38 self.delegate.bell()
39 return
40 except TclError:
41 pass
42 UndoDelegator.delete(self, index1, index2)
43
44class OutputWindow(EditorWindow):
45 """An editor window that can serve as an input and output file.
46 The input support has been rather hastily hacked in, and should
47 not be trusted.
48 """
49
50 UndoDelegator = OutputUndoDelegator
51 source_window = None
52
53 def __init__(self, *args, **keywords):
54 if keywords.has_key('source_window'):
55 self.source_window = keywords['source_window']
56 apply(EditorWindow.__init__, (self,) + args)
57 self.text.bind("<<goto-file-line>>", self.goto_file_line)
58 self.text.bind("<<newline-and-indent>>", self.enter_callback)
59 self.text.mark_set("iomark","1.0")
60 self.text.mark_gravity("iomark", LEFT)
61 self.text.mark_set("endmark","1.0")
62
63 # Customize EditorWindow
64
65 def ispythonsource(self, filename):
66 # No colorization needed
67 return 0
68
69 def short_title(self):
70 return "Output"
71
72 def long_title(self):
73 return ""
74
75 def maybesave(self):
76 # Override base class method -- don't ask any questions
77 if self.get_saved():
78 return "yes"
79 else:
80 return "no"
81
82 # Act as input file - incomplete
83
84 def set_line_and_column(self, event=None):
85 index = self.text.index(INSERT)
86 if (self.text.compare(index, ">", "endmark")):
87 self.text.mark_set("insert", "endmark")
88 self.text.see("insert")
89 EditorWindow.set_line_and_column(self)
90
91 reading = 0
92 canceled = 0
93 endoffile = 0
94
95 def readline(self):
96 save = self.reading
97 try:
98 self.reading = self.undo.reading = 1
99 self.text.mark_set("insert", "iomark")
100 self.text.see("insert")
101 self.top.mainloop()
102 finally:
103 self.reading = self.undo.reading = save
104 line = self.text.get("input", "iomark")
105 if self.canceled:
106 self.canceled = 0
107 raise KeyboardInterrupt
108 if self.endoffile:
109 self.endoffile = 0
110 return ""
111 return line or '\n'
112
113 def close(self):
114 self.interrupt()
115 return EditorWindow.close(self)
116
117 def interrupt(self):
118 if self.reading:
119 self.endoffile = 1
120 self.top.quit()
121
122 def enter_callback(self, event):
123 if self.reading and self.text.compare("insert", ">=", "iomark"):
124 self.text.mark_set("input", "iomark")
125 self.text.mark_set("iomark", "insert")
126 self.write('\n',"iomark")
127 self.text.tag_add("stdin", "input", "iomark")
128 self.text.update_idletasks()
129 self.top.quit() # Break out of recursive mainloop() in raw_input()
130
131 return "break"
132
133 # Act as output file
134
135 def write(self, s, tags=(), mark="iomark"):
136 self.text.mark_gravity(mark, RIGHT)
Steven M. Gava75a8e652002-02-23 23:27:08 +0000137 self.text.insert(mark, s, tags)
David Scherer7aced172000-08-15 01:13:23 +0000138 self.text.mark_gravity(mark, LEFT)
139 self.text.see(mark)
140 self.text.update()
141
142 def writelines(self, l):
143 map(self.write, l)
144
145 def flush(self):
146 pass
147
148 # Our own right-button menu
149
150 rmenu_specs = [
151 ("Go to file/line", "<<goto-file-line>>"),
152 ]
153
154 file_line_pats = [
155 r'file "([^"]*)", line (\d+)',
156 r'([^\s]+)\((\d+)\)',
157 r'([^\s]+):\s*(\d+):',
158 ]
159
160 file_line_progs = None
161
162 def goto_file_line(self, event=None):
163 if self.file_line_progs is None:
164 l = []
165 for pat in self.file_line_pats:
166 l.append(re.compile(pat, re.IGNORECASE))
167 self.file_line_progs = l
168 # x, y = self.event.x, self.event.y
169 # self.text.mark_set("insert", "@%d,%d" % (x, y))
170 line = self.text.get("insert linestart", "insert lineend")
171 result = self._file_line_helper(line)
172 if not result:
173 # Try the previous line. This is handy e.g. in tracebacks,
174 # where you tend to right-click on the displayed source line
175 line = self.text.get("insert -1line linestart",
176 "insert -1line lineend")
177 result = self._file_line_helper(line)
178 if not result:
179 tkMessageBox.showerror(
180 "No special line",
181 "The line you point at doesn't look like "
182 "a valid file name followed by a line number.",
183 master=self.text)
184 return
185 filename, lineno = result
186 edit = self.untitled(filename) or self.flist.open(filename)
187 edit.gotoline(lineno)
188 edit.wakeup()
189
190 def untitled(self, filename):
191 if filename!='Untitled' or not self.source_window or self.source_window.io.filename:
192 return None
193 return self.source_window
194
195 def _file_line_helper(self, line):
196 for prog in self.file_line_progs:
197 m = prog.search(line)
198 if m:
199 break
200 else:
201 return None
202 filename, lineno = m.group(1, 2)
203 if not self.untitled(filename):
204 try:
205 f = open(filename, "r")
206 f.close()
207 except IOError:
208 return None
209 try:
210 return filename, int(lineno)
211 except TypeError:
212 return None
213
214# This classes now used by ExecBinding.py:
215
216class OnDemandOutputWindow:
217 source_window = None
218
219 tagdefs = {
220 # XXX Should use IdlePrefs.ColorPrefs
221 "stdin": {"foreground": "black"},
222 "stdout": {"foreground": "blue"},
223 "stderr": {"foreground": "red"},
224 }
225
226 def __init__(self, flist):
227 self.flist = flist
228 self.owin = None
229 self.title = "Output"
230 self.close_hook = None
231 self.old_close = None
232
233 def owclose(self):
234 if self.close_hook:
235 self.close_hook()
236 if self.old_close:
237 self.old_close()
238
239 def set_title(self, title):
240 self.title = title
241 if self.owin and self.owin.text:
242 self.owin.saved_change_hook()
243
244 def write(self, s, tags=(), mark="iomark"):
245 if not self.owin or not self.owin.text:
246 self.setup()
247 self.owin.write(s, tags, mark)
248
249 def readline(self):
250 if not self.owin or not self.owin.text:
251 self.setup()
252 return self.owin.readline()
253
254 def scroll_clear(self):
255 if self.owin and self.owin.text:
256 lineno = self.owin.getlineno("endmark")
257 self.owin.text.mark_set("insert","endmark")
258 self.owin.text.yview(float(lineno))
259 self.owin.wakeup()
260
261 def setup(self):
262 self.owin = owin = OutputWindow(self.flist, source_window = self.source_window)
263 owin.short_title = lambda self=self: self.title
264 text = owin.text
265
266 self.old_close = owin.close_hook
267 owin.close_hook = self.owclose
268
269 # xxx Bad hack: 50 blank lines at the bottom so that
270 # we can scroll the top of the window to the output
271 # cursor in scroll_clear(). There must be a better way...
272 owin.text.mark_gravity('endmark', LEFT)
273 owin.text.insert('iomark', '\n'*50)
274 owin.text.mark_gravity('endmark', RIGHT)
275
276 for tag, cnf in self.tagdefs.items():
277 if cnf:
278 apply(text.tag_configure, (tag,), cnf)
279 text.tag_raise('sel')