]>
Commit | Line | Data |
---|---|---|
d14a1e28 RD |
1 | #---------------------------------------------------------------------- |
2 | # Name: wxPython.lib.pyshell | |
3 | # Purpose: A Python Interactive Interpreter running in a wxStyledTextCtrl | |
4 | # window. | |
5 | # | |
6 | # Author: Robin Dunn | |
7 | # | |
8 | # Created: 7-July-2000 | |
9 | # RCS-ID: $Id$ | |
10 | # Copyright: (c) 2000 by Total Control Software | |
11 | # Licence: wxWindows license | |
12 | #---------------------------------------------------------------------- | |
b881fc78 RD |
13 | # 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net) |
14 | # | |
15 | # o 2.5 compatability update. | |
16 | # o Added deprecation warning. | |
17 | # | |
d14a1e28 RD |
18 | |
19 | """ | |
20 | PyShellWindow is a class that provides an Interactive Interpreter running | |
21 | inside a wxStyledTextCtrl, similar to the Python shell windows found in | |
22 | IDLE and PythonWin. | |
23 | ||
24 | There is still much to be done to improve this class, such as line | |
25 | buffering/recall, autoindent, calltips, autocomplete, fixing the colourizer, | |
26 | etc... But it's a good start. | |
27 | ||
28 | ||
29 | 8-10-2001 THIS MODULE IS NOW DEPRECATED. Please see the most excellent | |
30 | PyCrust package instead. | |
31 | ||
32 | """ | |
33 | ||
b881fc78 RD |
34 | import keyword |
35 | import sys | |
36 | import warnings | |
d14a1e28 | 37 | |
d14a1e28 RD |
38 | from code import InteractiveInterpreter |
39 | ||
b881fc78 RD |
40 | import wx |
41 | import wx.stc as stc | |
42 | ||
43 | warningmsg = r"""\ | |
44 | ||
45 | ########################################\ | |
46 | # THIS MODULE IS NOW DEPRECATED | | |
47 | # | | |
48 | # Please see the most excellent PyCrust | | |
49 | # package instead. | | |
50 | ########################################/ | |
51 | ||
52 | """ | |
53 | ||
54 | warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) | |
55 | ||
d14a1e28 RD |
56 | #---------------------------------------------------------------------- |
57 | # default styles, etc. to use for the STC | |
58 | ||
b881fc78 | 59 | if wx.Platform == '__WXMSW__': |
d14a1e28 RD |
60 | _defaultSize = 8 |
61 | else: | |
62 | _defaultSize = 10 | |
63 | ||
64 | ||
65 | _default_properties = { | |
66 | 'selMargin' : 0, | |
67 | 'marginWidth' : 1, | |
68 | 'ps1' : '>>> ', | |
69 | 'stdout' : 'fore:#0000FF', | |
70 | 'stderr' : 'fore:#007f00', | |
71 | 'trace' : 'fore:#FF0000', | |
72 | ||
73 | 'default' : 'size:%d' % _defaultSize, | |
74 | 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold', | |
75 | 'bracebad' : 'fore:#000000,back:#FF0000,bold', | |
76 | ||
77 | # properties for the various Python lexer styles | |
78 | 'comment' : 'fore:#007F00', | |
79 | 'number' : 'fore:#007F7F', | |
80 | 'string' : 'fore:#7F007F,italic', | |
81 | 'char' : 'fore:#7F007F,italic', | |
82 | 'keyword' : 'fore:#00007F,bold', | |
83 | 'triple' : 'fore:#7F0000', | |
84 | 'tripledouble': 'fore:#7F0000', | |
85 | 'class' : 'fore:#0000FF,bold,underline', | |
86 | 'def' : 'fore:#007F7F,bold', | |
87 | 'operator' : 'bold', | |
88 | ||
89 | } | |
90 | ||
91 | ||
92 | # new style numbers | |
93 | _stdout_style = 15 | |
94 | _stderr_style = 16 | |
95 | _trace_style = 17 | |
96 | ||
97 | ||
98 | #---------------------------------------------------------------------- | |
99 | ||
b881fc78 RD |
100 | class PyShellWindow(stc.StyledTextCtrl, InteractiveInterpreter): |
101 | def __init__(self, parent, ID, pos=wx.DefaultPosition, | |
102 | size=wx.DefaultSize, style=0, | |
d14a1e28 | 103 | locals=None, properties=None, banner=None): |
b881fc78 | 104 | stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style) |
d14a1e28 RD |
105 | InteractiveInterpreter.__init__(self, locals) |
106 | ||
107 | self.lastPromptPos = 0 | |
108 | ||
109 | # the line cache is used to cycle through previous commands | |
110 | self.lines = [] | |
111 | self.lastUsedLine = self.curLine = 0 | |
112 | ||
113 | # set defaults and then deal with any user defined properties | |
114 | self.props = {} | |
115 | self.props.update(_default_properties) | |
116 | if properties: | |
117 | self.props.update(properties) | |
118 | self.UpdateProperties() | |
119 | ||
120 | # copyright/banner message | |
121 | if banner is None: | |
122 | self.write("Python %s on %s\n" % #%s\n(%s)\n" % | |
123 | (sys.version, sys.platform, | |
124 | #sys.copyright, self.__class__.__name__ | |
125 | )) | |
126 | else: | |
127 | self.write("%s\n" % banner) | |
128 | ||
129 | # write the initial prompt | |
130 | self.Prompt() | |
131 | ||
132 | # Event handlers | |
b881fc78 RD |
133 | self.Bind(wx.EVT_KEY_DOWN, self.OnKey) |
134 | self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI, id=ID) | |
135 | #self.Bind(stc.EVT_STC_STYLENEEDED, self.OnStyle, id=ID) | |
d14a1e28 RD |
136 | |
137 | ||
138 | def GetLocals(self): return self.locals | |
139 | def SetLocals(self, locals): self.locals = locals | |
140 | ||
141 | def GetProperties(self): return self.props | |
142 | def SetProperties(self, properties): | |
143 | self.props.update(properties) | |
144 | self.UpdateProperties() | |
145 | ||
146 | ||
147 | def UpdateProperties(self): | |
148 | """ | |
149 | Reset the editor and other settings based on the contents of the | |
150 | current properties dictionary. | |
151 | """ | |
152 | p = self.props | |
153 | ||
b881fc78 | 154 | #self.SetEdgeMode(stc.STC_EDGE_LINE) |
d14a1e28 RD |
155 | #self.SetEdgeColumn(80) |
156 | ||
157 | ||
158 | # set the selection margin and window margin | |
159 | self.SetMarginWidth(1, p['selMargin']) | |
160 | self.SetMargins(p['marginWidth'], p['marginWidth']) | |
161 | ||
162 | # styles | |
b881fc78 | 163 | self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default']) |
d14a1e28 RD |
164 | self.StyleClearAll() |
165 | self.StyleSetSpec(_stdout_style, p['stdout']) | |
166 | self.StyleSetSpec(_stderr_style, p['stderr']) | |
167 | self.StyleSetSpec(_trace_style, p['trace']) | |
168 | ||
b881fc78 RD |
169 | self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood']) |
170 | self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad']) | |
171 | self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment']) | |
172 | self.StyleSetSpec(stc.STC_P_NUMBER, p['number']) | |
173 | self.StyleSetSpec(stc.STC_P_STRING, p['string']) | |
174 | self.StyleSetSpec(stc.STC_P_CHARACTER, p['char']) | |
175 | self.StyleSetSpec(stc.STC_P_WORD, p['keyword']) | |
176 | self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple']) | |
177 | self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble']) | |
178 | self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class']) | |
179 | self.StyleSetSpec(stc.STC_P_DEFNAME, p['def']) | |
180 | self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator']) | |
181 | self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment']) | |
d14a1e28 RD |
182 | |
183 | ||
184 | # used for writing to stdout, etc. | |
185 | def _write(self, text, style=_stdout_style): | |
186 | self.lastPromptPos = 0 | |
187 | pos = self.GetCurrentPos() | |
188 | self.AddText(text) | |
189 | self.StartStyling(pos, 0xFF) | |
190 | self.SetStyling(len(text), style) | |
191 | self.EnsureCaretVisible() | |
b881fc78 | 192 | wx.Yield() |
d14a1e28 RD |
193 | |
194 | write = _write | |
195 | ||
196 | def writeTrace(self, text): | |
197 | self._write(text, _trace_style) | |
198 | ||
199 | ||
200 | def Prompt(self): | |
201 | # is the current line non-empty? | |
202 | text, pos = self.GetCurLine() | |
203 | if pos != 0: | |
204 | self.AddText('\n') | |
205 | self.AddText(self.props['ps1']) | |
206 | self.lastPromptPos = self.GetCurrentPos() | |
207 | self.EnsureCaretVisible() | |
208 | self.ScrollToColumn(0) | |
209 | ||
210 | ||
211 | def PushLine(self, text): | |
212 | # TODO: Add the text to the line cache, manage the cache so | |
213 | # it doesn't get too big. | |
214 | pass | |
215 | ||
216 | ||
217 | ||
218 | def OnKey(self, evt): | |
219 | key = evt.KeyCode() | |
b881fc78 | 220 | if key == wx.WXK_RETURN: |
d14a1e28 RD |
221 | pos = self.GetCurrentPos() |
222 | lastPos = self.GetTextLength() | |
223 | ||
224 | # if not on the last line, duplicate the current line | |
225 | if self.GetLineCount()-1 != self.GetCurrentLine(): | |
226 | text, col = self.GetCurLine() | |
227 | prompt = self.props['ps1'] | |
228 | lp = len(prompt) | |
229 | if text[:lp] == prompt: | |
230 | text = text[lp:] | |
231 | ||
232 | self.SetSelection(self.lastPromptPos, lastPos) | |
233 | self.ReplaceSelection(text[:-1]) | |
234 | ||
235 | else: # try to execute the text from the prompt to the end | |
236 | if lastPos == self.lastPromptPos: | |
237 | self.AddText('\n') | |
238 | self.Prompt() | |
239 | return | |
240 | ||
241 | text = self.GetTextRange(self.lastPromptPos, lastPos) | |
242 | self.AddText('\n') | |
243 | ||
244 | more = self.runsource(text) | |
245 | if not more: | |
246 | self.PushLine(text) | |
247 | self.Prompt() | |
248 | ||
249 | # TODO: Add handlers for Alt-P and Alt-N to cycle through entries | |
250 | # in the line cache | |
251 | ||
252 | else: | |
253 | evt.Skip() | |
254 | ||
255 | ||
256 | def OnStyle(self, evt): | |
257 | # Only style from the prompt pos to the end | |
258 | lastPos = self.GetTextLength() | |
259 | if self.lastPromptPos and self.lastPromptPos != lastPos: | |
b881fc78 | 260 | self.SetLexer(stc.STC_LEX_PYTHON) |
d14a1e28 RD |
261 | self.SetKeywords(0, ' '.join(keyword.kwlist)) |
262 | ||
263 | self.Colourise(self.lastPromptPos, lastPos) | |
264 | ||
265 | self.SetLexer(0) | |
266 | ||
267 | ||
268 | def OnUpdateUI(self, evt): | |
269 | # check for matching braces | |
270 | braceAtCaret = -1 | |
271 | braceOpposite = -1 | |
272 | charBefore = None | |
273 | caretPos = self.GetCurrentPos() | |
274 | if caretPos > 0: | |
275 | charBefore = self.GetCharAt(caretPos - 1) | |
276 | styleBefore = self.GetStyleAt(caretPos - 1) | |
277 | ||
278 | # check before | |
b881fc78 | 279 | if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR: |
d14a1e28 RD |
280 | braceAtCaret = caretPos - 1 |
281 | ||
282 | # check after | |
283 | if braceAtCaret < 0: | |
284 | charAfter = self.GetCharAt(caretPos) | |
285 | styleAfter = self.GetStyleAt(caretPos) | |
b881fc78 | 286 | if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR: |
d14a1e28 RD |
287 | braceAtCaret = caretPos |
288 | ||
289 | if braceAtCaret >= 0: | |
290 | braceOpposite = self.BraceMatch(braceAtCaret) | |
291 | ||
292 | if braceAtCaret != -1 and braceOpposite == -1: | |
293 | self.BraceBadlight(braceAtCaret) | |
294 | else: | |
295 | self.BraceHighlight(braceAtCaret, braceOpposite) | |
296 | ||
297 | ||
298 | ||
299 | #---------------------------------------------- | |
300 | # overloaded methods from InteractiveInterpreter | |
301 | def runsource(self, source): | |
302 | stdout, stderr = sys.stdout, sys.stderr | |
303 | sys.stdout = FauxFile(self, _stdout_style) | |
304 | sys.stderr = FauxFile(self, _stderr_style) | |
305 | ||
306 | more = InteractiveInterpreter.runsource(self, source) | |
307 | ||
308 | sys.stdout, sys.stderr = stdout, stderr | |
309 | return more | |
310 | ||
311 | def showsyntaxerror(self, filename=None): | |
312 | self.write = self.writeTrace | |
313 | InteractiveInterpreter.showsyntaxerror(self, filename) | |
314 | self.write = self._write | |
315 | ||
316 | def showtraceback(self): | |
317 | self.write = self.writeTrace | |
318 | InteractiveInterpreter.showtraceback(self) | |
319 | self.write = self._write | |
320 | ||
321 | #---------------------------------------------------------------------- | |
322 | ||
323 | class FauxFile: | |
324 | def __init__(self, psw, style): | |
325 | self.psw = psw | |
326 | self.style = style | |
327 | ||
328 | def write(self, text): | |
329 | self.psw.write(text, self.style) | |
330 | ||
331 | def writelines(self, lst): | |
332 | map(self.write, lst) | |
333 | ||
334 | def flush(self): | |
335 | pass | |
336 | ||
337 | ||
338 | #---------------------------------------------------------------------- | |
339 | # test code | |
340 | ||
341 | if __name__ == '__main__': | |
b881fc78 | 342 | app = wx.PyWidgetTester(size = (640, 480)) |
d14a1e28 RD |
343 | app.SetWidget(PyShellWindow, -1) |
344 | app.MainLoop() | |
345 | ||
346 | ||
347 | #---------------------------------------------------------------------- | |
1fded56b | 348 | |
1fded56b | 349 |