##// END OF EJS Templates
Make pageup work.
Gael Varoquaux -
Show More
@@ -1,413 +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 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 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 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
240 # stc.STC_CMD_PAGEUP)
241
242 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
243 # stc.STC_CMD_PAGEDOWN)
244
245 239 # Keys: we need to clear some of the keys the that don't play
246 240 # well with a console.
247 241 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
248 242 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
249 243 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
250 244
251 245
252 246 self.SetEOLMode(stc.STC_EOL_CRLF)
253 247 self.SetWrapMode(stc.STC_WRAP_CHAR)
254 248 self.SetWrapMode(stc.STC_WRAP_WORD)
255 249 self.SetBufferedDraw(True)
256 250 self.SetUseAntiAliasing(True)
257 251 self.SetLayoutCache(stc.STC_CACHE_PAGE)
258 252 self.SetUndoCollection(False)
259 253 self.SetUseTabs(True)
260 254 self.SetIndent(4)
261 255 self.SetTabWidth(4)
262 256
263 257 self.EnsureCaretVisible()
264 258 # we don't want scintilla's autocompletion to choose
265 259 # automaticaly out of a single choice list, as we pop it up
266 260 # automaticaly
267 261 self.AutoCompSetChooseSingle(False)
268 262 self.AutoCompSetMaxHeight(10)
269 263
270 264 self.SetMargins(3, 3) #text is moved away from border with 3px
271 265 # Suppressing Scintilla margins
272 266 self.SetMarginWidth(0, 0)
273 267 self.SetMarginWidth(1, 0)
274 268 self.SetMarginWidth(2, 0)
275 269
276 270 self._apply_style()
277 271
278 272 # Xterm escape sequences
279 273 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
280 274 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
281 275
282 276 #self.SetEdgeMode(stc.STC_EDGE_LINE)
283 277 #self.SetEdgeColumn(80)
284 278
285 279 # styles
286 280 p = self.style
287 281 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
288 282 self.StyleClearAll()
289 283 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
290 284 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
291 285 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
292 286
293 287 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
294 288 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
295 289 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
296 290 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
297 291 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
298 292 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
299 293 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
300 294 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
301 295 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
302 296 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
303 297 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
304 298 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
305 299 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
306 300 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
307 301
308 302
309 303 def _on_key_down(self, event, skip=True):
310 304 """ Key press callback used for correcting behavior for
311 305 console-like interfaces: the cursor is constraint to be after
312 306 the last prompt.
313 307
314 308 Return True if event as been catched.
315 309 """
316 310 catched = True
317 311 # Intercept some specific keys.
318 312 if event.KeyCode == ord('L') and event.ControlDown() :
319 313 self.scroll_to_bottom()
320 314 elif event.KeyCode == ord('K') and event.ControlDown() :
321 315 self.input_buffer = ''
322 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
316 elif event.KeyCode == wx.WXK_PAGEUP:
323 317 self.ScrollPages(-1)
324 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
318 elif event.KeyCode == wx.WXK_PAGEDOWN:
325 319 self.ScrollPages(1)
326 320 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
327 321 self.ScrollLines(-1)
328 322 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
329 323 self.ScrollLines(1)
330 324 else:
331 325 catched = False
332 326
333 327 if self.AutoCompActive():
334 328 event.Skip()
335 329 else:
336 330 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
337 331 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
338 332 catched = True
339 333 self.CallTipCancel()
340 334 self.write('\n', refresh=False)
341 335 # Under windows scintilla seems to be doing funny stuff to the
342 336 # line returns here, but the getter for input_buffer filters
343 337 # this out.
344 338 if sys.platform == 'win32':
345 339 self.input_buffer = self.input_buffer
346 340 self._on_enter()
347 341
348 342 elif event.KeyCode == wx.WXK_HOME:
349 343 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
350 344 self.GotoPos(self.current_prompt_pos)
351 345 catched = True
352 346
353 347 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
354 348 # FIXME: This behavior is not ideal: if the selection
355 349 # is already started, it will jump.
356 350 self.SetSelectionStart(self.current_prompt_pos)
357 351 self.SetSelectionEnd(self.GetCurrentPos())
358 352 catched = True
359 353
360 354 elif event.KeyCode == wx.WXK_UP:
361 355 if self.GetCurrentLine() > self.current_prompt_line:
362 356 if self.GetCurrentLine() == self.current_prompt_line + 1 \
363 357 and self.GetColumn(self.GetCurrentPos()) < \
364 358 self.GetColumn(self.current_prompt_pos):
365 359 self.GotoPos(self.current_prompt_pos)
366 360 else:
367 361 event.Skip()
368 362 catched = True
369 363
370 364 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
371 365 if self.GetCurrentPos() > self.current_prompt_pos:
372 366 event.Skip()
373 367 catched = True
374 368
375 369 if skip and not catched:
376 370 # Put the cursor back in the edit region
377 371 if self.GetCurrentPos() < self.current_prompt_pos:
378 372 self.GotoPos(self.current_prompt_pos)
379 373 else:
380 374 event.Skip()
381 375
382 376 return catched
383 377
384 378
385 379 def _on_key_up(self, event, skip=True):
386 380 """ If cursor is outside the editing region, put it back.
387 381 """
388 382 event.Skip()
389 383 if self.GetCurrentPos() < self.current_prompt_pos:
390 384 self.GotoPos(self.current_prompt_pos)
391 385
392 386
393 387
394 388 if __name__ == '__main__':
395 389 # Some simple code to test the console widget.
396 390 class MainWindow(wx.Frame):
397 391 def __init__(self, parent, id, title):
398 392 wx.Frame.__init__(self, parent, id, title, size=(300,250))
399 393 self._sizer = wx.BoxSizer(wx.VERTICAL)
400 394 self.console_widget = ConsoleWidget(self)
401 395 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
402 396 self.SetSizer(self._sizer)
403 397 self.SetAutoLayout(1)
404 398 self.Show(True)
405 399
406 400 app = wx.PySimpleApp()
407 401 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
408 402 w.SetSize((780, 460))
409 403 w.Show()
410 404
411 405 app.MainLoop()
412 406
413 407
General Comments 0
You need to be logged in to leave comments. Login now