##// END OF EJS Templates
Correct small history bug. Add busy cursor.
Gael Varoquaux -
Show More
@@ -1,158 +1,156 b''
1 1 """
2 2 Base front end class for all line-oriented frontends.
3 3
4 4 Currently this focuses on synchronous frontends.
5 5 """
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15 #-------------------------------------------------------------------------------
16 16 # Imports
17 17 #-------------------------------------------------------------------------------
18 18 import re
19 19
20 20 import IPython
21 21
22 22
23 23 from frontendbase import FrontEndBase
24 24 from IPython.kernel.core.interpreter import Interpreter
25 25
26 26 #-------------------------------------------------------------------------------
27 27 # Base class for the line-oriented front ends
28 28 #-------------------------------------------------------------------------------
29 29 class LineFrontEndBase(FrontEndBase):
30 30
31 31 # We need to keep the prompt number, to be able to increment
32 32 # it when there is an exception.
33 33 prompt_number = 1
34 34
35 35
36 36 #--------------------------------------------------------------------------
37 37 # Public API
38 38 #--------------------------------------------------------------------------
39 39
40 40 def __init__(self, shell=None, history=None):
41 41 if shell is None:
42 42 shell = Interpreter()
43 43 FrontEndBase.__init__(self, shell=shell, history=history)
44 44
45 45 #FIXME: print banner.
46 46 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
47 47 % IPython.__version__
48 48
49 49
50 50 def complete(self, token):
51 51 """Complete token in engine's user_ns
52 52
53 53 Parameters
54 54 ----------
55 55 token : string
56 56
57 57 Result
58 58 ------
59 59 Deferred result of
60 60 IPython.kernel.engineservice.IEngineBase.complete
61 61 """
62 62
63 63 return self.shell.complete(token)
64 64
65 65
66 66 def render_result(self, result):
67 67 if 'stdout' in result and result['stdout']:
68 68 self.write('\n' + result['stdout'])
69 69 if 'display' in result and result['display']:
70 70 self.write("%s%s\n" % (
71 71 self.output_prompt % result['number'],
72 72 result['display']['pprint']
73 73 ) )
74 74
75 75
76 76 def render_error(self, failure):
77 77 self.insert_text('\n\n'+str(failure)+'\n\n')
78 78 return failure
79 79
80 80
81 81 def prefilter_input(self, string):
82 82 string = string.replace('\r\n', '\n')
83 83 string = string.replace('\t', 4*' ')
84 84 # Clean the trailing whitespace
85 85 string = '\n'.join(l.rstrip() for l in string.split('\n'))
86 86 return string
87 87
88 88
89 89 def is_complete(self, string):
90 if ( len(self.get_current_edit_buffer().split('\n'))>1
91 and not re.findall(r"\n[\t ]*$", string)):
90 if ( len(self.get_current_edit_buffer().split('\n'))>2
91 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
92 92 return False
93 93 else:
94 94 return FrontEndBase.is_complete(self, string)
95 95
96 96
97 97 def execute(self, python_string, raw_string=None):
98 98 """ Send the python_string to the interpreter, stores the
99 99 raw_string in the history and starts a new prompt.
100 100 """
101 101 if raw_string is None:
102 102 raw_string = string
103 103 # Create a false result, in case there is an exception
104 104 self.last_result = dict(number=self.prompt_number)
105 105 try:
106 self.history.input_cache[-1] = raw_string
106 self.history.input_cache[-1] = raw_string.rstrip()
107 107 result = self.shell.execute(python_string)
108 108 self.last_result = result
109 109 self.render_result(result)
110 110 except:
111 111 self.show_traceback()
112 112 finally:
113 113 self.after_execute()
114 114
115 115
116 116 def after_execute(self):
117 117 """ All the operations required after an execution to put the
118 118 terminal back in a shape where it is usable.
119 119 """
120 120 self.prompt_number += 1
121 121 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
122 122 # Start a new empty history entry
123 123 self._add_history(None, '')
124 # The result contains useful information that can be used
125 # elsewhere.
124 self.history_cursor = len(self.history.input_cache) - 1
126 125
127 126
128 127 def _on_enter(self):
129 128 """ Called when the return key is pressed in a line editing
130 129 buffer.
131 130 """
132 131 current_buffer = self.get_current_edit_buffer()
133 132 cleaned_buffer = self.prefilter_input(current_buffer)
134 if self.is_complete(cleaned_buffer + '\n'):
135 # The '\n' is important in case prefiltering empties the
136 # line, to get a new prompt.
133 if self.is_complete(cleaned_buffer):
137 134 self.execute(cleaned_buffer, raw_string=current_buffer)
138 135 else:
139 if len(current_buffer.split('\n'))>1:
140 self.write(self._get_indent_string(current_buffer))
136 if len(current_buffer.split('\n'))>2:
137 # We need to clean the trailing '\n'
138 self.write(self._get_indent_string(current_buffer[:-1]))
141 139 else:
142 140 self.write('\t')
143 141
144 142
145 143 #--------------------------------------------------------------------------
146 144 # Private API
147 145 #--------------------------------------------------------------------------
148 146
149 147 def _get_indent_string(self, string):
150 148 string = string.replace('\t', ' '*4)
151 149 string = string.split('\n')[-1]
152 150 indent_chars = len(string) - len(string.lstrip())
153 151 indent_string = '\t'*(indent_chars // 4) + \
154 152 ' '*(indent_chars % 4)
155 153
156 154 return indent_string
157 155
158 156
@@ -1,412 +1,417 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 autocomplete_mode='IPYTHON'):
102 autocomplete_mode='STC'):
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 # Tell autocompletion to choose automaticaly out of a single
156 # choice list
157 self.AutoCompSetChooseSingle(True)
158 self.AutoCompSetMaxHeight(10)
159
156 160 self.SetMargins(3, 3) #text is moved away from border with 3px
157 161 # Suppressing Scintilla margins
158 162 self.SetMarginWidth(0, 0)
159 163 self.SetMarginWidth(1, 0)
160 164 self.SetMarginWidth(2, 0)
161 165
162 166 self._apply_style()
163 167
164 168 # Xterm escape sequences
165 169 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
166 170 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
167 171
168 172 #self.SetEdgeMode(stc.STC_EDGE_LINE)
169 173 #self.SetEdgeColumn(80)
170 174
171 175 # styles
172 176 p = self.style
173 177 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
174 178 self.StyleClearAll()
175 179 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
176 180 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
177 181 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
178 182
179 183 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
180 184 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
181 185 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
182 186 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
183 187 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
184 188 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
185 189 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
186 190 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
187 191 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
188 192 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
189 193 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
190 194 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
191 195 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
192 196
193 197
194 198 def write(self, text):
195 199 """ Write given text to buffer, while translating the ansi escape
196 200 sequences.
197 201 """
198 202 title = self.title_pat.split(text)
199 203 if len(title)>0:
200 204 self.title = title[-1]
201 205
202 206 text = self.title_pat.sub('', text)
203 207 segments = self.color_pat.split(text)
204 208 segment = segments.pop(0)
205 209 self.StartStyling(self.GetLength(), 0xFF)
206 210 self.AppendText(segment)
207 211
208 212 if segments:
209 213 ansi_tags = self.color_pat.findall(text)
210 214
211 215 for tag in ansi_tags:
212 216 i = segments.index(tag)
213 217 self.StartStyling(self.GetLength(), 0xFF)
214 218 self.AppendText(segments[i+1])
215 219
216 220 if tag != '0':
217 221 self.SetStyling(len(segments[i+1]),
218 222 self.ANSI_STYLES[tag][0])
219 223
220 224 segments.pop(i)
221 225
222 226 self.GotoPos(self.GetLength())
223 227
224 228
225 229 def new_prompt(self, prompt):
226 230 """ Prints a prompt at start of line, and move the start of the
227 231 current block there.
228 232
229 233 The prompt can be give with ascii escape sequences.
230 234 """
231 235 self.write(prompt)
232 236 # now we update our cursor giving end of prompt
233 237 self.current_prompt_pos = self.GetLength()
234 238 self.current_prompt_line = self.GetCurrentLine()
235 239 wx.Yield()
236 240 self.EnsureCaretVisible()
237 241
238 242
239 243 def replace_current_edit_buffer(self, text):
240 244 """ Replace currently entered command line with given text.
241 245 """
242 246 self.SetSelection(self.current_prompt_pos, self.GetLength())
243 247 self.ReplaceSelection(text)
244 248 self.GotoPos(self.GetLength())
245 249
246 250
247 251 def get_current_edit_buffer(self):
248 252 """ Returns the text in current edit buffer.
249 253 """
250 254 return self.GetTextRange(self.current_prompt_pos,
251 255 self.GetLength())
252 256
253 257
254 258 #--------------------------------------------------------------------------
255 259 # Private API
256 260 #--------------------------------------------------------------------------
257 261
258 262 def _apply_style(self):
259 263 """ Applies the colors for the different text elements and the
260 264 carret.
261 265 """
262 266 self.SetCaretForeground(self.carret_color)
263 267
264 268 self.StyleClearAll()
265 269 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
266 270 "fore:#FF0000,back:#0000FF,bold")
267 271 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
268 272 "fore:#000000,back:#FF0000,bold")
269 273
270 274 for style in self.ANSI_STYLES.values():
271 275 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
272 276
273 277
274 def writeCompletion(self, possibilities):
278 def write_completion(self, possibilities):
275 279 if self.autocomplete_mode == 'IPYTHON':
276 280 max_len = len(max(possibilities, key=len))
277 281 max_symbol = ' '*max_len
278 282
279 283 #now we check how much symbol we can put on a line...
280 284 test_buffer = max_symbol + ' '*4
281 285
282 286 allowed_symbols = 80/len(test_buffer)
283 287 if allowed_symbols == 0:
284 288 allowed_symbols = 1
285 289
286 290 pos = 1
287 291 buf = ''
288 292 for symbol in possibilities:
289 293 #buf += symbol+'\n'#*spaces)
290 294 if pos < allowed_symbols:
291 295 spaces = max_len - len(symbol) + 4
292 296 buf += symbol+' '*spaces
293 297 pos += 1
294 298 else:
295 299 buf += symbol+'\n'
296 300 pos = 1
297 301 self.write(buf)
298 302 else:
299 303 possibilities.sort() # Python sorts are case sensitive
300 304 self.AutoCompSetIgnoreCase(False)
301 305 self.AutoCompSetAutoHide(False)
302 306 #let compute the length ot text)last word
303 307 splitter = [' ', '(', '[', '{']
304 308 last_word = self.get_current_edit_buffer()
305 309 for breaker in splitter:
306 310 last_word = last_word.split(breaker)[-1]
311 self.AutoCompSetMaxHeight(len(possibilities))
307 312 self.AutoCompShow(len(last_word), " ".join(possibilities))
308 313
309 314
310 315 def scroll_to_bottom(self):
311 316 maxrange = self.GetScrollRange(wx.VERTICAL)
312 317 self.ScrollLines(maxrange)
313 318
314 319
315 320 def _on_enter(self):
316 321 """ Called when the return key is hit.
317 322 """
318 323 pass
319 324
320 325
321 326 def _on_key_down(self, event, skip=True):
322 327 """ Key press callback used for correcting behavior for
323 328 console-like interfaces: the cursor is constraint to be after
324 329 the last prompt.
325 330
326 331 Return True if event as been catched.
327 332 """
328 333 catched = True
329 334 # Intercept some specific keys.
330 335 if event.KeyCode == ord('L') and event.ControlDown() :
331 336 self.scroll_to_bottom()
332 if event.KeyCode == ord('K') and event.ControlDown() :
337 elif event.KeyCode == ord('K') and event.ControlDown() :
333 338 self.replace_current_edit_buffer('')
334 339 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
335 340 self.ScrollPages(-1)
336 341 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
337 342 self.ScrollPages(1)
338 343 else:
339 344 catched = False
340 345
341 346 if self.AutoCompActive():
342 347 event.Skip()
343 348 else:
344 349 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
345 350 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
346 351 catched = True
347 352 self.write('\n')
348 353 self._on_enter()
349 354
350 355 elif event.KeyCode == wx.WXK_HOME:
351 356 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
352 357 self.GotoPos(self.current_prompt_pos)
353 358 catched = True
354 359
355 360 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
356 361 # FIXME: This behavior is not ideal: if the selection
357 362 # is already started, it will jump.
358 363 self.SetSelectionStart(self.current_prompt_pos)
359 364 self.SetSelectionEnd(self.GetCurrentPos())
360 365 catched = True
361 366
362 367 elif event.KeyCode == wx.WXK_UP:
363 368 if self.GetCurrentLine() > self.current_prompt_line:
364 369 if self.GetCurrentLine() == self.current_prompt_line + 1 \
365 370 and self.GetColumn(self.GetCurrentPos()) < \
366 371 self.GetColumn(self.current_prompt_pos):
367 372 self.GotoPos(self.current_prompt_pos)
368 373 else:
369 374 event.Skip()
370 375 catched = True
371 376
372 377 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
373 378 if self.GetCurrentPos() > self.current_prompt_pos:
374 379 event.Skip()
375 380 catched = True
376 381
377 382 if skip and not catched:
378 383 event.Skip()
379 384
380 385 return catched
381 386
382 387
383 388 def _on_key_up(self, event, skip=True):
384 389 """ If cursor is outside the editing region, put it back.
385 390 """
386 391 event.Skip()
387 392 if self.GetCurrentPos() < self.current_prompt_pos:
388 393 self.GotoPos(self.current_prompt_pos)
389 394
390 395
391 396
392 397
393 398 if __name__ == '__main__':
394 399 # Some simple code to test the console widget.
395 400 class MainWindow(wx.Frame):
396 401 def __init__(self, parent, id, title):
397 402 wx.Frame.__init__(self, parent, id, title, size=(300,250))
398 403 self._sizer = wx.BoxSizer(wx.VERTICAL)
399 404 self.console_widget = ConsoleWidget(self)
400 405 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
401 406 self.SetSizer(self._sizer)
402 407 self.SetAutoLayout(1)
403 408 self.Show(True)
404 409
405 410 app = wx.PySimpleApp()
406 411 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
407 412 w.SetSize((780, 460))
408 413 w.Show()
409 414
410 415 app.MainLoop()
411 416
412 417
@@ -1,106 +1,132 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 import re
25 26 from console_widget import ConsoleWidget
26 27
27 28 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
28 29
29 30 #-------------------------------------------------------------------------------
30 31 # Classes to implement the Wx frontend
31 32 #-------------------------------------------------------------------------------
32 33 class IPythonWxController(PrefilterFrontEnd, ConsoleWidget):
33 34
34 35 output_prompt = \
35 36 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
36 37
37 38 #--------------------------------------------------------------------------
38 39 # Public API
39 40 #--------------------------------------------------------------------------
40 41
41 42 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
42 43 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
43 44 *args, **kwds):
44 45 """ Create Shell instance.
45 46 """
46 47 ConsoleWidget.__init__(self, parent, id, pos, size, style)
47 48 PrefilterFrontEnd.__init__(self)
48 49
49 50 # Capture Character keys
50 51 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
51
52
53
54 def do_completion(self):
55 line = self.get_current_edit_buffer()
56 completions = self.complete(line)
57 self.write_completion(completions)
58
59
60 def execute(self, *args, **kwargs):
61 self._cursor = wx.BusyCursor()
62 PrefilterFrontEnd.execute(self, *args, **kwargs)
63
64
65 def after_execute(self):
66 PrefilterFrontEnd.after_execute(self)
67 del self._cursor
68
52 69 #--------------------------------------------------------------------------
53 70 # Private API
54 71 #--------------------------------------------------------------------------
55 72
56 73
57 74 def _on_key_down(self, event, skip=True):
58 75 """ Capture the character events, let the parent
59 76 widget handle them, and put our logic afterward.
60 77 """
61 78 current_line_number = self.GetCurrentLine()
62 # Up history
63 if event.KeyCode == wx.WXK_UP and (
64 ( current_line_number == self.current_prompt_line and
65 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
66 or event.ControlDown() ):
67 new_buffer = self.get_history_previous(
68 self.get_current_edit_buffer())
69 if new_buffer is not None:
70 self.replace_current_edit_buffer(new_buffer)
71 if self.GetCurrentLine() > self.current_prompt_line:
72 # Go to first line, for seemless history up.
73 self.GotoPos(self.current_prompt_pos)
74 # Down history
75 elif event.KeyCode == wx.WXK_DOWN and (
76 ( current_line_number == self.LineCount -1 and
77 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
78 or event.ControlDown() ):
79 new_buffer = self.get_history_next()
80 if new_buffer is not None:
81 self.replace_current_edit_buffer(new_buffer)
79 if self.AutoCompActive():
80 event.Skip()
82 81 else:
83 ConsoleWidget._on_key_down(self, event, skip=skip)
82 # Up history
83 if event.KeyCode == wx.WXK_UP and (
84 ( current_line_number == self.current_prompt_line and
85 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
86 or event.ControlDown() ):
87 new_buffer = self.get_history_previous(
88 self.get_current_edit_buffer())
89 if new_buffer is not None:
90 self.replace_current_edit_buffer(new_buffer)
91 if self.GetCurrentLine() > self.current_prompt_line:
92 # Go to first line, for seemless history up.
93 self.GotoPos(self.current_prompt_pos)
94 # Down history
95 elif event.KeyCode == wx.WXK_DOWN and (
96 ( current_line_number == self.LineCount -1 and
97 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
98 or event.ControlDown() ):
99 new_buffer = self.get_history_next()
100 if new_buffer is not None:
101 self.replace_current_edit_buffer(new_buffer)
102 elif event.KeyCode == ord('\t'):
103 last_line = self.get_current_edit_buffer().split('\n')[-1]
104 if not re.match(r'^\s*$', last_line):
105 self.do_completion()
106 else:
107 event.Skip()
108 else:
109 ConsoleWidget._on_key_down(self, event, skip=skip)
84 110
85 111
86 112
87 113
88 114 if __name__ == '__main__':
89 115 class MainWindow(wx.Frame):
90 116 def __init__(self, parent, id, title):
91 117 wx.Frame.__init__(self, parent, id, title, size=(300,250))
92 118 self._sizer = wx.BoxSizer(wx.VERTICAL)
93 119 self.shell = IPythonWxController(self)
94 120 self._sizer.Add(self.shell, 1, wx.EXPAND)
95 121 self.SetSizer(self._sizer)
96 122 self.SetAutoLayout(1)
97 123 self.Show(True)
98 124
99 125 app = wx.PySimpleApp()
100 126 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
101 127 frame.shell.SetFocus()
102 128 frame.SetSize((660, 460))
103 129 self = frame.shell
104 130
105 131 app.MainLoop()
106 132
General Comments 0
You need to be logged in to leave comments. Login now