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