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