##// END OF EJS Templates
BUG: Fix handling of non-tab completion tab keys
Gael Varoquaux -
Show More
@@ -1,601 +1,602 b''
1 # encoding: utf-8 -*- test-case-name:
1 # encoding: utf-8 -*- test-case-name:
2 # FIXME: Need to add tests.
2 # FIXME: Need to add tests.
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4
4
5 """Classes to provide a Wx frontend to the
5 """Classes to provide a Wx frontend to the
6 IPython.kernel.core.interpreter.
6 IPython.kernel.core.interpreter.
7
7
8 This class inherits from ConsoleWidget, that provides a console-like
8 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
9 widget to provide a text-rendering widget suitable for a terminal.
10 """
10 """
11
11
12 __docformat__ = "restructuredtext en"
12 __docformat__ = "restructuredtext en"
13
13
14 #-------------------------------------------------------------------------------
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
15 # Copyright (C) 2008 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20
20
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-------------------------------------------------------------------------------
23 #-------------------------------------------------------------------------------
24
24
25 # Major library imports
25 # Major library imports
26 import re
26 import re
27 import __builtin__
27 import __builtin__
28 import sys
28 import sys
29 from threading import Lock
29 from threading import Lock
30
30
31 import wx
31 import wx
32 from wx import stc
32 from wx import stc
33
33
34 # Ipython-specific imports.
34 # Ipython-specific imports.
35 from IPython.frontend.process import PipedProcess
35 from IPython.frontend.process import PipedProcess
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
36 from console_widget import ConsoleWidget, _COMPLETE_BUFFER_MARKER, \
37 _ERROR_MARKER, _INPUT_MARKER
37 _ERROR_MARKER, _INPUT_MARKER
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Classes to implement the Wx frontend
41 # Classes to implement the Wx frontend
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
43 class WxController(ConsoleWidget, PrefilterFrontEnd):
44 """Classes to provide a Wx frontend to the
44 """Classes to provide a Wx frontend to the
45 IPython.kernel.core.interpreter.
45 IPython.kernel.core.interpreter.
46
46
47 This class inherits from ConsoleWidget, that provides a console-like
47 This class inherits from ConsoleWidget, that provides a console-like
48 widget to provide a text-rendering widget suitable for a terminal.
48 widget to provide a text-rendering widget suitable for a terminal.
49 """
49 """
50
50
51 # Print debug info on what is happening to the console.
51 # Print debug info on what is happening to the console.
52 debug = False
52 debug = False
53
53
54 # The title of the terminal, as captured through the ANSI escape
54 # The title of the terminal, as captured through the ANSI escape
55 # sequences.
55 # sequences.
56 def _set_title(self, title):
56 def _set_title(self, title):
57 return self.Parent.SetTitle(title)
57 return self.Parent.SetTitle(title)
58
58
59 def _get_title(self):
59 def _get_title(self):
60 return self.Parent.GetTitle()
60 return self.Parent.GetTitle()
61
61
62 title = property(_get_title, _set_title)
62 title = property(_get_title, _set_title)
63
63
64
64
65 # The buffer being edited.
65 # The buffer being edited.
66 # We are duplicating the definition here because of multiple
66 # We are duplicating the definition here because of multiple
67 # inheritence
67 # inheritence
68 def _set_input_buffer(self, string):
68 def _set_input_buffer(self, string):
69 ConsoleWidget._set_input_buffer(self, string)
69 ConsoleWidget._set_input_buffer(self, string)
70 self._colorize_input_buffer()
70 self._colorize_input_buffer()
71
71
72 def _get_input_buffer(self):
72 def _get_input_buffer(self):
73 """ Returns the text in current edit buffer.
73 """ Returns the text in current edit buffer.
74 """
74 """
75 return ConsoleWidget._get_input_buffer(self)
75 return ConsoleWidget._get_input_buffer(self)
76
76
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
77 input_buffer = property(_get_input_buffer, _set_input_buffer)
78
78
79
79
80 #--------------------------------------------------------------------------
80 #--------------------------------------------------------------------------
81 # Private Attributes
81 # Private Attributes
82 #--------------------------------------------------------------------------
82 #--------------------------------------------------------------------------
83
83
84 # A flag governing the behavior of the input. Can be:
84 # A flag governing the behavior of the input. Can be:
85 #
85 #
86 # 'readline' for readline-like behavior with a prompt
86 # 'readline' for readline-like behavior with a prompt
87 # and an edit buffer.
87 # and an edit buffer.
88 # 'raw_input' similar to readline, but triggered by a raw-input
88 # 'raw_input' similar to readline, but triggered by a raw-input
89 # call. Can be used by subclasses to act differently.
89 # call. Can be used by subclasses to act differently.
90 # 'subprocess' for sending the raw input directly to a
90 # 'subprocess' for sending the raw input directly to a
91 # subprocess.
91 # subprocess.
92 # 'buffering' for buffering of the input, that will be used
92 # 'buffering' for buffering of the input, that will be used
93 # when the input state switches back to another state.
93 # when the input state switches back to another state.
94 _input_state = 'readline'
94 _input_state = 'readline'
95
95
96 # Attribute to store reference to the pipes of a subprocess, if we
96 # Attribute to store reference to the pipes of a subprocess, if we
97 # are running any.
97 # are running any.
98 _running_process = False
98 _running_process = False
99
99
100 # A queue for writing fast streams to the screen without flooding the
100 # A queue for writing fast streams to the screen without flooding the
101 # event loop
101 # event loop
102 _out_buffer = []
102 _out_buffer = []
103
103
104 # A lock to lock the _out_buffer to make sure we don't empty it
104 # A lock to lock the _out_buffer to make sure we don't empty it
105 # while it is being swapped
105 # while it is being swapped
106 _out_buffer_lock = Lock()
106 _out_buffer_lock = Lock()
107
107
108 # The different line markers used to higlight the prompts.
108 # The different line markers used to higlight the prompts.
109 _markers = dict()
109 _markers = dict()
110
110
111 #--------------------------------------------------------------------------
111 #--------------------------------------------------------------------------
112 # Public API
112 # Public API
113 #--------------------------------------------------------------------------
113 #--------------------------------------------------------------------------
114
114
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
115 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
116 size=wx.DefaultSize,
116 size=wx.DefaultSize,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
117 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
118 styledef=None,
118 styledef=None,
119 *args, **kwds):
119 *args, **kwds):
120 """ Create Shell instance.
120 """ Create Shell instance.
121
121
122 Parameters
122 Parameters
123 -----------
123 -----------
124 styledef : dict, optional
124 styledef : dict, optional
125 styledef is the dictionary of options used to define the
125 styledef is the dictionary of options used to define the
126 style.
126 style.
127 """
127 """
128 if styledef is not None:
128 if styledef is not None:
129 self.style = styledef
129 self.style = styledef
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
130 ConsoleWidget.__init__(self, parent, id, pos, size, style)
131 PrefilterFrontEnd.__init__(self, **kwds)
131 PrefilterFrontEnd.__init__(self, **kwds)
132
132
133 # Stick in our own raw_input:
133 # Stick in our own raw_input:
134 self.ipython0.raw_input = self.raw_input
134 self.ipython0.raw_input = self.raw_input
135
135
136 # A time for flushing the write buffer
136 # A time for flushing the write buffer
137 BUFFER_FLUSH_TIMER_ID = 100
137 BUFFER_FLUSH_TIMER_ID = 100
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
138 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
139 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
140
140
141 if 'debug' in kwds:
141 if 'debug' in kwds:
142 self.debug = kwds['debug']
142 self.debug = kwds['debug']
143 kwds.pop('debug')
143 kwds.pop('debug')
144
144
145 # Inject self in namespace, for debug
145 # Inject self in namespace, for debug
146 if self.debug:
146 if self.debug:
147 self.shell.user_ns['self'] = self
147 self.shell.user_ns['self'] = self
148 # Inject our own raw_input in namespace
148 # Inject our own raw_input in namespace
149 self.shell.user_ns['raw_input'] = self.raw_input
149 self.shell.user_ns['raw_input'] = self.raw_input
150
150
151 def raw_input(self, prompt=''):
151 def raw_input(self, prompt=''):
152 """ A replacement from python's raw_input.
152 """ A replacement from python's raw_input.
153 """
153 """
154 self.new_prompt(prompt)
154 self.new_prompt(prompt)
155 self._input_state = 'raw_input'
155 self._input_state = 'raw_input'
156 if hasattr(self, '_cursor'):
156 if hasattr(self, '_cursor'):
157 del self._cursor
157 del self._cursor
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
158 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
159 self.__old_on_enter = self._on_enter
159 self.__old_on_enter = self._on_enter
160 event_loop = wx.EventLoop()
160 event_loop = wx.EventLoop()
161 def my_on_enter():
161 def my_on_enter():
162 event_loop.Exit()
162 event_loop.Exit()
163 self._on_enter = my_on_enter
163 self._on_enter = my_on_enter
164 # XXX: Running a separate event_loop. Ugly.
164 # XXX: Running a separate event_loop. Ugly.
165 event_loop.Run()
165 event_loop.Run()
166 self._on_enter = self.__old_on_enter
166 self._on_enter = self.__old_on_enter
167 self._input_state = 'buffering'
167 self._input_state = 'buffering'
168 self._cursor = wx.BusyCursor()
168 self._cursor = wx.BusyCursor()
169 return self.input_buffer.rstrip('\n')
169 return self.input_buffer.rstrip('\n')
170
170
171
171
172 def system_call(self, command_string):
172 def system_call(self, command_string):
173 self._input_state = 'subprocess'
173 self._input_state = 'subprocess'
174 event_loop = wx.EventLoop()
174 event_loop = wx.EventLoop()
175 def _end_system_call():
175 def _end_system_call():
176 self._input_state = 'buffering'
176 self._input_state = 'buffering'
177 self._running_process = False
177 self._running_process = False
178 event_loop.Exit()
178 event_loop.Exit()
179
179
180 self._running_process = PipedProcess(command_string,
180 self._running_process = PipedProcess(command_string,
181 out_callback=self.buffered_write,
181 out_callback=self.buffered_write,
182 end_callback = _end_system_call)
182 end_callback = _end_system_call)
183 self._running_process.start()
183 self._running_process.start()
184 # XXX: Running a separate event_loop. Ugly.
184 # XXX: Running a separate event_loop. Ugly.
185 event_loop.Run()
185 event_loop.Run()
186 # Be sure to flush the buffer.
186 # Be sure to flush the buffer.
187 self._buffer_flush(event=None)
187 self._buffer_flush(event=None)
188
188
189
189
190 def do_calltip(self):
190 def do_calltip(self):
191 """ Analyse current and displays useful calltip for it.
191 """ Analyse current and displays useful calltip for it.
192 """
192 """
193 if self.debug:
193 if self.debug:
194 print >>sys.__stdout__, "do_calltip"
194 print >>sys.__stdout__, "do_calltip"
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
195 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
196 symbol = self.input_buffer
196 symbol = self.input_buffer
197 symbol_string = separators.split(symbol)[-1]
197 symbol_string = separators.split(symbol)[-1]
198 base_symbol_string = symbol_string.split('.')[0]
198 base_symbol_string = symbol_string.split('.')[0]
199 if base_symbol_string in self.shell.user_ns:
199 if base_symbol_string in self.shell.user_ns:
200 symbol = self.shell.user_ns[base_symbol_string]
200 symbol = self.shell.user_ns[base_symbol_string]
201 elif base_symbol_string in self.shell.user_global_ns:
201 elif base_symbol_string in self.shell.user_global_ns:
202 symbol = self.shell.user_global_ns[base_symbol_string]
202 symbol = self.shell.user_global_ns[base_symbol_string]
203 elif base_symbol_string in __builtin__.__dict__:
203 elif base_symbol_string in __builtin__.__dict__:
204 symbol = __builtin__.__dict__[base_symbol_string]
204 symbol = __builtin__.__dict__[base_symbol_string]
205 else:
205 else:
206 return False
206 return False
207 try:
207 try:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
208 for name in symbol_string.split('.')[1:] + ['__doc__']:
209 symbol = getattr(symbol, name)
209 symbol = getattr(symbol, name)
210 self.AutoCompCancel()
210 self.AutoCompCancel()
211 # Check that the symbol can indeed be converted to a string:
211 # Check that the symbol can indeed be converted to a string:
212 symbol += ''
212 symbol += ''
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
213 wx.CallAfter(self.CallTipShow, self.GetCurrentPos(), symbol)
214 except:
214 except:
215 # The retrieve symbol couldn't be converted to a string
215 # The retrieve symbol couldn't be converted to a string
216 pass
216 pass
217
217
218
218
219 def _popup_completion(self, create=False):
219 def _popup_completion(self, create=False):
220 """ Updates the popup completion menu if it exists. If create is
220 """ Updates the popup completion menu if it exists. If create is
221 true, open the menu.
221 true, open the menu.
222 """
222 """
223 if self.debug:
223 if self.debug:
224 print >>sys.__stdout__, "_popup_completion"
224 print >>sys.__stdout__, "_popup_completion"
225 line = self.input_buffer
225 line = self.input_buffer
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
226 if (self.AutoCompActive() and line and not line[-1] == '.') \
227 or create==True:
227 or create==True:
228 suggestion, completions = self.complete(line)
228 suggestion, completions = self.complete(line)
229 if completions:
229 if completions:
230 offset = len(self._get_completion_text(line))
230 offset = len(self._get_completion_text(line))
231 self.pop_completion(completions, offset=offset)
231 self.pop_completion(completions, offset=offset)
232 if self.debug:
232 if self.debug:
233 print >>sys.__stdout__, completions
233 print >>sys.__stdout__, completions
234
234
235
235
236 def buffered_write(self, text):
236 def buffered_write(self, text):
237 """ A write method for streams, that caches the stream in order
237 """ A write method for streams, that caches the stream in order
238 to avoid flooding the event loop.
238 to avoid flooding the event loop.
239
239
240 This can be called outside of the main loop, in separate
240 This can be called outside of the main loop, in separate
241 threads.
241 threads.
242 """
242 """
243 self._out_buffer_lock.acquire()
243 self._out_buffer_lock.acquire()
244 self._out_buffer.append(text)
244 self._out_buffer.append(text)
245 self._out_buffer_lock.release()
245 self._out_buffer_lock.release()
246 if not self._buffer_flush_timer.IsRunning():
246 if not self._buffer_flush_timer.IsRunning():
247 wx.CallAfter(self._buffer_flush_timer.Start,
247 wx.CallAfter(self._buffer_flush_timer.Start,
248 milliseconds=100, oneShot=True)
248 milliseconds=100, oneShot=True)
249
249
250
250
251 def clear_screen(self):
251 def clear_screen(self):
252 """ Empty completely the widget.
252 """ Empty completely the widget.
253 """
253 """
254 self.ClearAll()
254 self.ClearAll()
255 self.new_prompt(self.input_prompt_template.substitute(
255 self.new_prompt(self.input_prompt_template.substitute(
256 number=(self.last_result['number'] + 1)))
256 number=(self.last_result['number'] + 1)))
257
257
258
258
259 #--------------------------------------------------------------------------
259 #--------------------------------------------------------------------------
260 # LineFrontEnd interface
260 # LineFrontEnd interface
261 #--------------------------------------------------------------------------
261 #--------------------------------------------------------------------------
262
262
263 def execute(self, python_string, raw_string=None):
263 def execute(self, python_string, raw_string=None):
264 self._input_state = 'buffering'
264 self._input_state = 'buffering'
265 self.CallTipCancel()
265 self.CallTipCancel()
266 self._cursor = wx.BusyCursor()
266 self._cursor = wx.BusyCursor()
267 if raw_string is None:
267 if raw_string is None:
268 raw_string = python_string
268 raw_string = python_string
269 end_line = self.current_prompt_line \
269 end_line = self.current_prompt_line \
270 + max(1, len(raw_string.split('\n'))-1)
270 + max(1, len(raw_string.split('\n'))-1)
271 for i in range(self.current_prompt_line, end_line):
271 for i in range(self.current_prompt_line, end_line):
272 if i in self._markers:
272 if i in self._markers:
273 self.MarkerDeleteHandle(self._markers[i])
273 self.MarkerDeleteHandle(self._markers[i])
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
274 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
275 # Use a callafter to update the display robustly under windows
275 # Use a callafter to update the display robustly under windows
276 def callback():
276 def callback():
277 self.GotoPos(self.GetLength())
277 self.GotoPos(self.GetLength())
278 PrefilterFrontEnd.execute(self, python_string,
278 PrefilterFrontEnd.execute(self, python_string,
279 raw_string=raw_string)
279 raw_string=raw_string)
280 wx.CallAfter(callback)
280 wx.CallAfter(callback)
281
281
282
282
283 def execute_command(self, command, hidden=False):
283 def execute_command(self, command, hidden=False):
284 """ Execute a command, not only in the model, but also in the
284 """ Execute a command, not only in the model, but also in the
285 view.
285 view.
286 """
286 """
287 # XXX: This method needs to be integrated in the base fronted
287 # XXX: This method needs to be integrated in the base fronted
288 # interface
288 # interface
289 if hidden:
289 if hidden:
290 return self.shell.execute(command)
290 return self.shell.execute(command)
291 else:
291 else:
292 # XXX: we are not storing the input buffer previous to the
292 # XXX: we are not storing the input buffer previous to the
293 # execution, as this forces us to run the execution
293 # execution, as this forces us to run the execution
294 # input_buffer a yield, which is not good.
294 # input_buffer a yield, which is not good.
295 ##current_buffer = self.shell.control.input_buffer
295 ##current_buffer = self.shell.control.input_buffer
296 command = command.rstrip()
296 command = command.rstrip()
297 if len(command.split('\n')) > 1:
297 if len(command.split('\n')) > 1:
298 # The input command is several lines long, we need to
298 # The input command is several lines long, we need to
299 # force the execution to happen
299 # force the execution to happen
300 command += '\n'
300 command += '\n'
301 cleaned_command = self.prefilter_input(command)
301 cleaned_command = self.prefilter_input(command)
302 self.input_buffer = command
302 self.input_buffer = command
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
303 # Do not use wx.Yield() (aka GUI.process_events()) to avoid
304 # recursive yields.
304 # recursive yields.
305 self.ProcessEvent(wx.PaintEvent())
305 self.ProcessEvent(wx.PaintEvent())
306 self.write('\n')
306 self.write('\n')
307 if not self.is_complete(cleaned_command + '\n'):
307 if not self.is_complete(cleaned_command + '\n'):
308 self._colorize_input_buffer()
308 self._colorize_input_buffer()
309 self.render_error('Incomplete or invalid input')
309 self.render_error('Incomplete or invalid input')
310 self.new_prompt(self.input_prompt_template.substitute(
310 self.new_prompt(self.input_prompt_template.substitute(
311 number=(self.last_result['number'] + 1)))
311 number=(self.last_result['number'] + 1)))
312 return False
312 return False
313 self._on_enter()
313 self._on_enter()
314 return True
314 return True
315
315
316
316
317 def save_output_hooks(self):
317 def save_output_hooks(self):
318 self.__old_raw_input = __builtin__.raw_input
318 self.__old_raw_input = __builtin__.raw_input
319 PrefilterFrontEnd.save_output_hooks(self)
319 PrefilterFrontEnd.save_output_hooks(self)
320
320
321 def capture_output(self):
321 def capture_output(self):
322 self.SetLexer(stc.STC_LEX_NULL)
322 self.SetLexer(stc.STC_LEX_NULL)
323 PrefilterFrontEnd.capture_output(self)
323 PrefilterFrontEnd.capture_output(self)
324 __builtin__.raw_input = self.raw_input
324 __builtin__.raw_input = self.raw_input
325
325
326
326
327 def release_output(self):
327 def release_output(self):
328 __builtin__.raw_input = self.__old_raw_input
328 __builtin__.raw_input = self.__old_raw_input
329 PrefilterFrontEnd.release_output(self)
329 PrefilterFrontEnd.release_output(self)
330 self.SetLexer(stc.STC_LEX_PYTHON)
330 self.SetLexer(stc.STC_LEX_PYTHON)
331
331
332
332
333 def after_execute(self):
333 def after_execute(self):
334 PrefilterFrontEnd.after_execute(self)
334 PrefilterFrontEnd.after_execute(self)
335 # Clear the wait cursor
335 # Clear the wait cursor
336 if hasattr(self, '_cursor'):
336 if hasattr(self, '_cursor'):
337 del self._cursor
337 del self._cursor
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
338 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
339
339
340
340
341 def show_traceback(self):
341 def show_traceback(self):
342 start_line = self.GetCurrentLine()
342 start_line = self.GetCurrentLine()
343 PrefilterFrontEnd.show_traceback(self)
343 PrefilterFrontEnd.show_traceback(self)
344 self.ProcessEvent(wx.PaintEvent())
344 self.ProcessEvent(wx.PaintEvent())
345 #wx.Yield()
345 #wx.Yield()
346 for i in range(start_line, self.GetCurrentLine()):
346 for i in range(start_line, self.GetCurrentLine()):
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
347 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
348
348
349
349
350 #--------------------------------------------------------------------------
350 #--------------------------------------------------------------------------
351 # FrontEndBase interface
351 # FrontEndBase interface
352 #--------------------------------------------------------------------------
352 #--------------------------------------------------------------------------
353
353
354 def render_error(self, e):
354 def render_error(self, e):
355 start_line = self.GetCurrentLine()
355 start_line = self.GetCurrentLine()
356 self.write('\n' + e + '\n')
356 self.write('\n' + e + '\n')
357 for i in range(start_line, self.GetCurrentLine()):
357 for i in range(start_line, self.GetCurrentLine()):
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
358 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
359
359
360
360
361 #--------------------------------------------------------------------------
361 #--------------------------------------------------------------------------
362 # ConsoleWidget interface
362 # ConsoleWidget interface
363 #--------------------------------------------------------------------------
363 #--------------------------------------------------------------------------
364
364
365 def new_prompt(self, prompt):
365 def new_prompt(self, prompt):
366 """ Display a new prompt, and start a new input buffer.
366 """ Display a new prompt, and start a new input buffer.
367 """
367 """
368 self._input_state = 'readline'
368 self._input_state = 'readline'
369 ConsoleWidget.new_prompt(self, prompt)
369 ConsoleWidget.new_prompt(self, prompt)
370 i = self.current_prompt_line
370 i = self.current_prompt_line
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
371 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
372
372
373
373
374 def continuation_prompt(self, *args, **kwargs):
374 def continuation_prompt(self, *args, **kwargs):
375 # Avoid multiple inheritence, be explicit about which
375 # Avoid multiple inheritence, be explicit about which
376 # parent method class gets called
376 # parent method class gets called
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
377 return ConsoleWidget.continuation_prompt(self, *args, **kwargs)
378
378
379
379
380 def write(self, *args, **kwargs):
380 def write(self, *args, **kwargs):
381 # Avoid multiple inheritence, be explicit about which
381 # Avoid multiple inheritence, be explicit about which
382 # parent method class gets called
382 # parent method class gets called
383 return ConsoleWidget.write(self, *args, **kwargs)
383 return ConsoleWidget.write(self, *args, **kwargs)
384
384
385
385
386 def _on_key_down(self, event, skip=True):
386 def _on_key_down(self, event, skip=True):
387 """ Capture the character events, let the parent
387 """ Capture the character events, let the parent
388 widget handle them, and put our logic afterward.
388 widget handle them, and put our logic afterward.
389 """
389 """
390 # FIXME: This method needs to be broken down in smaller ones.
390 # FIXME: This method needs to be broken down in smaller ones.
391 current_line_num = self.GetCurrentLine()
391 current_line_num = self.GetCurrentLine()
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
392 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
393 # Capture Control-C
393 # Capture Control-C
394 if self._input_state == 'subprocess':
394 if self._input_state == 'subprocess':
395 if self.debug:
395 if self.debug:
396 print >>sys.__stderr__, 'Killing running process'
396 print >>sys.__stderr__, 'Killing running process'
397 if hasattr(self._running_process, 'process'):
397 if hasattr(self._running_process, 'process'):
398 self._running_process.process.kill()
398 self._running_process.process.kill()
399 elif self._input_state == 'buffering':
399 elif self._input_state == 'buffering':
400 if self.debug:
400 if self.debug:
401 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
401 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
402 raise KeyboardInterrupt
402 raise KeyboardInterrupt
403 # XXX: We need to make really sure we
403 # XXX: We need to make really sure we
404 # get back to a prompt.
404 # get back to a prompt.
405 elif self._input_state == 'subprocess' and (
405 elif self._input_state == 'subprocess' and (
406 ( event.KeyCode<256 and
406 ( event.KeyCode<256 and
407 not event.ControlDown() )
407 not event.ControlDown() )
408 or
408 or
409 ( event.KeyCode in (ord('d'), ord('D')) and
409 ( event.KeyCode in (ord('d'), ord('D')) and
410 event.ControlDown())):
410 event.ControlDown())):
411 # We are running a process, we redirect keys.
411 # We are running a process, we redirect keys.
412 ConsoleWidget._on_key_down(self, event, skip=skip)
412 ConsoleWidget._on_key_down(self, event, skip=skip)
413 char = chr(event.KeyCode)
413 char = chr(event.KeyCode)
414 # Deal with some inconsistency in wx keycodes:
414 # Deal with some inconsistency in wx keycodes:
415 if char == '\r':
415 if char == '\r':
416 char = '\n'
416 char = '\n'
417 elif not event.ShiftDown():
417 elif not event.ShiftDown():
418 char = char.lower()
418 char = char.lower()
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
419 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
420 char = '\04'
420 char = '\04'
421 self._running_process.process.stdin.write(char)
421 self._running_process.process.stdin.write(char)
422 self._running_process.process.stdin.flush()
422 self._running_process.process.stdin.flush()
423 elif event.KeyCode in (ord('('), 57, 53):
423 elif event.KeyCode in (ord('('), 57, 53):
424 # Calltips
424 # Calltips
425 event.Skip()
425 event.Skip()
426 self.do_calltip()
426 self.do_calltip()
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
427 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
428 event.Skip()
428 event.Skip()
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
429 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
430 wx.CallAfter(self._popup_completion, create=True)
430 wx.CallAfter(self._popup_completion, create=True)
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
431 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
432 wx.WXK_RIGHT, wx.WXK_ESCAPE):
433 wx.CallAfter(self._popup_completion)
433 wx.CallAfter(self._popup_completion)
434 else:
434 else:
435 # Up history
435 # Up history
436 if event.KeyCode == wx.WXK_UP and (
436 if event.KeyCode == wx.WXK_UP and (
437 ( current_line_num == self.current_prompt_line and
437 ( current_line_num == self.current_prompt_line and
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
438 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
439 or event.ControlDown() ):
439 or event.ControlDown() ):
440 new_buffer = self.get_history_previous(
440 new_buffer = self.get_history_previous(
441 self.input_buffer)
441 self.input_buffer)
442 if new_buffer is not None:
442 if new_buffer is not None:
443 self.input_buffer = new_buffer
443 self.input_buffer = new_buffer
444 if self.GetCurrentLine() > self.current_prompt_line:
444 if self.GetCurrentLine() > self.current_prompt_line:
445 # Go to first line, for seemless history up.
445 # Go to first line, for seemless history up.
446 self.GotoPos(self.current_prompt_pos)
446 self.GotoPos(self.current_prompt_pos)
447 # Down history
447 # Down history
448 elif event.KeyCode == wx.WXK_DOWN and (
448 elif event.KeyCode == wx.WXK_DOWN and (
449 ( current_line_num == self.LineCount -1 and
449 ( current_line_num == self.LineCount -1 and
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
450 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
451 or event.ControlDown() ):
451 or event.ControlDown() ):
452 new_buffer = self.get_history_next()
452 new_buffer = self.get_history_next()
453 if new_buffer is not None:
453 if new_buffer is not None:
454 self.input_buffer = new_buffer
454 self.input_buffer = new_buffer
455 # Tab-completion
455 # Tab-completion
456 elif event.KeyCode == ord('\t'):
456 elif event.KeyCode == ord('\t'):
457 current_line, current_line_num = self.CurLine
457 current_line, current_line_num = self.CurLine
458 if not re.match(r'^\s*$', current_line):
458 if not re.match(r'^%s\s*$' % self.continuation_prompt(),
459 current_line):
459 self.complete_current_input()
460 self.complete_current_input()
460 if self.AutoCompActive():
461 if self.AutoCompActive():
461 wx.CallAfter(self._popup_completion, create=True)
462 wx.CallAfter(self._popup_completion, create=True)
462 else:
463 else:
463 event.Skip()
464 event.Skip()
464 elif event.KeyCode == wx.WXK_BACK:
465 elif event.KeyCode == wx.WXK_BACK:
465 # If characters where erased, check if we have to
466 # If characters where erased, check if we have to
466 # remove a line.
467 # remove a line.
467 # XXX: What about DEL?
468 # XXX: What about DEL?
468 # FIXME: This logics should be in ConsoleWidget, as it is
469 # FIXME: This logics should be in ConsoleWidget, as it is
469 # independant of IPython
470 # independant of IPython
470 current_line, _ = self.CurLine
471 current_line, _ = self.CurLine
471 current_pos = self.GetCurrentPos()
472 current_pos = self.GetCurrentPos()
472 current_line_num = self.LineFromPosition(current_pos)
473 current_line_num = self.LineFromPosition(current_pos)
473 current_col = self.GetColumn(current_pos)
474 current_col = self.GetColumn(current_pos)
474 len_prompt = len(self.continuation_prompt())
475 len_prompt = len(self.continuation_prompt())
475 if ( current_line.startswith(self.continuation_prompt())
476 if ( current_line.startswith(self.continuation_prompt())
476 and current_col == len_prompt):
477 and current_col == len_prompt):
477 new_lines = []
478 new_lines = []
478 for line_num, line in enumerate(
479 for line_num, line in enumerate(
479 self.input_buffer.split('\n')):
480 self.input_buffer.split('\n')):
480 if (line_num + self.current_prompt_line ==
481 if (line_num + self.current_prompt_line ==
481 current_line_num):
482 current_line_num):
482 new_lines.append(line[len_prompt:])
483 new_lines.append(line[len_prompt:])
483 else:
484 else:
484 new_lines.append('\n'+line)
485 new_lines.append('\n'+line)
485 # The first character is '\n', due to the above
486 # The first character is '\n', due to the above
486 # code:
487 # code:
487 self.input_buffer = ''.join(new_lines)[1:]
488 self.input_buffer = ''.join(new_lines)[1:]
488 self.GotoPos(current_pos - 1 - len_prompt)
489 self.GotoPos(current_pos - 1 - len_prompt)
489 else:
490 else:
490 ConsoleWidget._on_key_down(self, event, skip=skip)
491 ConsoleWidget._on_key_down(self, event, skip=skip)
491 else:
492 else:
492 ConsoleWidget._on_key_down(self, event, skip=skip)
493 ConsoleWidget._on_key_down(self, event, skip=skip)
493
494
494
495
495
496
496 def _on_key_up(self, event, skip=True):
497 def _on_key_up(self, event, skip=True):
497 """ Called when any key is released.
498 """ Called when any key is released.
498 """
499 """
499 if event.KeyCode in (59, ord('.')):
500 if event.KeyCode in (59, ord('.')):
500 # Intercepting '.'
501 # Intercepting '.'
501 event.Skip()
502 event.Skip()
502 wx.CallAfter(self._popup_completion, create=True)
503 wx.CallAfter(self._popup_completion, create=True)
503 else:
504 else:
504 ConsoleWidget._on_key_up(self, event, skip=skip)
505 ConsoleWidget._on_key_up(self, event, skip=skip)
505 # Make sure the continuation_prompts are always followed by a
506 # Make sure the continuation_prompts are always followed by a
506 # whitespace
507 # whitespace
507 new_lines = []
508 new_lines = []
508 if self._input_state == 'readline':
509 if self._input_state == 'readline':
509 position = self.GetCurrentPos()
510 position = self.GetCurrentPos()
510 continuation_prompt = self.continuation_prompt()[:-1]
511 continuation_prompt = self.continuation_prompt()[:-1]
511 for line in self.input_buffer.split('\n'):
512 for line in self.input_buffer.split('\n'):
512 if not line == continuation_prompt:
513 if not line == continuation_prompt:
513 new_lines.append(line)
514 new_lines.append(line)
514 self.input_buffer = '\n'.join(new_lines)
515 self.input_buffer = '\n'.join(new_lines)
515 self.GotoPos(position)
516 self.GotoPos(position)
516
517
517
518
518 def _on_enter(self):
519 def _on_enter(self):
519 """ Called on return key down, in readline input_state.
520 """ Called on return key down, in readline input_state.
520 """
521 """
521 last_line_num = self.LineFromPosition(self.GetLength())
522 last_line_num = self.LineFromPosition(self.GetLength())
522 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 current_line_num = self.LineFromPosition(self.GetCurrentPos())
523 new_line_pos = (last_line_num - current_line_num)
524 new_line_pos = (last_line_num - current_line_num)
524 if self.debug:
525 if self.debug:
525 print >>sys.__stdout__, repr(self.input_buffer)
526 print >>sys.__stdout__, repr(self.input_buffer)
526 self.write('\n', refresh=False)
527 self.write('\n', refresh=False)
527 # Under windows scintilla seems to be doing funny
528 # Under windows scintilla seems to be doing funny
528 # stuff to the line returns here, but the getter for
529 # stuff to the line returns here, but the getter for
529 # input_buffer filters this out.
530 # input_buffer filters this out.
530 if sys.platform == 'win32':
531 if sys.platform == 'win32':
531 self.input_buffer = self.input_buffer
532 self.input_buffer = self.input_buffer
532 old_prompt_num = self.current_prompt_pos
533 old_prompt_num = self.current_prompt_pos
533 has_executed = PrefilterFrontEnd._on_enter(self,
534 has_executed = PrefilterFrontEnd._on_enter(self,
534 new_line_pos=new_line_pos)
535 new_line_pos=new_line_pos)
535 if old_prompt_num == self.current_prompt_pos:
536 if old_prompt_num == self.current_prompt_pos:
536 # No execution has happened
537 # No execution has happened
537 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 self.GotoPos(self.GetLineEndPosition(current_line_num + 1))
538 return has_executed
539 return has_executed
539
540
540
541
541 #--------------------------------------------------------------------------
542 #--------------------------------------------------------------------------
542 # EditWindow API
543 # EditWindow API
543 #--------------------------------------------------------------------------
544 #--------------------------------------------------------------------------
544
545
545 def OnUpdateUI(self, event):
546 def OnUpdateUI(self, event):
546 """ Override the OnUpdateUI of the EditWindow class, to prevent
547 """ Override the OnUpdateUI of the EditWindow class, to prevent
547 syntax highlighting both for faster redraw, and for more
548 syntax highlighting both for faster redraw, and for more
548 consistent look and feel.
549 consistent look and feel.
549 """
550 """
550 if not self._input_state == 'readline':
551 if not self._input_state == 'readline':
551 ConsoleWidget.OnUpdateUI(self, event)
552 ConsoleWidget.OnUpdateUI(self, event)
552
553
553 #--------------------------------------------------------------------------
554 #--------------------------------------------------------------------------
554 # Private API
555 # Private API
555 #--------------------------------------------------------------------------
556 #--------------------------------------------------------------------------
556
557
557 def _buffer_flush(self, event):
558 def _buffer_flush(self, event):
558 """ Called by the timer to flush the write buffer.
559 """ Called by the timer to flush the write buffer.
559
560
560 This is always called in the mainloop, by the wx timer.
561 This is always called in the mainloop, by the wx timer.
561 """
562 """
562 self._out_buffer_lock.acquire()
563 self._out_buffer_lock.acquire()
563 _out_buffer = self._out_buffer
564 _out_buffer = self._out_buffer
564 self._out_buffer = []
565 self._out_buffer = []
565 self._out_buffer_lock.release()
566 self._out_buffer_lock.release()
566 self.write(''.join(_out_buffer), refresh=False)
567 self.write(''.join(_out_buffer), refresh=False)
567
568
568
569
569 def _colorize_input_buffer(self):
570 def _colorize_input_buffer(self):
570 """ Keep the input buffer lines at a bright color.
571 """ Keep the input buffer lines at a bright color.
571 """
572 """
572 if not self._input_state in ('readline', 'raw_input'):
573 if not self._input_state in ('readline', 'raw_input'):
573 return
574 return
574 end_line = self.GetCurrentLine()
575 end_line = self.GetCurrentLine()
575 if not sys.platform == 'win32':
576 if not sys.platform == 'win32':
576 end_line += 1
577 end_line += 1
577 for i in range(self.current_prompt_line, end_line):
578 for i in range(self.current_prompt_line, end_line):
578 if i in self._markers:
579 if i in self._markers:
579 self.MarkerDeleteHandle(self._markers[i])
580 self.MarkerDeleteHandle(self._markers[i])
580 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
581 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
581
582
582
583
583 if __name__ == '__main__':
584 if __name__ == '__main__':
584 class MainWindow(wx.Frame):
585 class MainWindow(wx.Frame):
585 def __init__(self, parent, id, title):
586 def __init__(self, parent, id, title):
586 wx.Frame.__init__(self, parent, id, title, size=(300,250))
587 wx.Frame.__init__(self, parent, id, title, size=(300,250))
587 self._sizer = wx.BoxSizer(wx.VERTICAL)
588 self._sizer = wx.BoxSizer(wx.VERTICAL)
588 self.shell = WxController(self)
589 self.shell = WxController(self)
589 self._sizer.Add(self.shell, 1, wx.EXPAND)
590 self._sizer.Add(self.shell, 1, wx.EXPAND)
590 self.SetSizer(self._sizer)
591 self.SetSizer(self._sizer)
591 self.SetAutoLayout(1)
592 self.SetAutoLayout(1)
592 self.Show(True)
593 self.Show(True)
593
594
594 app = wx.PySimpleApp()
595 app = wx.PySimpleApp()
595 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
596 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
596 frame.shell.SetFocus()
597 frame.shell.SetFocus()
597 frame.SetSize((680, 460))
598 frame.SetSize((680, 460))
598 self = frame.shell
599 self = frame.shell
599
600
600 app.MainLoop()
601 app.MainLoop()
601
602
General Comments 0
You need to be logged in to leave comments. Login now