##// END OF EJS Templates
Added "ctr-K" to delete current buffer.
Gael Varoquaux -
Show More
@@ -1,411 +1,412 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
27 27 import re
28 28
29 29 # FIXME: Need to provide an API for non user-generated display on the
30 30 # screen: this should not be editable by the user.
31 31
32 32 if wx.Platform == '__WXMSW__':
33 33 _DEFAULT_SIZE = 80
34 34 else:
35 35 _DEFAULT_SIZE = 10
36 36
37 37 _DEFAULT_STYLE = {
38 38 'stdout' : 'fore:#0000FF',
39 39 'stderr' : 'fore:#007f00',
40 40 'trace' : 'fore:#FF0000',
41 41
42 42 'default' : 'size:%d' % _DEFAULT_SIZE,
43 43 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
44 44 'bracebad' : 'fore:#000000,back:#FF0000,bold',
45 45
46 46 # properties for the various Python lexer styles
47 47 'comment' : 'fore:#007F00',
48 48 'number' : 'fore:#007F7F',
49 49 'string' : 'fore:#7F007F,italic',
50 50 'char' : 'fore:#7F007F,italic',
51 51 'keyword' : 'fore:#00007F,bold',
52 52 'triple' : 'fore:#7F0000',
53 53 'tripledouble': 'fore:#7F0000',
54 54 'class' : 'fore:#0000FF,bold,underline',
55 55 'def' : 'fore:#007F7F,bold',
56 56 'operator' : 'bold',
57 57
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 #-------------------------------------------------------------------------------
67 67 # The console widget class
68 68 #-------------------------------------------------------------------------------
69 69 class ConsoleWidget(editwindow.EditWindow):
70 70 """ Specialized styled text control view for console-like workflow.
71 71
72 72 This widget is mainly interested in dealing with the prompt and
73 73 keeping the cursor inside the editing line.
74 74 """
75 75
76 76 title = 'Console'
77 77
78 78 style = _DEFAULT_STYLE.copy()
79 79
80 80 # Translation table from ANSI escape sequences to color. Override
81 81 # this to specify your colors.
82 82 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
83 83 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
84 84 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
85 85 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
86 86 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
87 87 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
88 88 '1;34': [12, 'LIGHT BLUE'], '1;35':
89 89 [13, 'MEDIUM VIOLET RED'],
90 90 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
91 91
92 92 # The color of the carret (call _apply_style() after setting)
93 93 carret_color = 'BLACK'
94 94
95 95
96 96 #--------------------------------------------------------------------------
97 97 # Public API
98 98 #--------------------------------------------------------------------------
99 99
100 100 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
101 101 size=wx.DefaultSize, style=0,
102 102 autocomplete_mode='IPYTHON'):
103 103 """ Autocomplete_mode: Can be 'IPYTHON' or 'STC'
104 104 'IPYTHON' show autocompletion the ipython way
105 105 'STC" show it scintilla text control way
106 106 """
107 107 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
108 108 self.configure_scintilla()
109 109
110 110 # FIXME: we need to retrieve this from the interpreter.
111 111 self.prompt = \
112 112 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
113 113 self.new_prompt(self.prompt % 1)
114 114
115 115 self.autocomplete_mode = autocomplete_mode
116 116
117 117 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
118 118 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
119 119
120 120
121 121 def configure_scintilla(self):
122 122 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
123 123 # the widget
124 124 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
125 125 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
126 126 # Also allow Ctrl Shift "=" for poor non US keyboard users.
127 127 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
128 128 stc.STC_CMD_ZOOMIN)
129 129
130 130 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
131 131 # stc.STC_CMD_PAGEUP)
132 132
133 133 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
134 134 # stc.STC_CMD_PAGEDOWN)
135 135
136 136 # Keys: we need to clear some of the keys the that don't play
137 137 # well with a console.
138 138 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
139 139 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
140 140 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
141 141
142 142
143 143 self.SetEOLMode(stc.STC_EOL_CRLF)
144 144 self.SetWrapMode(stc.STC_WRAP_CHAR)
145 145 self.SetWrapMode(stc.STC_WRAP_WORD)
146 146 self.SetBufferedDraw(True)
147 147 self.SetUseAntiAliasing(True)
148 148 self.SetLayoutCache(stc.STC_CACHE_PAGE)
149 149 self.SetUndoCollection(False)
150 150 self.SetUseTabs(True)
151 151 self.SetIndent(4)
152 152 self.SetTabWidth(4)
153 153
154 154 self.EnsureCaretVisible()
155 155
156 156 self.SetMargins(3, 3) #text is moved away from border with 3px
157 157 # Suppressing Scintilla margins
158 158 self.SetMarginWidth(0, 0)
159 159 self.SetMarginWidth(1, 0)
160 160 self.SetMarginWidth(2, 0)
161 161
162 162 self._apply_style()
163 163
164 164 # Xterm escape sequences
165 165 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
166 166 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
167 167
168 168 #self.SetEdgeMode(stc.STC_EDGE_LINE)
169 169 #self.SetEdgeColumn(80)
170 170
171 171 # styles
172 172 p = self.style
173 173 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
174 174 self.StyleClearAll()
175 175 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
176 176 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
177 177 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
178 178
179 179 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
180 180 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
181 181 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
182 182 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
183 183 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
184 184 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
185 185 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
186 186 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
187 187 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
188 188 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
189 189 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
190 190 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
191 191 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
192 192
193 193
194 194 def write(self, text):
195 195 """ Write given text to buffer, while translating the ansi escape
196 196 sequences.
197 197 """
198 198 title = self.title_pat.split(text)
199 199 if len(title)>0:
200 200 self.title = title[-1]
201 201
202 202 text = self.title_pat.sub('', text)
203 203 segments = self.color_pat.split(text)
204 204 segment = segments.pop(0)
205 205 self.StartStyling(self.GetLength(), 0xFF)
206 206 self.AppendText(segment)
207 207
208 208 if segments:
209 209 ansi_tags = self.color_pat.findall(text)
210 210
211 211 for tag in ansi_tags:
212 212 i = segments.index(tag)
213 213 self.StartStyling(self.GetLength(), 0xFF)
214 214 self.AppendText(segments[i+1])
215 215
216 216 if tag != '0':
217 217 self.SetStyling(len(segments[i+1]),
218 218 self.ANSI_STYLES[tag][0])
219 219
220 220 segments.pop(i)
221 221
222 222 self.GotoPos(self.GetLength())
223 223
224 224
225 225 def new_prompt(self, prompt):
226 226 """ Prints a prompt at start of line, and move the start of the
227 227 current block there.
228 228
229 229 The prompt can be give with ascii escape sequences.
230 230 """
231 231 self.write(prompt)
232 232 # now we update our cursor giving end of prompt
233 233 self.current_prompt_pos = self.GetLength()
234 234 self.current_prompt_line = self.GetCurrentLine()
235 235 wx.Yield()
236 236 self.EnsureCaretVisible()
237 237
238 238
239 239 def replace_current_edit_buffer(self, text):
240 240 """ Replace currently entered command line with given text.
241 241 """
242 242 self.SetSelection(self.current_prompt_pos, self.GetLength())
243 243 self.ReplaceSelection(text)
244 244 self.GotoPos(self.GetLength())
245 245
246 246
247 247 def get_current_edit_buffer(self):
248 248 """ Returns the text in current edit buffer.
249 249 """
250 250 return self.GetTextRange(self.current_prompt_pos,
251 251 self.GetLength())
252 252
253 253
254 254 #--------------------------------------------------------------------------
255 255 # Private API
256 256 #--------------------------------------------------------------------------
257 257
258 258 def _apply_style(self):
259 259 """ Applies the colors for the different text elements and the
260 260 carret.
261 261 """
262 262 self.SetCaretForeground(self.carret_color)
263 263
264 264 self.StyleClearAll()
265 265 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
266 266 "fore:#FF0000,back:#0000FF,bold")
267 267 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
268 268 "fore:#000000,back:#FF0000,bold")
269 269
270 270 for style in self.ANSI_STYLES.values():
271 271 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
272 272
273 273
274 274 def writeCompletion(self, possibilities):
275 275 if self.autocomplete_mode == 'IPYTHON':
276 276 max_len = len(max(possibilities, key=len))
277 277 max_symbol = ' '*max_len
278 278
279 279 #now we check how much symbol we can put on a line...
280 280 test_buffer = max_symbol + ' '*4
281 281
282 282 allowed_symbols = 80/len(test_buffer)
283 283 if allowed_symbols == 0:
284 284 allowed_symbols = 1
285 285
286 286 pos = 1
287 287 buf = ''
288 288 for symbol in possibilities:
289 289 #buf += symbol+'\n'#*spaces)
290 290 if pos < allowed_symbols:
291 291 spaces = max_len - len(symbol) + 4
292 292 buf += symbol+' '*spaces
293 293 pos += 1
294 294 else:
295 295 buf += symbol+'\n'
296 296 pos = 1
297 297 self.write(buf)
298 298 else:
299 299 possibilities.sort() # Python sorts are case sensitive
300 300 self.AutoCompSetIgnoreCase(False)
301 301 self.AutoCompSetAutoHide(False)
302 302 #let compute the length ot text)last word
303 303 splitter = [' ', '(', '[', '{']
304 304 last_word = self.get_current_edit_buffer()
305 305 for breaker in splitter:
306 306 last_word = last_word.split(breaker)[-1]
307 307 self.AutoCompShow(len(last_word), " ".join(possibilities))
308 308
309 309
310 310 def scroll_to_bottom(self):
311 311 maxrange = self.GetScrollRange(wx.VERTICAL)
312 312 self.ScrollLines(maxrange)
313 313
314 314
315 315 def _on_enter(self):
316 316 """ Called when the return key is hit.
317 317 """
318 318 pass
319 319
320 320
321 321 def _on_key_down(self, event, skip=True):
322 322 """ Key press callback used for correcting behavior for
323 323 console-like interfaces: the cursor is constraint to be after
324 324 the last prompt.
325 325
326 326 Return True if event as been catched.
327 327 """
328 catched = False
328 catched = True
329 329 # Intercept some specific keys.
330 330 if event.KeyCode == ord('L') and event.ControlDown() :
331 catched = True
332 331 self.scroll_to_bottom()
332 if event.KeyCode == ord('K') and event.ControlDown() :
333 self.replace_current_edit_buffer('')
333 334 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
334 catched = True
335 335 self.ScrollPages(-1)
336 336 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
337 catched = True
338 337 self.ScrollPages(1)
338 else:
339 catched = False
339 340
340 341 if self.AutoCompActive():
341 342 event.Skip()
342 343 else:
343 344 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
344 345 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
345 346 catched = True
346 347 self.write('\n')
347 348 self._on_enter()
348 349
349 350 elif event.KeyCode == wx.WXK_HOME:
350 351 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
351 352 self.GotoPos(self.current_prompt_pos)
352 353 catched = True
353 354
354 355 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
355 356 # FIXME: This behavior is not ideal: if the selection
356 357 # is already started, it will jump.
357 358 self.SetSelectionStart(self.current_prompt_pos)
358 359 self.SetSelectionEnd(self.GetCurrentPos())
359 360 catched = True
360 361
361 362 elif event.KeyCode == wx.WXK_UP:
362 363 if self.GetCurrentLine() > self.current_prompt_line:
363 364 if self.GetCurrentLine() == self.current_prompt_line + 1 \
364 365 and self.GetColumn(self.GetCurrentPos()) < \
365 366 self.GetColumn(self.current_prompt_pos):
366 367 self.GotoPos(self.current_prompt_pos)
367 368 else:
368 369 event.Skip()
369 370 catched = True
370 371
371 372 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
372 373 if self.GetCurrentPos() > self.current_prompt_pos:
373 374 event.Skip()
374 375 catched = True
375 376
376 377 if skip and not catched:
377 378 event.Skip()
378 379
379 380 return catched
380 381
381 382
382 383 def _on_key_up(self, event, skip=True):
383 384 """ If cursor is outside the editing region, put it back.
384 385 """
385 386 event.Skip()
386 387 if self.GetCurrentPos() < self.current_prompt_pos:
387 388 self.GotoPos(self.current_prompt_pos)
388 389
389 390
390 391
391 392
392 393 if __name__ == '__main__':
393 394 # Some simple code to test the console widget.
394 395 class MainWindow(wx.Frame):
395 396 def __init__(self, parent, id, title):
396 397 wx.Frame.__init__(self, parent, id, title, size=(300,250))
397 398 self._sizer = wx.BoxSizer(wx.VERTICAL)
398 399 self.console_widget = ConsoleWidget(self)
399 400 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
400 401 self.SetSizer(self._sizer)
401 402 self.SetAutoLayout(1)
402 403 self.Show(True)
403 404
404 405 app = wx.PySimpleApp()
405 406 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
406 407 w.SetSize((780, 460))
407 408 w.Show()
408 409
409 410 app.MainLoop()
410 411
411 412
General Comments 0
You need to be logged in to leave comments. Login now