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