##// END OF EJS Templates
Get matching parenthesis to display well.
Gael Varoquaux -
Show More
@@ -1,407 +1,407 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 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
44 'bracegood' : 'fore:#00AA00,back:#000000,bold',
45 'bracebad' : 'fore:#FF0000,back:#000000,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 title = self.title_pat.split(text)
137 137 if len(title)>1:
138 138 self.title = title[-2]
139 139
140 140 text = self.title_pat.sub('', text)
141 141 segments = self.color_pat.split(text)
142 142 segment = segments.pop(0)
143 143 self.GotoPos(self.GetLength())
144 144 self.StartStyling(self.GetLength(), 0xFF)
145 145 try:
146 146 self.AppendText(segment)
147 147 except UnicodeDecodeError:
148 148 # XXX: Do I really want to skip the exception?
149 149 pass
150 150
151 151 if segments:
152 152 for ansi_tag, text in zip(segments[::2], segments[1::2]):
153 153 self.StartStyling(self.GetLength(), 0xFF)
154 154 try:
155 155 self.AppendText(text)
156 156 except UnicodeDecodeError:
157 157 # XXX: Do I really want to skip the exception?
158 158 pass
159 159
160 160 if ansi_tag not in self.ANSI_STYLES:
161 161 style = 0
162 162 else:
163 163 style = self.ANSI_STYLES[ansi_tag][0]
164 164
165 165 self.SetStyling(len(text), style)
166 166
167 167 self.GotoPos(self.GetLength())
168 168 if refresh:
169 169 wx.Yield()
170 170
171 171
172 172 def new_prompt(self, prompt):
173 173 """ Prints a prompt at start of line, and move the start of the
174 174 current block there.
175 175
176 176 The prompt can be given with ascii escape sequences.
177 177 """
178 178 self.write(prompt)
179 179 # now we update our cursor giving end of prompt
180 180 self.current_prompt_pos = self.GetLength()
181 181 self.current_prompt_line = self.GetCurrentLine()
182 182 wx.Yield()
183 183 self.EnsureCaretVisible()
184 184
185 185
186 186 def scroll_to_bottom(self):
187 187 maxrange = self.GetScrollRange(wx.VERTICAL)
188 188 self.ScrollLines(maxrange)
189 189
190 190
191 191 def pop_completion(self, possibilities, offset=0):
192 192 """ Pops up an autocompletion menu. Offset is the offset
193 193 in characters of the position at which the menu should
194 194 appear, relativ to the cursor.
195 195 """
196 196 self.AutoCompSetIgnoreCase(False)
197 197 self.AutoCompSetAutoHide(False)
198 198 self.AutoCompSetMaxHeight(len(possibilities))
199 199 self.AutoCompShow(offset, " ".join(possibilities))
200 200
201 201
202 202 def get_line_width(self):
203 203 """ Return the width of the line in characters.
204 204 """
205 205 return self.GetSize()[0]/self.GetCharWidth()
206 206
207 207
208 208 #--------------------------------------------------------------------------
209 209 # Private API
210 210 #--------------------------------------------------------------------------
211 211
212 212 def _apply_style(self):
213 213 """ Applies the colors for the different text elements and the
214 214 carret.
215 215 """
216 216 self.SetCaretForeground(self.carret_color)
217 217
218 218 #self.StyleClearAll()
219 219 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
220 220 "fore:#FF0000,back:#0000FF,bold")
221 221 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
222 222 "fore:#000000,back:#FF0000,bold")
223 223
224 224 for style in self.ANSI_STYLES.values():
225 225 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
226 226
227 227
228 228 def _configure_scintilla(self):
229 229 self.SetEOLMode(stc.STC_EOL_LF)
230 230
231 231 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
232 232 # the widget
233 233 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
234 234 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
235 235 # Also allow Ctrl Shift "=" for poor non US keyboard users.
236 236 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
237 237 stc.STC_CMD_ZOOMIN)
238 238
239 239 # Keys: we need to clear some of the keys the that don't play
240 240 # well with a console.
241 241 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
242 242 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
243 243 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
244 244
245 245
246 246 self.SetEOLMode(stc.STC_EOL_CRLF)
247 247 self.SetWrapMode(stc.STC_WRAP_CHAR)
248 248 self.SetWrapMode(stc.STC_WRAP_WORD)
249 249 self.SetBufferedDraw(True)
250 250 self.SetUseAntiAliasing(True)
251 251 self.SetLayoutCache(stc.STC_CACHE_PAGE)
252 252 self.SetUndoCollection(False)
253 253 self.SetUseTabs(True)
254 254 self.SetIndent(4)
255 255 self.SetTabWidth(4)
256 256
257 257 self.EnsureCaretVisible()
258 258 # we don't want scintilla's autocompletion to choose
259 259 # automaticaly out of a single choice list, as we pop it up
260 260 # automaticaly
261 261 self.AutoCompSetChooseSingle(False)
262 262 self.AutoCompSetMaxHeight(10)
263 263
264 264 self.SetMargins(3, 3) #text is moved away from border with 3px
265 265 # Suppressing Scintilla margins
266 266 self.SetMarginWidth(0, 0)
267 267 self.SetMarginWidth(1, 0)
268 268 self.SetMarginWidth(2, 0)
269 269
270 270 self._apply_style()
271 271
272 272 # Xterm escape sequences
273 273 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
274 274 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
275 275
276 276 #self.SetEdgeMode(stc.STC_EDGE_LINE)
277 277 #self.SetEdgeColumn(80)
278 278
279 279 # styles
280 280 p = self.style
281 281 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
282 282 self.StyleClearAll()
283 283 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
284 284 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
285 285 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
286 286
287 287 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
288 288 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
289 289 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
290 290 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
291 291 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
292 292 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
293 293 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
294 294 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
295 295 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
296 296 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
297 297 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
298 298 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
299 299 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
300 300 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
301 301
302 302
303 303 def _on_key_down(self, event, skip=True):
304 304 """ Key press callback used for correcting behavior for
305 305 console-like interfaces: the cursor is constraint to be after
306 306 the last prompt.
307 307
308 308 Return True if event as been catched.
309 309 """
310 310 catched = True
311 311 # Intercept some specific keys.
312 312 if event.KeyCode == ord('L') and event.ControlDown() :
313 313 self.scroll_to_bottom()
314 314 elif event.KeyCode == ord('K') and event.ControlDown() :
315 315 self.input_buffer = ''
316 316 elif event.KeyCode == wx.WXK_PAGEUP:
317 317 self.ScrollPages(-1)
318 318 elif event.KeyCode == wx.WXK_PAGEDOWN:
319 319 self.ScrollPages(1)
320 320 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
321 321 self.ScrollLines(-1)
322 322 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
323 323 self.ScrollLines(1)
324 324 else:
325 325 catched = False
326 326
327 327 if self.AutoCompActive():
328 328 event.Skip()
329 329 else:
330 330 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
331 331 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
332 332 catched = True
333 333 self.CallTipCancel()
334 334 self.write('\n', refresh=False)
335 335 # Under windows scintilla seems to be doing funny stuff to the
336 336 # line returns here, but the getter for input_buffer filters
337 337 # this out.
338 338 if sys.platform == 'win32':
339 339 self.input_buffer = self.input_buffer
340 340 self._on_enter()
341 341
342 342 elif event.KeyCode == wx.WXK_HOME:
343 343 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
344 344 self.GotoPos(self.current_prompt_pos)
345 345 catched = True
346 346
347 347 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
348 348 # FIXME: This behavior is not ideal: if the selection
349 349 # is already started, it will jump.
350 350 self.SetSelectionStart(self.current_prompt_pos)
351 351 self.SetSelectionEnd(self.GetCurrentPos())
352 352 catched = True
353 353
354 354 elif event.KeyCode == wx.WXK_UP:
355 355 if self.GetCurrentLine() > self.current_prompt_line:
356 356 if self.GetCurrentLine() == self.current_prompt_line + 1 \
357 357 and self.GetColumn(self.GetCurrentPos()) < \
358 358 self.GetColumn(self.current_prompt_pos):
359 359 self.GotoPos(self.current_prompt_pos)
360 360 else:
361 361 event.Skip()
362 362 catched = True
363 363
364 364 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
365 365 if self.GetCurrentPos() > self.current_prompt_pos:
366 366 event.Skip()
367 367 catched = True
368 368
369 369 if skip and not catched:
370 370 # Put the cursor back in the edit region
371 371 if self.GetCurrentPos() < self.current_prompt_pos:
372 372 self.GotoPos(self.current_prompt_pos)
373 373 else:
374 374 event.Skip()
375 375
376 376 return catched
377 377
378 378
379 379 def _on_key_up(self, event, skip=True):
380 380 """ If cursor is outside the editing region, put it back.
381 381 """
382 382 event.Skip()
383 383 if self.GetCurrentPos() < self.current_prompt_pos:
384 384 self.GotoPos(self.current_prompt_pos)
385 385
386 386
387 387
388 388 if __name__ == '__main__':
389 389 # Some simple code to test the console widget.
390 390 class MainWindow(wx.Frame):
391 391 def __init__(self, parent, id, title):
392 392 wx.Frame.__init__(self, parent, id, title, size=(300,250))
393 393 self._sizer = wx.BoxSizer(wx.VERTICAL)
394 394 self.console_widget = ConsoleWidget(self)
395 395 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
396 396 self.SetSizer(self._sizer)
397 397 self.SetAutoLayout(1)
398 398 self.Show(True)
399 399
400 400 app = wx.PySimpleApp()
401 401 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
402 402 w.SetSize((780, 460))
403 403 w.Show()
404 404
405 405 app.MainLoop()
406 406
407 407
General Comments 0
You need to be logged in to leave comments. Login now