##// END OF EJS Templates
Spelling
gvaroquaux -
Show More
@@ -1,415 +1,415 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import sys
27 27 LINESEP = '\n'
28 28 if sys.platform == 'win32':
29 29 LINESEP = '\n\r'
30 30
31 31 import re
32 32
33 33 # FIXME: Need to provide an API for non user-generated display on the
34 34 # screen: this should not be editable by the user.
35 35
36 36 _DEFAULT_SIZE = 10
37 37
38 38 _DEFAULT_STYLE = {
39 39 'stdout' : 'fore:#0000FF',
40 40 'stderr' : 'fore:#007f00',
41 41 'trace' : 'fore:#FF0000',
42 42
43 43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
46 46
47 47 # properties for the various Python lexer styles
48 48 'comment' : 'fore:#007F00',
49 49 'number' : 'fore:#007F7F',
50 50 'string' : 'fore:#7F007F,italic',
51 51 'char' : 'fore:#7F007F,italic',
52 52 'keyword' : 'fore:#00007F,bold',
53 53 'triple' : 'fore:#7F0000',
54 54 'tripledouble' : 'fore:#7F0000',
55 55 'class' : 'fore:#0000FF,bold,underline',
56 56 'def' : 'fore:#007F7F,bold',
57 57 'operator' : 'bold'
58 58 }
59 59
60 60 # new style numbers
61 61 _STDOUT_STYLE = 15
62 62 _STDERR_STYLE = 16
63 63 _TRACE_STYLE = 17
64 64
65 65
66 66 # system colors
67 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 68
69 69 #-------------------------------------------------------------------------------
70 70 # The console widget class
71 71 #-------------------------------------------------------------------------------
72 72 class ConsoleWidget(editwindow.EditWindow):
73 73 """ Specialized styled text control view for console-like workflow.
74 74
75 75 This widget is mainly interested in dealing with the prompt and
76 76 keeping the cursor inside the editing line.
77 77 """
78 78
79 79 # This is where the title captured from the ANSI escape sequences are
80 80 # stored.
81 81 title = 'Console'
82 82
83 83 # The buffer being edited.
84 84 def _set_input_buffer(self, string):
85 85 self.SetSelection(self.current_prompt_pos, self.GetLength())
86 86 self.ReplaceSelection(string)
87 87 self.GotoPos(self.GetLength())
88 88
89 89 def _get_input_buffer(self):
90 90 """ Returns the text in current edit buffer.
91 91 """
92 92 input_buffer = self.GetTextRange(self.current_prompt_pos,
93 93 self.GetLength())
94 94 input_buffer = input_buffer.replace(LINESEP, '\n')
95 95 return input_buffer
96 96
97 97 input_buffer = property(_get_input_buffer, _set_input_buffer)
98 98
99 99 style = _DEFAULT_STYLE.copy()
100 100
101 101 # Translation table from ANSI escape sequences to color. Override
102 102 # this to specify your colors.
103 103 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
104 104 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
105 105 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
106 106 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
107 107 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
108 108 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
109 109 '1;34': [12, 'LIGHT BLUE'], '1;35':
110 110 [13, 'MEDIUM VIOLET RED'],
111 111 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
112 112
113 113 # The color of the carret (call _apply_style() after setting)
114 114 carret_color = 'BLACK'
115 115
116 116 #--------------------------------------------------------------------------
117 117 # Public API
118 118 #--------------------------------------------------------------------------
119 119
120 120 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
121 121 size=wx.DefaultSize, style=0, ):
122 122 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
123 123 self._configure_scintilla()
124 124
125 125 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
126 126 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
127 127
128 128
129 129 def write(self, text, refresh=True):
130 130 """ Write given text to buffer, while translating the ansi escape
131 131 sequences.
132 132 """
133 133 # XXX: do not put print statements to sys.stdout/sys.stderr in
134 134 # this method, the print statements will call this method, as
135 135 # you will end up with an infinit loop
136 136 if self.debug:
137 137 print >>sys.__stderr__, text
138 138 title = self.title_pat.split(text)
139 139 if len(title)>1:
140 140 self.title = title[-2]
141 141
142 142 text = self.title_pat.sub('', text)
143 143 segments = self.color_pat.split(text)
144 144 segment = segments.pop(0)
145 145 self.GotoPos(self.GetLength())
146 146 self.StartStyling(self.GetLength(), 0xFF)
147 147 try:
148 148 self.AppendText(segment)
149 149 except UnicodeDecodeError:
150 150 # XXX: Do I really want to skip the exception?
151 151 pass
152 152
153 153 if segments:
154 154 for ansi_tag, text in zip(segments[::2], segments[1::2]):
155 155 self.StartStyling(self.GetLength(), 0xFF)
156 156 try:
157 157 self.AppendText(text)
158 158 except UnicodeDecodeError:
159 159 # XXX: Do I really want to skip the exception?
160 160 pass
161 161
162 162 if ansi_tag not in self.ANSI_STYLES:
163 163 style = 0
164 164 else:
165 165 style = self.ANSI_STYLES[ansi_tag][0]
166 166
167 167 self.SetStyling(len(text), style)
168 168
169 169 self.GotoPos(self.GetLength())
170 170 if refresh:
171 171 wx.Yield()
172 172
173 173
174 174 def new_prompt(self, prompt):
175 175 """ Prints a prompt at start of line, and move the start of the
176 176 current block there.
177 177
178 The prompt can be give with ascii escape sequences.
178 The prompt can be given with ascii escape sequences.
179 179 """
180 180 self.write(prompt)
181 181 # now we update our cursor giving end of prompt
182 182 self.current_prompt_pos = self.GetLength()
183 183 self.current_prompt_line = self.GetCurrentLine()
184 184 wx.Yield()
185 185 self.EnsureCaretVisible()
186 186
187 187
188 188 def scroll_to_bottom(self):
189 189 maxrange = self.GetScrollRange(wx.VERTICAL)
190 190 self.ScrollLines(maxrange)
191 191
192 192
193 193 def pop_completion(self, possibilities, offset=0):
194 194 """ Pops up an autocompletion menu. Offset is the offset
195 195 in characters of the position at which the menu should
196 196 appear, relativ to the cursor.
197 197 """
198 198 self.AutoCompSetIgnoreCase(False)
199 199 self.AutoCompSetAutoHide(False)
200 200 self.AutoCompSetMaxHeight(len(possibilities))
201 201 self.AutoCompShow(offset, " ".join(possibilities))
202 202
203 203
204 204 def get_line_width(self):
205 205 """ Return the width of the line in characters.
206 206 """
207 207 return self.GetSize()[0]/self.GetCharWidth()
208 208
209 209
210 210 #--------------------------------------------------------------------------
211 211 # Private API
212 212 #--------------------------------------------------------------------------
213 213
214 214 def _apply_style(self):
215 215 """ Applies the colors for the different text elements and the
216 216 carret.
217 217 """
218 218 self.SetCaretForeground(self.carret_color)
219 219
220 220 #self.StyleClearAll()
221 221 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
222 222 "fore:#FF0000,back:#0000FF,bold")
223 223 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
224 224 "fore:#000000,back:#FF0000,bold")
225 225
226 226 for style in self.ANSI_STYLES.values():
227 227 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
228 228
229 229
230 230 def _configure_scintilla(self):
231 231 self.SetEOLMode(stc.STC_EOL_LF)
232 232
233 233 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
234 234 # the widget
235 235 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
236 236 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
237 237 # Also allow Ctrl Shift "=" for poor non US keyboard users.
238 238 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
239 239 stc.STC_CMD_ZOOMIN)
240 240
241 241 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
242 242 # stc.STC_CMD_PAGEUP)
243 243
244 244 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
245 245 # stc.STC_CMD_PAGEDOWN)
246 246
247 247 # Keys: we need to clear some of the keys the that don't play
248 248 # well with a console.
249 249 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
250 250 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
251 251 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
252 252
253 253
254 254 self.SetEOLMode(stc.STC_EOL_CRLF)
255 255 self.SetWrapMode(stc.STC_WRAP_CHAR)
256 256 self.SetWrapMode(stc.STC_WRAP_WORD)
257 257 self.SetBufferedDraw(True)
258 258 self.SetUseAntiAliasing(True)
259 259 self.SetLayoutCache(stc.STC_CACHE_PAGE)
260 260 self.SetUndoCollection(False)
261 261 self.SetUseTabs(True)
262 262 self.SetIndent(4)
263 263 self.SetTabWidth(4)
264 264
265 265 self.EnsureCaretVisible()
266 266 # we don't want scintilla's autocompletion to choose
267 267 # automaticaly out of a single choice list, as we pop it up
268 268 # automaticaly
269 269 self.AutoCompSetChooseSingle(False)
270 270 self.AutoCompSetMaxHeight(10)
271 271
272 272 self.SetMargins(3, 3) #text is moved away from border with 3px
273 273 # Suppressing Scintilla margins
274 274 self.SetMarginWidth(0, 0)
275 275 self.SetMarginWidth(1, 0)
276 276 self.SetMarginWidth(2, 0)
277 277
278 278 self._apply_style()
279 279
280 280 # Xterm escape sequences
281 281 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
282 282 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
283 283
284 284 #self.SetEdgeMode(stc.STC_EDGE_LINE)
285 285 #self.SetEdgeColumn(80)
286 286
287 287 # styles
288 288 p = self.style
289 289 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
290 290 self.StyleClearAll()
291 291 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
292 292 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
293 293 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
294 294
295 295 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
296 296 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
297 297 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
298 298 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
299 299 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
300 300 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
301 301 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
302 302 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
303 303 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
304 304 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
305 305 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
306 306 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
307 307 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
308 308 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
309 309
310 310
311 311 def _on_key_down(self, event, skip=True):
312 312 """ Key press callback used for correcting behavior for
313 313 console-like interfaces: the cursor is constraint to be after
314 314 the last prompt.
315 315
316 316 Return True if event as been catched.
317 317 """
318 318 catched = True
319 319 # Intercept some specific keys.
320 320 if event.KeyCode == ord('L') and event.ControlDown() :
321 321 self.scroll_to_bottom()
322 322 elif event.KeyCode == ord('K') and event.ControlDown() :
323 323 self.input_buffer = ''
324 324 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
325 325 self.ScrollPages(-1)
326 326 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
327 327 self.ScrollPages(1)
328 328 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
329 329 self.ScrollLines(-1)
330 330 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
331 331 self.ScrollLines(1)
332 332 else:
333 333 catched = False
334 334
335 335 if self.AutoCompActive():
336 336 event.Skip()
337 337 else:
338 338 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
339 339 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
340 340 catched = True
341 341 self.CallTipCancel()
342 342 self.write('\n')
343 343 # Under windows scintilla seems to be doing funny stuff to the
344 344 # line returns here, but the getter for input_buffer filters
345 345 # this out.
346 346 if sys.platform == 'win32':
347 347 self.input_buffer = self.input_buffer
348 348 self._on_enter()
349 349
350 350 elif event.KeyCode == wx.WXK_HOME:
351 351 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
352 352 self.GotoPos(self.current_prompt_pos)
353 353 catched = True
354 354
355 355 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
356 356 # FIXME: This behavior is not ideal: if the selection
357 357 # is already started, it will jump.
358 358 self.SetSelectionStart(self.current_prompt_pos)
359 359 self.SetSelectionEnd(self.GetCurrentPos())
360 360 catched = True
361 361
362 362 elif event.KeyCode == wx.WXK_UP:
363 363 if self.GetCurrentLine() > self.current_prompt_line:
364 364 if self.GetCurrentLine() == self.current_prompt_line + 1 \
365 365 and self.GetColumn(self.GetCurrentPos()) < \
366 366 self.GetColumn(self.current_prompt_pos):
367 367 self.GotoPos(self.current_prompt_pos)
368 368 else:
369 369 event.Skip()
370 370 catched = True
371 371
372 372 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
373 373 if self.GetCurrentPos() > self.current_prompt_pos:
374 374 event.Skip()
375 375 catched = True
376 376
377 377 if skip and not catched:
378 378 # Put the cursor back in the edit region
379 379 if self.GetCurrentPos() < self.current_prompt_pos:
380 380 self.GotoPos(self.current_prompt_pos)
381 381 else:
382 382 event.Skip()
383 383
384 384 return catched
385 385
386 386
387 387 def _on_key_up(self, event, skip=True):
388 388 """ If cursor is outside the editing region, put it back.
389 389 """
390 390 event.Skip()
391 391 if self.GetCurrentPos() < self.current_prompt_pos:
392 392 self.GotoPos(self.current_prompt_pos)
393 393
394 394
395 395
396 396 if __name__ == '__main__':
397 397 # Some simple code to test the console widget.
398 398 class MainWindow(wx.Frame):
399 399 def __init__(self, parent, id, title):
400 400 wx.Frame.__init__(self, parent, id, title, size=(300,250))
401 401 self._sizer = wx.BoxSizer(wx.VERTICAL)
402 402 self.console_widget = ConsoleWidget(self)
403 403 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
404 404 self.SetSizer(self._sizer)
405 405 self.SetAutoLayout(1)
406 406 self.Show(True)
407 407
408 408 app = wx.PySimpleApp()
409 409 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
410 410 w.SetSize((780, 460))
411 411 w.Show()
412 412
413 413 app.MainLoop()
414 414
415 415
General Comments 0
You need to be logged in to leave comments. Login now