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