2 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
4 __date__
= "July 1, 2001"
5 __version__
= "$Revision$"[11:-2]
7 from wxPython
.wx
import *
8 from wxPython
.stc
import *
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',
25 faces
= { 'times' : 'Times',
28 'other' : 'new century schoolbook',
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.
47 # Configure various defaults and user preferences.
49 # Assign handlers for keyboard events.
50 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
51 EVT_CHAR(self
, self
.OnChar
)
54 """Configure editor based on user preferences."""
55 self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
)
56 self
.SetMarginWidth(1, 40)
58 self
.SetLexer(wxSTC_LEX_PYTHON
)
59 self
.SetKeyWords(0, ' '.join(keyword
.kwlist
))
62 self
.SetViewWhiteSpace(0)
65 # Do we want to automatically pop up command completion options?
67 self
.autoCompleteCaseInsensitive
= 1
68 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
69 # De we want to automatically pop up command argument help?
71 self
.CallTipSetBackground(wxColour(255, 255, 232))
73 def setStyles(self
, faces
):
74 """Configure font size, typeface and color for lexer."""
77 self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d" % faces
)
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")
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
)
103 def OnKeyDown(self
, event
):
104 """Key down event handler.
106 The main goal here is to not allow modifications to previous
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():
115 # Return is used to submit a command to the interpreter.
116 elif key
== WXK_RETURN
:
117 if self
.CallTipActive
: self
.CallTipCancel()
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
)
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
):
130 # Don't backspace over the latest prompt.
131 elif key
== WXK_BACK
:
132 if currpos
> stoppos
:
134 # Only allow these keys after the latest prompt.
135 elif key
in (WXK_TAB
, WXK_DELETE
):
136 if currpos
>= stoppos
:
138 # Don't toggle between insert mode and overwrite mode.
139 elif key
== WXK_INSERT
:
144 def OnChar(self
, event
):
145 """Keypress event handler.
147 The main goal here is to not allow modifications to previous
150 key
= event
.KeyCode()
151 currpos
= self
.GetCurrentPos()
152 stoppos
= self
.promptPos
[1]
153 if currpos
>= stoppos
:
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
) + '.'
160 if self
.autoComplete
: self
.autoCompleteShow(command
)
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
) + '('
169 if self
.autoCallTip
: self
.autoCallTipShow(command
)
171 # Allow the normal event handling to take place.
176 def setStatusText(self
, text
):
177 """Display status information."""
179 # This method will most likely be replaced by the enclosing app
180 # to do something more interesting, like write to a status bar.
183 def autoCompleteShow(self
, command
):
184 """Display auto-completion popup list."""
185 list = self
.getAutoCompleteList(command
)
187 options
= ' '.join(list)
189 self
.AutoCompShow(offset
, options
)
191 def getAutoCompleteList(self
, command
):
192 """Return list of auto-completion options for command."""
194 # This method needs to be replaced by the enclosing app
195 # to get the proper auto complete list from the interpreter.
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
)
203 offset
= self
.GetCurrentPos()
204 self
.CallTipShow(offset
, tip
)
206 def getCallTip(self
, command
):
207 """Return arguments and docstring for command."""
209 # This method needs to be replaced by the enclosing app
210 # to get the proper auto complete list from the interpreter.
213 def processLine(self
):
214 """Process the line of text at which the user hit Enter."""
216 # The user hit ENTER and we need to decide what to do. They could be
217 # sitting on any line in the editor.
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
:
231 # Otherwise, replace the last line with the new line.
233 # If the new line contains a command (even an invalid one).
235 startpos
= self
.PositionFromLine(endline
)
236 self
.SetSelection(startpos
, endpos
)
237 self
.ReplaceSelection('')
240 # Otherwise, put the cursor back where we started.
242 self
.SetCurrentPos(thepos
)
243 self
.SetAnchor(thepos
)
245 def getCommand(self
, text
):
246 """Extract a command from text which may include a shell prompt.
248 The command may not necessarily be valid Python syntax.
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.
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
:]
268 def push(self
, command
):
269 """Start a new line, send command to the shell, display a prompt."""
271 self
.more
= self
.shellPush(command
)
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()
279 """Delete all text from the editor."""
283 """Display appropriate prompt for the context, either ps1 or ps2.
285 If this is a continuation line, autoindent as necessary.
288 prompt
= str(sys
.ps2
)
290 prompt
= str(sys
.ps1
)
291 pos
= self
.GetCurLine()[1]
292 if pos
> 0: self
.write('\n')
293 self
.promptPos
[0] = self
.GetCurrentPos()
295 self
.promptPos
[1] = self
.GetCurrentPos()
296 # XXX Add some autoindent magic here if more.
298 self
.write('\t') # Temporary hack indentation.
299 self
.EnsureCaretVisible()
300 self
.ScrollToColumn(0)
303 """Replacement for stdin."""
304 prompt
= 'Please enter your response:'
305 dialog
= wxTextEntryDialog(None, prompt
, \
306 'Input Dialog (Standard)', '')
308 if dialog
.ShowModal() == wxID_OK
:
309 text
= dialog
.GetValue()
310 self
.write(text
+ '\n')
316 def readRaw(self
, prompt
='Please enter your response:'):
317 """Replacement for raw_input."""
318 dialog
= wxTextEntryDialog(None, prompt
, \
319 'Input Dialog (Raw)', '')
321 if dialog
.ShowModal() == wxID_OK
:
322 text
= dialog
.GetValue()
328 def write(self
, text
):
329 """Display text in the editor."""
331 self
.EnsureCaretVisible()
332 #self.ScrollToColumn(0)
334 def writeOut(self
, text
):
335 """Replacement for stdout."""
338 def writeErr(self
, text
):
339 """Replacement for stderr."""
343 """Return true if text is selected and can be cut."""
344 return self
.GetSelectionStart() != self
.GetSelectionEnd()
347 """Return true if text is selected and can be copied."""
348 return self
.GetSelectionStart() != self
.GetSelectionEnd()