##// END OF EJS Templates
Docstrings tweaks.
gvaroquaux -
Show More
@@ -1,367 +1,370 b''
1 1 # encoding: utf-8
2 2 """
3 A Wx widget that deals with prompts and provides an edit buffer
3 A Wx widget to act as a console and input commands.
4
5 This widget deals with prompts and provides an edit buffer
4 6 restricted to after the last prompt.
5 7 """
6 8
7 9 __docformat__ = "restructuredtext en"
8 10
9 11 #-------------------------------------------------------------------------------
10 12 # Copyright (C) 2008 The IPython Development Team
11 13 #
12 14 # Distributed under the terms of the BSD License. The full license is
13 15 # in the file COPYING, distributed as part of this software.
14 16 #-------------------------------------------------------------------------------
15 17
16 18 #-------------------------------------------------------------------------------
17 19 # Imports
18 20 #-------------------------------------------------------------------------------
19 21
20 22 import wx
21 23 import wx.stc as stc
22 24
23 25 import re
24 26
25 27 # FIXME: Need to provide an API for non user-generated display on the
26 28 # screen: this should not be editable by the user.
27 29
28 30 #-------------------------------------------------------------------------------
29 31 # The console widget class
30 32 #-------------------------------------------------------------------------------
31 33 class ConsoleWidget(stc.StyledTextCtrl):
32 34 """ Specialized styled text control view for console-like workflow.
35
33 36 This widget is mainly interested in dealing with the prompt and
34 37 keeping the cursor inside the editing line.
35 38 """
36 39
37 40 # Translation table from ANSI escape sequences to color. Override
38 41 # this to specify your colors.
39 42 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
40 43 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
41 44 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
42 45 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
43 46 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
44 47 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
45 48 '1;34': [12, 'LIGHT BLUE'], '1;35':
46 49 [13, 'MEDIUM VIOLET RED'],
47 50 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
48 51
49 52 # The color of the carret (call _apply_style() after setting)
50 53 carret_color = 'BLACK'
51 54
52 55
53 56 #--------------------------------------------------------------------------
54 57 # Public API
55 58 #--------------------------------------------------------------------------
56 59
57 60 def write(self, text):
58 61 """ Write given text to buffer, while translating the ansi escape
59 62 sequences.
60 63 """
61 64 segments = self.color_pat.split(text)
62 65 segment = segments.pop(0)
63 66 self.StartStyling(self.GetLength(), 0xFF)
64 67 self.AppendText(segment)
65 68
66 69 if segments:
67 70 ansi_tags = self.color_pat.findall(text)
68 71
69 72 for tag in ansi_tags:
70 73 i = segments.index(tag)
71 74 self.StartStyling(self.GetLength(), 0xFF)
72 75 self.AppendText(segments[i+1])
73 76
74 77 if tag != '0':
75 78 self.SetStyling(len(segments[i+1]),
76 79 self.ANSI_STYLES[tag][0])
77 80
78 81 segments.pop(i)
79 82
80 83 self.GotoPos(self.GetLength())
81 84
82 85
83 86 def new_prompt(self, prompt):
84 87 """ Prints a prompt at start of line, and move the start of the
85 88 current block there.
86 89
87 90 The prompt can be give with ascii escape sequences.
88 91 """
89 92 self.write(prompt)
90 93 # now we update our cursor giving end of prompt
91 94 self.current_prompt_pos = self.GetLength()
92 95 self.current_prompt_line = self.GetCurrentLine()
93 96
94 97 autoindent = self.indent * ' '
95 98 autoindent = autoindent.replace(' ','\t')
96 99 self.write(autoindent)
97 100
98 101
99 102 def replace_current_edit_buffer(self, text):
100 103 """ Replace currently entered command line with given text.
101 104 """
102 105 self.SetSelection(self.current_prompt_pos, self.GetLength())
103 106 self.ReplaceSelection(text)
104 107 self.GotoPos(self.GetLength())
105 108
106 109
107 110 def get_current_edit_buffer(self):
108 111 """ Returns the text in current edit buffer.
109 112 """
110 113 return self.GetTextRange(self.current_prompt_pos,
111 114 self.GetLength())
112 115
113 116
114 117 #--------------------------------------------------------------------------
115 118 # Private API
116 119 #--------------------------------------------------------------------------
117 120
118 121 def __init__(self, parent, pos=wx.DefaultPosition, ID=-1,
119 122 size=wx.DefaultSize, style=0,
120 123 autocomplete_mode='IPYTHON'):
121 124 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
122 125 'IPYTHON' show autocompletion the ipython way
123 126 'STC" show it scintilla text control way
124 127 """
125 128 stc.StyledTextCtrl.__init__(self, parent, ID, pos, size, style)
126 129
127 130 #------ Scintilla configuration -----------------------------------
128 131
129 132 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
130 133 # the widget
131 134 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
132 135 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
133 136 # Also allow Ctrl Shift "=" for poor non US keyboard users.
134 137 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
135 138 stc.STC_CMD_ZOOMIN)
136 139
137 140 self.SetEOLMode(stc.STC_EOL_CRLF)
138 141 self.SetWrapMode(stc.STC_WRAP_CHAR)
139 142 self.SetWrapMode(stc.STC_WRAP_WORD)
140 143 self.SetBufferedDraw(True)
141 144 self.SetUseAntiAliasing(True)
142 145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
143 146 self.SetUndoCollection(False)
144 147 self.SetUseTabs(True)
145 148 self.SetIndent(4)
146 149 self.SetTabWidth(4)
147 150
148 151 self.EnsureCaretVisible()
149 152
150 153 self.SetMargins(3, 3) #text is moved away from border with 3px
151 154 # Suppressing Scintilla margins
152 155 self.SetMarginWidth(0, 0)
153 156 self.SetMarginWidth(1, 0)
154 157 self.SetMarginWidth(2, 0)
155 158
156 159 self._apply_style()
157 160
158 161 self.indent = 0
159 162 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
160 163
161 164 # FIXME: we need to retrieve this from the interpreter.
162 165 self.prompt = \
163 166 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x026\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
164 167 self.new_prompt(self.prompt)
165 168
166 169 self.autocomplete_mode = autocomplete_mode
167 170
168 171 self.Bind(wx.EVT_KEY_DOWN, self._onKeypress)
169 172
170 173
171 174 def _apply_style(self):
172 175 """ Applies the colors for the different text elements and the
173 176 carret.
174 177 """
175 178 # FIXME: We need to do something for the fonts, but this is
176 179 # clearly not the right option.
177 180 #we define platform specific fonts
178 181 # if wx.Platform == '__WXMSW__':
179 182 # faces = { 'times': 'Times New Roman',
180 183 # 'mono' : 'Courier New',
181 184 # 'helv' : 'Arial',
182 185 # 'other': 'Comic Sans MS',
183 186 # 'size' : 10,
184 187 # 'size2': 8,
185 188 # }
186 189 # elif wx.Platform == '__WXMAC__':
187 190 # faces = { 'times': 'Times New Roman',
188 191 # 'mono' : 'Monaco',
189 192 # 'helv' : 'Arial',
190 193 # 'other': 'Comic Sans MS',
191 194 # 'size' : 10,
192 195 # 'size2': 8,
193 196 # }
194 197 # else:
195 198 # faces = { 'times': 'Times',
196 199 # 'mono' : 'Courier',
197 200 # 'helv' : 'Helvetica',
198 201 # 'other': 'new century schoolbook',
199 202 # 'size' : 10,
200 203 # 'size2': 8,
201 204 # }
202 205 # self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
203 206 # "fore:%s,back:%s,size:%d,face:%s"
204 207 # % (self.ANSI_STYLES['0;30'][1],
205 208 # self.background_color,
206 209 # faces['size'], faces['mono']))
207 210
208 211 self.SetCaretForeground(self.carret_color)
209 212
210 213 self.StyleClearAll()
211 214 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
212 215 "fore:#FF0000,back:#0000FF,bold")
213 216 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
214 217 "fore:#000000,back:#FF0000,bold")
215 218
216 219 for style in self.ANSI_STYLES.values():
217 220 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
218 221
219 222
220 223 def removeFromTo(self, from_pos, to_pos):
221 224 if from_pos < to_pos:
222 225 self.SetSelection(from_pos, to_pos)
223 226 self.DeleteBack()
224 227
225 228
226 229 def selectFromTo(self, from_pos, to_pos):
227 230 self.SetSelectionStart(from_pos)
228 231 self.SetSelectionEnd(to_pos)
229 232
230 233
231 234 def writeCompletion(self, possibilities):
232 235 if self.autocomplete_mode == 'IPYTHON':
233 236 max_len = len(max(possibilities, key=len))
234 237 max_symbol = ' '*max_len
235 238
236 239 #now we check how much symbol we can put on a line...
237 240 test_buffer = max_symbol + ' '*4
238 241
239 242 allowed_symbols = 80/len(test_buffer)
240 243 if allowed_symbols == 0:
241 244 allowed_symbols = 1
242 245
243 246 pos = 1
244 247 buf = ''
245 248 for symbol in possibilities:
246 249 #buf += symbol+'\n'#*spaces)
247 250 if pos < allowed_symbols:
248 251 spaces = max_len - len(symbol) + 4
249 252 buf += symbol+' '*spaces
250 253 pos += 1
251 254 else:
252 255 buf += symbol+'\n'
253 256 pos = 1
254 257 self.write(buf)
255 258 else:
256 259 possibilities.sort() # Python sorts are case sensitive
257 260 self.AutoCompSetIgnoreCase(False)
258 261 self.AutoCompSetAutoHide(False)
259 262 #let compute the length ot last word
260 263 splitter = [' ', '(', '[', '{']
261 264 last_word = self.get_current_edit_buffer()
262 265 for breaker in splitter:
263 266 last_word = last_word.split(breaker)[-1]
264 267 self.AutoCompShow(len(last_word), " ".join(possibilities))
265 268
266 269
267 270 def _onKeypress(self, event, skip=True):
268 271 """ Key press callback used for correcting behavior for
269 272 console-like interfaces: the cursor is constraint to be after
270 273 the last prompt.
271 274
272 275 Return True if event as been catched.
273 276 """
274 277 catched = False
275 278 if self.AutoCompActive():
276 279 event.Skip()
277 280 else:
278 281 if event.GetKeyCode() == wx.WXK_HOME:
279 282 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
280 283 self.GotoPos(self.current_prompt_pos)
281 284 catched = True
282 285
283 286 elif event.Modifiers == wx.MOD_SHIFT:
284 287 self.selectFromTo(self.current_prompt_pos,
285 288 self.GetCurrentPos())
286 289 catched = True
287 290
288 291 elif event.GetKeyCode() == wx.WXK_UP:
289 292 if self.GetCurrentLine() > self.current_prompt_line:
290 293 if self.GetCurrentLine() == self.current_prompt_line + 1 \
291 294 and self.GetColumn(self.GetCurrentPos()) < \
292 295 self.GetColumn(self.current_prompt_pos):
293 296 self.GotoPos(self.current_prompt_pos)
294 297 else:
295 298 event.Skip()
296 299 catched = True
297 300
298 301 elif event.GetKeyCode() in (wx.WXK_LEFT, wx.WXK_BACK):
299 302 if self.GetCurrentPos() > self.current_prompt_pos:
300 303 event.Skip()
301 304 catched = True
302 305
303 306 if skip and not catched:
304 307 event.Skip()
305 308
306 309 if event.GetKeyCode() not in (wx.WXK_PAGEUP, wx.WXK_PAGEDOWN)\
307 310 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN,
308 311 wx.MOD_SHIFT):
309 312 # If cursor is outside the editing region, put it back.
310 313 if self.GetCurrentPos() < self.current_prompt_pos:
311 314 self.GotoPos(self.current_prompt_pos)
312 315
313 316 return catched
314 317
315 318
316 319 def OnUpdateUI(self, evt):
317 320 # check for matching braces
318 321 braceAtCaret = -1
319 322 braceOpposite = -1
320 323 charBefore = None
321 324 caretPos = self.GetCurrentPos()
322 325
323 326 if caretPos > 0:
324 327 charBefore = self.GetCharAt(caretPos - 1)
325 328 styleBefore = self.GetStyleAt(caretPos - 1)
326 329
327 330 # check before
328 331 if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
329 332 braceAtCaret = caretPos - 1
330 333
331 334 # check after
332 335 if braceAtCaret < 0:
333 336 charAfter = self.GetCharAt(caretPos)
334 337 styleAfter = self.GetStyleAt(caretPos)
335 338
336 339 if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
337 340 braceAtCaret = caretPos
338 341
339 342 if braceAtCaret >= 0:
340 343 braceOpposite = self.BraceMatch(braceAtCaret)
341 344
342 345 if braceAtCaret != -1 and braceOpposite == -1:
343 346 self.BraceBadLight(braceAtCaret)
344 347 else:
345 348 self.BraceHighlight(braceAtCaret, braceOpposite)
346 349
347 350
348 351 if __name__ == '__main__':
349 352 # Some simple code to test the console widget.
350 353 class MainWindow(wx.Frame):
351 354 def __init__(self, parent, id, title):
352 355 wx.Frame.__init__(self, parent, id, title, size=(300,250))
353 356 self._sizer = wx.BoxSizer(wx.VERTICAL)
354 357 self.console_widget = ConsoleWidget(self)
355 358 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
356 359 self.SetSizer(self._sizer)
357 360 self.SetAutoLayout(1)
358 361 self.Show(True)
359 362
360 363 app = wx.PySimpleApp()
361 364 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
362 365 w.SetSize((780, 460))
363 366 w.Show()
364 367
365 368 app.MainLoop()
366 369
367 370
General Comments 0
You need to be logged in to leave comments. Login now