]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/PyCrust/PyCrustEditor.py
458e0ad5b1974bc6484161653cf15cd6c5d81d10
[wxWidgets.git] / wxPython / wxPython / lib / PyCrust / PyCrustEditor.py
1
2 __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
3 __cvsid__ = "$Id$"
4 __date__ = "July 1, 2001"
5 __version__ = "$Revision$"[11:-2]
6
7 from wxPython.wx import *
8 from wxPython.stc import *
9
10 import keyword
11 import sys
12
13
14 if wxPlatform == '__WXMSW__':
15 faces = { 'times' : 'Times New Roman',
16 'mono' : 'Courier New',
17 'helv' : 'Lucida Console',
18 'lucida' : 'Lucida Console',
19 'other' : 'Comic Sans MS',
20 'size' : 8,
21 'lnsize' : 7,
22 'backcol': '#FFFFFF',
23 }
24 else: # GTK
25 faces = { 'times' : 'Times',
26 'mono' : 'Courier',
27 'helv' : 'Helvetica',
28 'other' : 'new century schoolbook',
29 'size' : 9,
30 'lnsize' : 8,
31 'backcol': '#FFFFFF',
32 }
33
34
35 class Editor(wxStyledTextCtrl):
36 """PyCrust Editor based on wxStyledTextCtrl."""
37 revision = __version__
38 def __init__(self, parent, id):
39 """Create a PyCrust editor object based on wxStyledTextCtrl."""
40 wxStyledTextCtrl.__init__(self, parent, id, style=wxCLIP_CHILDREN)
41 # Commands get pushed to a method determined by the outer shell.
42 #self.shellPush = pushMethod
43 # Keep track of the most recent prompt starting and ending positions.
44 self.promptPos = [0, 0]
45 # Keep track of multi-line commands.
46 self.more = 0
47 # Configure various defaults and user preferences.
48 self.config()
49 # Assign handlers for keyboard events.
50 EVT_KEY_DOWN(self, self.OnKeyDown)
51 EVT_CHAR(self, self.OnChar)
52
53 def config(self):
54 """Configure editor based on user preferences."""
55 self.SetMarginType(1, wxSTC_MARGIN_NUMBER)
56 self.SetMarginWidth(1, 40)
57
58 self.SetLexer(wxSTC_LEX_PYTHON)
59 self.SetKeyWords(0, ' '.join(keyword.kwlist))
60
61 self.setStyles(faces)
62 self.SetViewWhiteSpace(0)
63 self.SetTabWidth(4)
64 self.SetUseTabs(0)
65 # Do we want to automatically pop up command completion options?
66 self.autoComplete = 1
67 self.autoCompleteCaseInsensitive = 1
68 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
69 # De we want to automatically pop up command argument help?
70 self.autoCallTip = 1
71 self.CallTipSetBackground(wxColour(255, 255, 232))
72
73 def setStyles(self, faces):
74 """Configure font size, typeface and color for lexer."""
75
76 # Default style
77 self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d" % faces)
78
79 self.StyleClearAll()
80
81 # Built in styles
82 self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces)
83 self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces)
84 self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88")
85 self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88")
86
87 # Python styles
88 self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces)
89 self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces)
90 self.StyleSetSpec(wxSTC_P_NUMBER, "")
91 self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces)
92 self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces)
93 self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold")
94 self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000")
95 self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8")
96 self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold")
97 self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold")
98 self.StyleSetSpec(wxSTC_P_OPERATOR, "")
99 self.StyleSetSpec(wxSTC_P_IDENTIFIER, "")
100 self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F")
101 self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces)
102
103 def OnKeyDown(self, event):
104 """Key down event handler.
105
106 The main goal here is to not allow modifications to previous
107 lines of text.
108 """
109 key = event.KeyCode()
110 currpos = self.GetCurrentPos()
111 stoppos = self.promptPos[1]
112 # If the auto-complete window is up let it do its thing.
113 if self.AutoCompActive():
114 event.Skip()
115 # Return is used to submit a command to the interpreter.
116 elif key == WXK_RETURN:
117 if self.CallTipActive: self.CallTipCancel()
118 self.processLine()
119 # Home needs to be aware of the prompt.
120 elif key == WXK_HOME:
121 if currpos >= stoppos:
122 self.SetCurrentPos(stoppos)
123 self.SetAnchor(stoppos)
124 else:
125 event.Skip()
126 # Basic navigation keys should work anywhere.
127 elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \
128 WXK_PRIOR, WXK_NEXT):
129 event.Skip()
130 # Don't backspace over the latest prompt.
131 elif key == WXK_BACK:
132 if currpos > stoppos:
133 event.Skip()
134 # Only allow these keys after the latest prompt.
135 elif key in (WXK_TAB, WXK_DELETE):
136 if currpos >= stoppos:
137 event.Skip()
138 # Don't toggle between insert mode and overwrite mode.
139 elif key == WXK_INSERT:
140 pass
141 else:
142 event.Skip()
143
144 def OnChar(self, event):
145 """Keypress event handler.
146
147 The main goal here is to not allow modifications to previous
148 lines of text.
149 """
150 key = event.KeyCode()
151 currpos = self.GetCurrentPos()
152 stoppos = self.promptPos[1]
153 if currpos >= stoppos:
154 if key == 46:
155 # "." The dot or period key activates auto completion.
156 # Get the command between the prompt and the cursor.
157 # Add a dot to the end of the command.
158 command = self.GetTextRange(stoppos, currpos) + '.'
159 self.write('.')
160 if self.autoComplete: self.autoCompleteShow(command)
161 elif key == 40:
162 # "(" The left paren activates a call tip and cancels
163 # an active auto completion.
164 if self.AutoCompActive(): self.AutoCompCancel()
165 # Get the command between the prompt and the cursor.
166 # Add the '(' to the end of the command.
167 command = self.GetTextRange(stoppos, currpos) + '('
168 self.write('(')
169 if self.autoCallTip: self.autoCallTipShow(command)
170 else:
171 # Allow the normal event handling to take place.
172 event.Skip()
173 else:
174 pass
175
176 def setStatusText(self, text):
177 """Display status information."""
178
179 # This method will most likely be replaced by the enclosing app
180 # to do something more interesting, like write to a status bar.
181 print text
182
183 def autoCompleteShow(self, command):
184 """Display auto-completion popup list."""
185 list = self.getAutoCompleteList(command)
186 if list:
187 options = ' '.join(list)
188 offset = 0
189 self.AutoCompShow(offset, options)
190
191 def getAutoCompleteList(self, command):
192 """Return list of auto-completion options for command."""
193
194 # This method needs to be replaced by the enclosing app
195 # to get the proper auto complete list from the interpreter.
196 return []
197
198 def autoCallTipShow(self, command):
199 """Display argument spec and docstring in a popup bubble thingie."""
200 if self.CallTipActive: self.CallTipCancel()
201 tip = self.getCallTip(command)
202 if tip:
203 offset = self.GetCurrentPos()
204 self.CallTipShow(offset, tip)
205
206 def getCallTip(self, command):
207 """Return arguments and docstring for command."""
208
209 # This method needs to be replaced by the enclosing app
210 # to get the proper auto complete list from the interpreter.
211 return ''
212
213 def processLine(self):
214 """Process the line of text at which the user hit Enter."""
215
216 # The user hit ENTER and we need to decide what to do. They could be
217 # sitting on any line in the editor.
218
219 # Grab information about the current line.
220 thepos = self.GetCurrentPos()
221 theline = self.GetCurrentLine()
222 thetext = self.GetCurLine()[0]
223 command = self.getCommand(thetext)
224 # Go to the very bottom of the editor.
225 endpos = self.GetTextLength()
226 self.SetCurrentPos(endpos)
227 endline = self.GetCurrentLine()
228 # If they hit RETURN on the last line, execute the command.
229 if theline == endline:
230 self.push(command)
231 # Otherwise, replace the last line with the new line.
232 else:
233 # If the new line contains a command (even an invalid one).
234 if command:
235 startpos = self.PositionFromLine(endline)
236 self.SetSelection(startpos, endpos)
237 self.ReplaceSelection('')
238 self.prompt()
239 self.write(command)
240 # Otherwise, put the cursor back where we started.
241 else:
242 self.SetCurrentPos(thepos)
243 self.SetAnchor(thepos)
244
245 def getCommand(self, text):
246 """Extract a command from text which may include a shell prompt.
247
248 The command may not necessarily be valid Python syntax.
249 """
250
251 # XXX Need to extract real prompts here. Need to keep track of the
252 # prompt every time a command is issued. Do this in the interpreter
253 # with a line number, prompt, command dictionary. For the history, perhaps.
254 ps1 = str(sys.ps1)
255 ps1size = len(ps1)
256 ps2 = str(sys.ps2)
257 ps2size = len(ps2)
258 text = text.rstrip()
259 # Strip the prompt off the front of text leaving just the command.
260 if text[:ps1size] == ps1:
261 command = text[ps1size:]
262 elif text[:ps2size] == ps2:
263 command = text[ps2size:]
264 else:
265 command = ''
266 return command
267
268 def push(self, command):
269 """Start a new line, send command to the shell, display a prompt."""
270 self.write('\n')
271 self.more = self.shellPush(command)
272 self.prompt()
273 # Keep the undo feature from undoing previous responses. The only
274 # thing that can be undone is stuff typed after the prompt, before
275 # hitting enter. After they hit enter it becomes permanent.
276 self.EmptyUndoBuffer()
277
278 def clear(self):
279 """Delete all text from the editor."""
280 self.ClearAll()
281
282 def prompt(self):
283 """Display appropriate prompt for the context, either ps1 or ps2.
284
285 If this is a continuation line, autoindent as necessary.
286 """
287 if self.more:
288 prompt = str(sys.ps2)
289 else:
290 prompt = str(sys.ps1)
291 pos = self.GetCurLine()[1]
292 if pos > 0: self.write('\n')
293 self.promptPos[0] = self.GetCurrentPos()
294 self.write(prompt)
295 self.promptPos[1] = self.GetCurrentPos()
296 # XXX Add some autoindent magic here if more.
297 if self.more:
298 self.write('\t') # Temporary hack indentation.
299 self.EnsureCaretVisible()
300 self.ScrollToColumn(0)
301
302 def readIn(self):
303 """Replacement for stdin."""
304 prompt = 'Please enter your response:'
305 dialog = wxTextEntryDialog(None, prompt, \
306 'Input Dialog (Standard)', '')
307 try:
308 if dialog.ShowModal() == wxID_OK:
309 text = dialog.GetValue()
310 self.write(text + '\n')
311 return text
312 finally:
313 dialog.Destroy()
314 return ''
315
316 def readRaw(self, prompt='Please enter your response:'):
317 """Replacement for raw_input."""
318 dialog = wxTextEntryDialog(None, prompt, \
319 'Input Dialog (Raw)', '')
320 try:
321 if dialog.ShowModal() == wxID_OK:
322 text = dialog.GetValue()
323 return text
324 finally:
325 dialog.Destroy()
326 return ''
327
328 def write(self, text):
329 """Display text in the editor."""
330 self.AddText(text)
331 self.EnsureCaretVisible()
332 #self.ScrollToColumn(0)
333
334 def writeOut(self, text):
335 """Replacement for stdout."""
336 self.write(text)
337
338 def writeErr(self, text):
339 """Replacement for stderr."""
340 self.write(text)
341
342 def CanCut(self):
343 """Return true if text is selected and can be cut."""
344 return self.GetSelectionStart() != self.GetSelectionEnd()
345
346 def CanCopy(self):
347 """Return true if text is selected and can be copied."""
348 return self.GetSelectionStart() != self.GetSelectionEnd()
349