##// END OF EJS Templates
First cut of subprocess execution with redirection of stdin/stdout.
Gael Varoquaux -
Show More
@@ -0,0 +1,48 b''
1 # encoding: utf-8
2 """
3 Object for encapsulating process execution by using callbacks for stdout,
4 stderr and stdin.
5 """
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18 from subprocess import Popen, PIPE
19 from threading import Thread
20
21
22 class PipedProcess(Thread):
23
24 def __init__(self, command_string, out_callback,
25 end_callback=None,):
26 self.command_string = command_string
27 self.out_callback = out_callback
28 self.end_callback = end_callback
29 Thread.__init__(self)
30
31
32 def run(self):
33 """ Start the process and hook up the callbacks.
34 """
35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
36 universal_newlines=True,
37 stdout=PIPE, stdin=PIPE)
38 self.process = process
39 while True:
40 out_char = process.stdout.read(1)
41 if out_char == '' and process.poll() is not None:
42 break
43 self.out_callback(out_char)
44
45 if self.end_callback is not None:
46 self.end_callback()
47
48
@@ -1,159 +1,163 b''
1 1 """
2 2 Frontend class that uses IPython0 to prefilter the inputs.
3 3
4 4 Using the IPython0 mechanism gives us access to the magics.
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 sys
19 19
20 20 from linefrontendbase import LineFrontEndBase, common_prefix
21 21
22 22 from IPython.ipmaker import make_IPython
23 23 from IPython.ipapi import IPApi
24 24 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
25 25
26 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
27
28 from IPython.ultraTB import ColorTB
26 29 from IPython.genutils import Term
27 30 import pydoc
28 31
29 #-------------------------------------------------------------------------------
30 # Utility functions (temporary, should be moved out of here)
31 #-------------------------------------------------------------------------------
32 import os
33 def xterm_system(command):
34 """ Run a command in a separate console window.
35 """
36 os.system(("""xterm -title "%s" -e \'/bin/sh -c "%s ; """
37 """echo; echo press enter to close ; """
38 # """echo \\"\x1b]0;%s (finished -- press enter to close)\x07\\" ;
39 """read foo;"\' """) % (command, command) )
40
41 def system_call(command):
42 """ Temporary hack for aliases
32 def mk_system_call(system_call_function, command):
33 """ given a os.system replacement, and a leading string command,
34 returns a function that will execute the command with the given
35 argument string.
43 36 """
44 37 def my_system_call(args):
45 os.system("%s %s" % (command, args))
38 system_call_function("%s %s" % (command, args))
46 39 return my_system_call
47 40
48 41 #-------------------------------------------------------------------------------
49 42 # Frontend class using ipython0 to do the prefiltering.
50 43 #-------------------------------------------------------------------------------
51 44 class PrefilterFrontEnd(LineFrontEndBase):
52 45
53 46 def __init__(self, *args, **kwargs):
54 47 LineFrontEndBase.__init__(self, *args, **kwargs)
55 48 # Instanciate an IPython0 interpreter to be able to use the
56 49 # prefiltering.
57 50 self.ipython0 = make_IPython()
58 51 # Set the pager:
59 52 self.ipython0.set_hook('show_in_pager',
60 53 lambda s, string: self.write("\n"+string))
61 54 self.ipython0.write = self.write
62 55 self._ip = _ip = IPApi(self.ipython0)
63 56 # XXX: Hack: mix the two namespaces
64 57 self.shell.user_ns = self.ipython0.user_ns
65 58 self.shell.user_global_ns = self.ipython0.user_global_ns
66 59 # Make sure the raw system call doesn't get called, as we don't
67 60 # have a stdin accessible.
68 self._ip.system = xterm_system
61 self._ip.system = self.system_call
69 62 # XXX: Muck around with magics so that they work better
70 63 # in our environment
71 self.ipython0.magic_ls = system_call('ls -CF')
64 self.ipython0.magic_ls = mk_system_call(self.system_call,
65 'ls -CF')
72 66 self.shell.output_trap = RedirectorOutputTrap(
73 67 out_callback=self.write,
74 68 err_callback=self.write,
75 69 )
70 self.shell.traceback_trap = SyncTracebackTrap(
71 formatters=[ColorTB(color_scheme='LightBG'), ]
72 )
76 73 # Capture and release the outputs, to make sure all the
77 74 # shadow variables are set
78 75 self.capture_output()
79 76 self.release_output()
80 77
81 78
82 79 def prefilter_input(self, input_string):
83 80 """ Using IPython0 to prefilter the commands.
84 81 """
85 82 input_string = LineFrontEndBase.prefilter_input(self, input_string)
86 83 filtered_lines = []
87 84 # The IPython0 prefilters sometime produce output. We need to
88 85 # capture it.
89 86 self.capture_output()
90 87 self.last_result = dict(number=self.prompt_number)
91 88 try:
92 89 for line in input_string.split('\n'):
93 90 filtered_lines.append(self.ipython0.prefilter(line, False))
94 91 except:
95 92 # XXX: probably not the right thing to do.
96 93 self.ipython0.showsyntaxerror()
97 94 self.after_execute()
98 95 finally:
99 96 self.release_output()
100 97
101 98 filtered_string = '\n'.join(filtered_lines)
102 99 return filtered_string
103 100
104 101
105 102 def show_traceback(self):
106 103 self.capture_output()
107 104 self.ipython0.showtraceback()
108 105 self.release_output()
109 106
110 107
111 108 def execute(self, python_string, raw_string=None):
112 109 self.capture_output()
113 110 LineFrontEndBase.execute(self, python_string,
114 111 raw_string=raw_string)
115 112 self.release_output()
116 113
117 114
115 def system_call(self, command):
116 """ Allows for frontend to define their own system call, to be
117 able capture output and redirect input.
118 """
119 return os.system(command, args)
120
121
118 122 def capture_output(self):
119 123 """ Capture all the output mechanisms we can think of.
120 124 """
121 125 self.__old_cout_write = Term.cout.write
122 126 self.__old_err_write = Term.cerr.write
123 127 Term.cout.write = self.write
124 128 Term.cerr.write = self.write
125 129 self.__old_stdout = sys.stdout
126 130 self.__old_stderr= sys.stderr
127 131 sys.stdout = Term.cout
128 132 sys.stderr = Term.cerr
129 133 self.__old_help_output = pydoc.help.output
130 134 pydoc.help.output = self.shell.output_trap.out
131 135
132 136
133 137 def release_output(self):
134 138 """ Release all the different captures we have made.
135 139 """
136 140 Term.cout.write = self.__old_cout_write
137 141 Term.cerr.write = self.__old_err_write
138 142 sys.stdout = self.__old_stdout
139 143 sys.stderr = self.__old_stderr
140 144 pydoc.help.output = self.__old_help_output
141 145
142 146
143 147 def complete(self, line):
144 148 word = line.split('\n')[-1].split(' ')[-1]
145 149 completions = self.ipython0.complete(word)
146 150 # FIXME: The proper sort should be done in the complete method.
147 151 key = lambda x: x.replace('_', '')
148 152 completions.sort(key=key)
149 153 if completions:
150 154 prefix = common_prefix(completions)
151 155 line = line[:-len(word)] + prefix
152 156 return line, completions
153 157
154 158
155 159 def do_exit(self):
156 160 """ Exit the shell, cleanup and save the history.
157 161 """
158 162 self.ipython0.atexit_operations()
159 163
@@ -1,430 +1,432 b''
1 1 # encoding: utf-8
2 2 """
3 3 A Wx widget to act as a console and input commands.
4 4
5 5 This widget deals with prompts and provides an edit buffer
6 6 restricted to after the last prompt.
7 7 """
8 8
9 9 __docformat__ = "restructuredtext en"
10 10
11 11 #-------------------------------------------------------------------------------
12 12 # Copyright (C) 2008 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is
15 15 # in the file COPYING, distributed as part of this software.
16 16 #-------------------------------------------------------------------------------
17 17
18 18 #-------------------------------------------------------------------------------
19 19 # Imports
20 20 #-------------------------------------------------------------------------------
21 21
22 22 import wx
23 23 import wx.stc as stc
24 24
25 25 from wx.py import editwindow
26 26 import sys
27 27 LINESEP = '\n'
28 28 if sys.platform == 'win32':
29 29 LINESEP = '\n\r'
30 30
31 31 import re
32 32
33 33 # FIXME: Need to provide an API for non user-generated display on the
34 34 # screen: this should not be editable by the user.
35 35
36 36 _DEFAULT_SIZE = 10
37 37
38 38 _DEFAULT_STYLE = {
39 39 'stdout' : 'fore:#0000FF',
40 40 'stderr' : 'fore:#007f00',
41 41 'trace' : 'fore:#FF0000',
42 42
43 43 'default' : 'size:%d' % _DEFAULT_SIZE,
44 44 'bracegood' : 'fore:#FFFFFF,back:#0000FF,bold',
45 45 'bracebad' : 'fore:#000000,back:#FF0000,bold',
46 46
47 47 # properties for the various Python lexer styles
48 48 'comment' : 'fore:#007F00',
49 49 'number' : 'fore:#007F7F',
50 50 'string' : 'fore:#7F007F,italic',
51 51 'char' : 'fore:#7F007F,italic',
52 52 'keyword' : 'fore:#00007F,bold',
53 53 'triple' : 'fore:#7F0000',
54 54 'tripledouble' : 'fore:#7F0000',
55 55 'class' : 'fore:#0000FF,bold,underline',
56 56 'def' : 'fore:#007F7F,bold',
57 57 'operator' : 'bold'
58 58 }
59 59
60 60 # new style numbers
61 61 _STDOUT_STYLE = 15
62 62 _STDERR_STYLE = 16
63 63 _TRACE_STYLE = 17
64 64
65 65
66 66 # system colors
67 67 #SYS_COLOUR_BACKGROUND = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BACKGROUND)
68 68
69 69 #-------------------------------------------------------------------------------
70 70 # The console widget class
71 71 #-------------------------------------------------------------------------------
72 72 class ConsoleWidget(editwindow.EditWindow):
73 73 """ Specialized styled text control view for console-like workflow.
74 74
75 75 This widget is mainly interested in dealing with the prompt and
76 76 keeping the cursor inside the editing line.
77 77 """
78 78
79 79 title = 'Console'
80 80
81 81 style = _DEFAULT_STYLE.copy()
82 82
83 83 # Translation table from ANSI escape sequences to color. Override
84 84 # this to specify your colors.
85 85 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
86 86 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
87 87 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
88 88 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
89 89 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
90 90 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
91 91 '1;34': [12, 'LIGHT BLUE'], '1;35':
92 92 [13, 'MEDIUM VIOLET RED'],
93 93 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
94 94
95 95 # The color of the carret (call _apply_style() after setting)
96 96 carret_color = 'BLACK'
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 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 105 self.configure_scintilla()
106 106
107 107 # FIXME: we need to retrieve this from the interpreter.
108 108 self.prompt = \
109 109 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 110 self.new_prompt(self.prompt % 1)
111 111
112 112 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 113 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114 114
115 115
116 116 def configure_scintilla(self):
117 117 self.SetEOLMode(stc.STC_EOL_LF)
118 118
119 119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
120 120 # the widget
121 121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
122 122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
123 123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
124 124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
125 125 stc.STC_CMD_ZOOMIN)
126 126
127 127 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
128 128 # stc.STC_CMD_PAGEUP)
129 129
130 130 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
131 131 # stc.STC_CMD_PAGEDOWN)
132 132
133 133 # Keys: we need to clear some of the keys the that don't play
134 134 # well with a console.
135 135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
136 136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
137 137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
138 138
139 139
140 140 self.SetEOLMode(stc.STC_EOL_CRLF)
141 141 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 142 self.SetWrapMode(stc.STC_WRAP_WORD)
143 143 self.SetBufferedDraw(True)
144 144 self.SetUseAntiAliasing(True)
145 145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 146 self.SetUndoCollection(False)
147 147 self.SetUseTabs(True)
148 148 self.SetIndent(4)
149 149 self.SetTabWidth(4)
150 150
151 151 self.EnsureCaretVisible()
152 152 # we don't want scintilla's autocompletion to choose
153 153 # automaticaly out of a single choice list, as we pop it up
154 154 # automaticaly
155 155 self.AutoCompSetChooseSingle(False)
156 156 self.AutoCompSetMaxHeight(10)
157 157
158 158 self.SetMargins(3, 3) #text is moved away from border with 3px
159 159 # Suppressing Scintilla margins
160 160 self.SetMarginWidth(0, 0)
161 161 self.SetMarginWidth(1, 0)
162 162 self.SetMarginWidth(2, 0)
163 163
164 164 self._apply_style()
165 165
166 166 # Xterm escape sequences
167 167 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
168 168 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
169 169
170 170 #self.SetEdgeMode(stc.STC_EDGE_LINE)
171 171 #self.SetEdgeColumn(80)
172 172
173 173 # styles
174 174 p = self.style
175 175 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
176 176 self.StyleClearAll()
177 177 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
178 178 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
179 179 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
180 180
181 181 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
182 182 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
183 183 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
184 184 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
185 185 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
186 186 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
187 187 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
188 188 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
189 189 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
190 190 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
191 191 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
192 192 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
193 193 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
194 194 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
195 195
196 196
197 197 def write(self, text):
198 198 """ Write given text to buffer, while translating the ansi escape
199 199 sequences.
200 200 """
201 # XXX: do not put print statements in this method, the print
202 # statements will call this method, and you will end up with
203 # an infinit loop
201 # XXX: do not put print statements to sys.stdout/sys.stderr in
202 # this method, the print statements will call this method, as
203 # you will end up with an infinit loop
204 if self.debug:
205 print >>sys.__stderr__, text
204 206 title = self.title_pat.split(text)
205 207 if len(title)>1:
206 208 self.title = title[-2]
207 209
208 210 text = self.title_pat.sub('', text)
209 211 segments = self.color_pat.split(text)
210 212 segment = segments.pop(0)
211 213 self.GotoPos(self.GetLength())
212 214 self.StartStyling(self.GetLength(), 0xFF)
213 215 self.AppendText(segment)
214 216
215 217 if segments:
216 218 for ansi_tag, text in zip(segments[::2], segments[1::2]):
217 219 self.StartStyling(self.GetLength(), 0xFF)
218 220 self.AppendText(text)
219 221
220 222 if ansi_tag not in self.ANSI_STYLES:
221 223 style = 0
222 224 else:
223 225 style = self.ANSI_STYLES[ansi_tag][0]
224 226
225 227 self.SetStyling(len(text), style)
226 228
227 229 self.GotoPos(self.GetLength())
228 230 wx.Yield()
229 231
230 232
231 233 def new_prompt(self, prompt):
232 234 """ Prints a prompt at start of line, and move the start of the
233 235 current block there.
234 236
235 237 The prompt can be give with ascii escape sequences.
236 238 """
237 239 self.write(prompt)
238 240 # now we update our cursor giving end of prompt
239 241 self.current_prompt_pos = self.GetLength()
240 242 self.current_prompt_line = self.GetCurrentLine()
241 243 wx.Yield()
242 244 self.EnsureCaretVisible()
243 245
244 246
245 247 def replace_current_edit_buffer(self, text):
246 248 """ Replace currently entered command line with given text.
247 249 """
248 250 self.SetSelection(self.current_prompt_pos, self.GetLength())
249 251 self.ReplaceSelection(text)
250 252 self.GotoPos(self.GetLength())
251 253
252 254
253 255 def get_current_edit_buffer(self):
254 256 """ Returns the text in current edit buffer.
255 257 """
256 258 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
257 259 self.GetLength())
258 260 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
259 261 return current_edit_buffer
260 262
261 263
262 264 #--------------------------------------------------------------------------
263 265 # Private API
264 266 #--------------------------------------------------------------------------
265 267
266 268 def _apply_style(self):
267 269 """ Applies the colors for the different text elements and the
268 270 carret.
269 271 """
270 272 self.SetCaretForeground(self.carret_color)
271 273
272 274 #self.StyleClearAll()
273 275 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
274 276 "fore:#FF0000,back:#0000FF,bold")
275 277 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
276 278 "fore:#000000,back:#FF0000,bold")
277 279
278 280 for style in self.ANSI_STYLES.values():
279 281 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
280 282
281 283
282 284 def write_completion(self, possibilities):
283 285 # FIXME: This is non Wx specific and needs to be moved into
284 286 # the base class.
285 287 current_buffer = self.get_current_edit_buffer()
286 288
287 289 self.write('\n')
288 290 max_len = len(max(possibilities, key=len)) + 1
289 291
290 292 #now we check how much symbol we can put on a line...
291 293 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
292 294 symbols_per_line = max(1, chars_per_line/max_len)
293 295
294 296 pos = 1
295 297 buf = []
296 298 for symbol in possibilities:
297 299 if pos < symbols_per_line:
298 300 buf.append(symbol.ljust(max_len))
299 301 pos += 1
300 302 else:
301 303 buf.append(symbol.rstrip() + '\n')
302 304 pos = 1
303 305 self.write(''.join(buf))
304 306 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
305 307 self.replace_current_edit_buffer(current_buffer)
306 308
307 309
308 310 def pop_completion(self, possibilities, offset=0):
309 311 """ Pops up an autocompletion menu. Offset is the offset
310 312 in characters of the position at which the menu should
311 313 appear, relativ to the cursor.
312 314 """
313 315 self.AutoCompSetIgnoreCase(False)
314 316 self.AutoCompSetAutoHide(False)
315 317 self.AutoCompSetMaxHeight(len(possibilities))
316 318 self.AutoCompShow(offset, " ".join(possibilities))
317 319
318 320
319 321 def scroll_to_bottom(self):
320 322 maxrange = self.GetScrollRange(wx.VERTICAL)
321 323 self.ScrollLines(maxrange)
322 324
323 325
324 326 def _on_key_down(self, event, skip=True):
325 327 """ Key press callback used for correcting behavior for
326 328 console-like interfaces: the cursor is constraint to be after
327 329 the last prompt.
328 330
329 331 Return True if event as been catched.
330 332 """
331 333 catched = True
332 334 # Intercept some specific keys.
333 335 if event.KeyCode == ord('L') and event.ControlDown() :
334 336 self.scroll_to_bottom()
335 337 elif event.KeyCode == ord('K') and event.ControlDown() :
336 338 self.replace_current_edit_buffer('')
337 339 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
338 340 self.ScrollPages(-1)
339 341 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
340 342 self.ScrollPages(1)
341 343 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
342 344 self.ScrollLines(-1)
343 345 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
344 346 self.ScrollLines(1)
345 347 else:
346 348 catched = False
347 349
348 350 if self.AutoCompActive():
349 351 event.Skip()
350 352 else:
351 353 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
352 354 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
353 355 catched = True
354 356 self.CallTipCancel()
355 357 self.write('\n')
356 358 # Under windows scintilla seems to be doing funny stuff to the
357 359 # line returns here, but get_current_edit_buffer filters this
358 360 # out.
359 361 if sys.platform == 'win32':
360 362 self.replace_current_edit_buffer(
361 363 self.get_current_edit_buffer())
362 364 self._on_enter()
363 365
364 366 elif event.KeyCode == wx.WXK_HOME:
365 367 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
366 368 self.GotoPos(self.current_prompt_pos)
367 369 catched = True
368 370
369 371 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
370 372 # FIXME: This behavior is not ideal: if the selection
371 373 # is already started, it will jump.
372 374 self.SetSelectionStart(self.current_prompt_pos)
373 375 self.SetSelectionEnd(self.GetCurrentPos())
374 376 catched = True
375 377
376 378 elif event.KeyCode == wx.WXK_UP:
377 379 if self.GetCurrentLine() > self.current_prompt_line:
378 380 if self.GetCurrentLine() == self.current_prompt_line + 1 \
379 381 and self.GetColumn(self.GetCurrentPos()) < \
380 382 self.GetColumn(self.current_prompt_pos):
381 383 self.GotoPos(self.current_prompt_pos)
382 384 else:
383 385 event.Skip()
384 386 catched = True
385 387
386 388 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
387 389 if self.GetCurrentPos() > self.current_prompt_pos:
388 390 event.Skip()
389 391 catched = True
390 392
391 393 if skip and not catched:
392 394 # Put the cursor back in the edit region
393 395 if self.GetCurrentPos() < self.current_prompt_pos:
394 396 self.GotoPos(self.current_prompt_pos)
395 397 else:
396 398 event.Skip()
397 399
398 400 return catched
399 401
400 402
401 403 def _on_key_up(self, event, skip=True):
402 404 """ If cursor is outside the editing region, put it back.
403 405 """
404 406 event.Skip()
405 407 if self.GetCurrentPos() < self.current_prompt_pos:
406 408 self.GotoPos(self.current_prompt_pos)
407 409
408 410
409 411
410 412
411 413 if __name__ == '__main__':
412 414 # Some simple code to test the console widget.
413 415 class MainWindow(wx.Frame):
414 416 def __init__(self, parent, id, title):
415 417 wx.Frame.__init__(self, parent, id, title, size=(300,250))
416 418 self._sizer = wx.BoxSizer(wx.VERTICAL)
417 419 self.console_widget = ConsoleWidget(self)
418 420 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
419 421 self.SetSizer(self._sizer)
420 422 self.SetAutoLayout(1)
421 423 self.Show(True)
422 424
423 425 app = wx.PySimpleApp()
424 426 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
425 427 w.SetSize((780, 460))
426 428 w.Show()
427 429
428 430 app.MainLoop()
429 431
430 432
@@ -1,73 +1,75 b''
1 1 """
2 2 Entry point for a simple application giving a graphical frontend to
3 3 ipython.
4 4 """
5 5
6 6 import wx
7 7 from wx_frontend import WxController
8 import __builtin__
8 9
9 10 class WIPythonController(WxController):
10 11 """ Sub class of WxController that adds some application-specific
11 12 bindings.
12 13 """
13 14
14 15 def __init__(self, *args, **kwargs):
15 16 WxController.__init__(self, *args, **kwargs)
16 17 self.ipython0.ask_exit = self.do_exit
17 18
18 19
19 20 def _on_key_down(self, event, skip=True):
20 21 # Intercept Ctrl-D to quit
21 22 if event.KeyCode == ord('D') and event.ControlDown() and \
22 self.get_current_edit_buffer()=='':
23 self.get_current_edit_buffer()=='' and \
24 not self.raw_input == __builtin__.raw_input:
23 25 wx.CallAfter(self.ask_exit)
24 26 else:
25 27 WxController._on_key_down(self, event, skip=skip)
26 28
27 29
28 30 def ask_exit(self):
29 31 """ Ask the user whether to exit.
30 32 """
31 33 self.write('\n')
32 34 self.capture_output()
33 35 self.ipython0.shell.exit()
34 36 self.release_output()
35 37 wx.Yield()
36 38 if not self.ipython0.exit_now:
37 39 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
38 40
39 41
40 42 def do_exit(self):
41 43 """ Exits the interpreter, kills the windows.
42 44 """
43 45 WxController.do_exit(self)
44 46 self.release_output()
45 47 wx.CallAfter(wx.Exit)
46 48
47 49
48 50
49 51 class WIPython(wx.Frame):
50 52 """ Main frame of the WIPython app.
51 53 """
52 54
53 55 def __init__(self, parent, id, title):
54 56 wx.Frame.__init__(self, parent, id, title, size=(300,250))
55 57 self._sizer = wx.BoxSizer(wx.VERTICAL)
56 58 self.shell = WIPythonController(self)
57 59 self._sizer.Add(self.shell, 1, wx.EXPAND)
58 60 self.SetSizer(self._sizer)
59 61 self.SetAutoLayout(1)
60 62 self.Show(True)
61 63
62 64
63 65 def main():
64 66 app = wx.PySimpleApp()
65 67 frame = WIPython(None, wx.ID_ANY, 'WIPython')
66 68 frame.shell.SetFocus()
67 69 frame.shell.app = app
68 70 frame.SetSize((680, 460))
69 71
70 72 app.MainLoop()
71 73
72 74 if __name__ == '__main__':
73 75 main()
@@ -1,277 +1,351 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 """
9 9
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 19 #-------------------------------------------------------------------------------
20 20 # Imports
21 21 #-------------------------------------------------------------------------------
22 22
23 23
24 24 import wx
25 25 import re
26 26 from wx import stc
27 27 from console_widget import ConsoleWidget
28 28 import __builtin__
29 29 from time import sleep
30 import sys
30 31
32 from threading import Lock
33
34 from IPython.frontend.piped_process import PipedProcess
31 35 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
32 36
33 37 #_COMMAND_BG = '#FAFAF1' # Nice green
34 38 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
35 39 _ERROR_BG = '#FFF1F1' # Nice red
36 40
37 41 _RUNNING_BUFFER_MARKER = 31
38 42 _ERROR_MARKER = 30
39 43
40 44 #-------------------------------------------------------------------------------
41 45 # Classes to implement the Wx frontend
42 46 #-------------------------------------------------------------------------------
43 47 class WxController(PrefilterFrontEnd, ConsoleWidget):
44 48
45 49 output_prompt = \
46 50 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
47 51
48 52 debug = True
49
53
54 # Attribute to store reference to the pipes of a subprocess, if we
55 # are running any.
56 running_process = False
57
58 # A queue for writing fast streams to the screen without flooding the
59 # event loop
60 write_buffer = []
61
62 # A lock to lock the write_buffer to make sure we don't empty it
63 # while it is being swapped
64 write_buffer_lock = Lock()
65
50 66 #--------------------------------------------------------------------------
51 67 # Public API
52 68 #--------------------------------------------------------------------------
53 69
54 70 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
55 71 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
56 72 *args, **kwds):
57 73 """ Create Shell instance.
58 74 """
59 75 ConsoleWidget.__init__(self, parent, id, pos, size, style)
60 76 PrefilterFrontEnd.__init__(self)
61 77
62 # Capture Character keys
63 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
64
65 78 # Marker for running buffer.
66 79 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
67 80 background=_RUNNING_BUFFER_BG)
68 81 # Marker for tracebacks.
69 82 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
70 83 background=_ERROR_BG)
71 84
85 # A time for flushing the write buffer
86 BUFFER_FLUSH_TIMER_ID = 100
87 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
88 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
72 89
73 90 def do_completion(self):
74 91 """ Do code completion.
75 92 """
93 if self.debug:
94 print >>sys.__stdout__, "do_completion",
76 95 line = self.get_current_edit_buffer()
77 96 new_line, completions = self.complete(line)
78 97 if len(completions)>1:
79 98 self.write_completion(completions)
80 99 self.replace_current_edit_buffer(new_line)
100 if self.debug:
101 print >>sys.__stdout__, completions
81 102
82 103
83 104 def do_calltip(self):
105 if self.debug:
106 print >>sys.__stdout__, "do_calltip"
84 107 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
85 108 symbol = self.get_current_edit_buffer()
86 109 symbol_string = separators.split(symbol)[-1]
87 110 base_symbol_string = symbol_string.split('.')[0]
88 111 if base_symbol_string in self.shell.user_ns:
89 112 symbol = self.shell.user_ns[base_symbol_string]
90 113 elif base_symbol_string in self.shell.user_global_ns:
91 114 symbol = self.shell.user_global_ns[base_symbol_string]
92 115 elif base_symbol_string in __builtin__.__dict__:
93 116 symbol = __builtin__.__dict__[base_symbol_string]
94 117 else:
95 118 return False
96 119 for name in symbol_string.split('.')[1:] + ['__doc__']:
97 120 symbol = getattr(symbol, name)
98 121 try:
99 122 self.AutoCompCancel()
100 123 wx.Yield()
101 124 self.CallTipShow(self.GetCurrentPos(), symbol)
102 125 except:
103 126 # The retrieve symbol couldn't be converted to a string
104 127 pass
105 128
106 129
107 130 def popup_completion(self, create=False):
108 131 """ Updates the popup completion menu if it exists. If create is
109 132 true, open the menu.
110 133 """
134 if self.debug:
135 print >>sys.__stdout__, "popup_completion",
111 136 line = self.get_current_edit_buffer()
112 137 if (self.AutoCompActive() and not line[-1] == '.') \
113 138 or create==True:
114 139 suggestion, completions = self.complete(line)
115 140 offset=0
116 141 if completions:
117 142 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
118 143 residual = complete_sep.split(line)[-1]
119 144 offset = len(residual)
120 145 self.pop_completion(completions, offset=offset)
146 if self.debug:
147 print >>sys.__stdout__, completions
121 148
122 149
123 150 def raw_input(self, prompt):
124 151 """ A replacement from python's raw_input.
125 152 """
126 153 self.new_prompt(prompt)
127 154 self.waiting = True
128 155 self.__old_on_enter = self._on_enter
129 156 def my_on_enter():
130 157 self.waiting = False
131 158 self._on_enter = my_on_enter
132 159 # XXX: Busy waiting, ugly.
133 160 while self.waiting:
134 161 wx.Yield()
135 162 sleep(0.1)
136 163 self._on_enter = self.__old_on_enter
137 164 return self.get_current_edit_buffer().rstrip('\n')
138 165
139 166
140 167 def execute(self, python_string, raw_string=None):
141 168 self.CallTipCancel()
142 169 self._cursor = wx.BusyCursor()
143 170 if raw_string is None:
144 171 raw_string = python_string
145 172 end_line = self.current_prompt_line \
146 173 + max(1, len(raw_string.split('\n'))-1)
147 174 for i in range(self.current_prompt_line, end_line):
148 175 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
149 176 # Update the display:
150 177 wx.Yield()
151 ## Remove the trailing "\n" for cleaner display
152 #self.SetSelection(self.GetLength()-1, self.GetLength())
153 #self.ReplaceSelection('')
154 178 self.GotoPos(self.GetLength())
155 179 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
156 180
157 181
158 182 def capture_output(self):
159 183 self.__old_raw_input = __builtin__.raw_input
160 184 __builtin__.raw_input = self.raw_input
161 185 PrefilterFrontEnd.capture_output(self)
162 186
163 187
164 188 def release_output(self):
165 189 __builtin__.raw_input = self.__old_raw_input
166 190 PrefilterFrontEnd.capture_output(self)
167 191
168 192
169 193 def after_execute(self):
170 194 PrefilterFrontEnd.after_execute(self)
171 195 if hasattr(self, '_cursor'):
172 196 del self._cursor
173 197
198
199 def system_call(self, command_string):
200 self.running_process = PipedProcess(command_string,
201 out_callback=self.buffered_write,
202 end_callback = self._end_system_call)
203 self.running_process.start()
204 # XXX: another one of these polling loops to have a blocking
205 # call
206 while self.running_process:
207 wx.Yield()
208 sleep(0.1)
209
210
211 def buffered_write(self, text):
212 """ A write method for streams, that caches the stream in order
213 to avoid flooding the event loop.
214
215 This can be called outside of the main loop, in separate
216 threads.
217 """
218 self.write_buffer_lock.acquire()
219 self.write_buffer.append(text)
220 self.write_buffer_lock.release()
221 if not self._buffer_flush_timer.IsRunning():
222 self._buffer_flush_timer.Start(100) # milliseconds
223
224
225 def _end_system_call(self):
226 """ Called at the end of a system call.
227 """
228 self.running_process = False
229
230
231 def _buffer_flush(self, event):
232 """ Called by the timer to flush the write buffer.
233
234 This is always called in the mainloop, by the wx timer.
235 """
236 self.write_buffer_lock.acquire()
237 write_buffer = self.write_buffer
238 self.write_buffer = []
239 self.write_buffer_lock.release()
240 self.write(''.join(write_buffer))
241 self._buffer_flush_timer.Stop()
242
174 243
175 244 def show_traceback(self):
176 245 start_line = self.GetCurrentLine()
177 246 PrefilterFrontEnd.show_traceback(self)
178 247 wx.Yield()
179 248 for i in range(start_line, self.GetCurrentLine()):
180 249 self.MarkerAdd(i, _ERROR_MARKER)
181 250
182 251
183 252 #--------------------------------------------------------------------------
184 253 # Private API
185 254 #--------------------------------------------------------------------------
186 255
187
188 256 def _on_key_down(self, event, skip=True):
189 257 """ Capture the character events, let the parent
190 258 widget handle them, and put our logic afterward.
191 259 """
192 260 current_line_number = self.GetCurrentLine()
193 # Calltips
194 if event.KeyCode == ord('('):
261 if self.running_process and event.KeyCode<256 \
262 and event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
263 # We are running a process, let us not be too clever.
264 ConsoleWidget._on_key_down(self, event, skip=skip)
265 if self.debug:
266 print >>sys.__stderr__, chr(event.KeyCode)
267 self.running_process.process.stdin.write(chr(event.KeyCode))
268 elif event.KeyCode in (ord('('), 57):
269 # Calltips
195 270 event.Skip()
196 271 self.do_calltip()
197 272 elif self.AutoCompActive():
198 273 event.Skip()
199 274 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
200 275 wx.CallAfter(self.popup_completion, create=True)
201 276 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
202 277 wx.WXK_RIGHT):
203 278 wx.CallAfter(self.popup_completion)
204 279 else:
205 280 # Up history
206 281 if event.KeyCode == wx.WXK_UP and (
207 282 ( current_line_number == self.current_prompt_line and
208 283 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
209 284 or event.ControlDown() ):
210 285 new_buffer = self.get_history_previous(
211 286 self.get_current_edit_buffer())
212 287 if new_buffer is not None:
213 288 self.replace_current_edit_buffer(new_buffer)
214 289 if self.GetCurrentLine() > self.current_prompt_line:
215 290 # Go to first line, for seemless history up.
216 291 self.GotoPos(self.current_prompt_pos)
217 292 # Down history
218 293 elif event.KeyCode == wx.WXK_DOWN and (
219 294 ( current_line_number == self.LineCount -1 and
220 295 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
221 296 or event.ControlDown() ):
222 297 new_buffer = self.get_history_next()
223 298 if new_buffer is not None:
224 299 self.replace_current_edit_buffer(new_buffer)
225 300 # Tab-completion
226 301 elif event.KeyCode == ord('\t'):
227 302 last_line = self.get_current_edit_buffer().split('\n')[-1]
228 303 if not re.match(r'^\s*$', last_line):
229 304 self.do_completion()
230 305 else:
231 306 event.Skip()
232 307 else:
233 308 ConsoleWidget._on_key_down(self, event, skip=skip)
234 309
235 310
236 311 def _on_key_up(self, event, skip=True):
237 if event.KeyCode == 59:
312 if event.KeyCode in (59, ord('.')):
238 313 # Intercepting '.'
239 314 event.Skip()
240 315 self.popup_completion(create=True)
241 316 else:
242 317 ConsoleWidget._on_key_up(self, event, skip=skip)
243 318
244 319 def _on_enter(self):
245 320 if self.debug:
246 import sys
247 321 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
248 322 PrefilterFrontEnd._on_enter(self)
249 323
250 324 def _set_title(self, title):
251 325 return self.Parent.SetTitle(title)
252 326
253 327 def _get_title(self):
254 328 return self.Parent.GetTitle()
255 329
256 330 title = property(_get_title, _set_title)
257 331
258 332
259 333 if __name__ == '__main__':
260 334 class MainWindow(wx.Frame):
261 335 def __init__(self, parent, id, title):
262 336 wx.Frame.__init__(self, parent, id, title, size=(300,250))
263 337 self._sizer = wx.BoxSizer(wx.VERTICAL)
264 338 self.shell = WxController(self)
265 339 self._sizer.Add(self.shell, 1, wx.EXPAND)
266 340 self.SetSizer(self._sizer)
267 341 self.SetAutoLayout(1)
268 342 self.Show(True)
269 343
270 344 app = wx.PySimpleApp()
271 345 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
272 346 frame.shell.SetFocus()
273 347 frame.SetSize((680, 460))
274 348 self = frame.shell
275 349
276 350 app.MainLoop()
277 351
@@ -1,85 +1,80 b''
1 1 # encoding: utf-8
2 2
3 3 """Object to manage sys.excepthook()."""
4 4
5 5 __docformat__ = "restructuredtext en"
6 6
7 7 #-------------------------------------------------------------------------------
8 8 # Copyright (C) 2008 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-------------------------------------------------------------------------------
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17
18 17 import sys
19 18
20
21 19 class TracebackTrap(object):
22 20 """ Object to trap and format tracebacks.
23 21 """
24 22
25 23 def __init__(self, formatters=None):
26 24 # A list of formatters to apply.
27 25 if formatters is None:
28 26 formatters = []
29 27 self.formatters = formatters
30 28
31 29 # All of the traceback information provided to sys.excepthook().
32 30 self.args = None
33 31
34 32 # The previous hook before we replace it.
35 33 self.old_hook = None
36 34
37 35
38 36 def hook(self, *args):
39 37 """ This method actually implements the hook.
40 38 """
41 import sys
42 print >>sys.stderr, "I have been raised"
43
44 39 self.args = args
45 40
46 41 def set(self):
47 42 """ Set the hook.
48 43 """
49 44
50 45 if sys.excepthook is not self.hook:
51 46 self.old_hook = sys.excepthook
52 47 sys.excepthook = self.hook
53 48
54 49 def unset(self):
55 50 """ Unset the hook.
56 51 """
57 52
58 53 sys.excepthook = self.old_hook
59 54
60 55 def clear(self):
61 56 """ Remove the stored traceback.
62 57 """
63 58
64 59 self.args = None
65 60
66 61 def add_to_message(self, message):
67 62 """ Add the formatted display of the traceback to the message dictionary
68 63 being returned from the interpreter to its listeners.
69 64
70 65 Parameters
71 66 ----------
72 67 message : dict
73 68 """
74 69
75 70 # If there was no traceback, then don't add anything.
76 71 if self.args is None:
77 72 return
78 73
79 74 # Go through the list of formatters and let them add their formatting.
80 75 traceback = {}
81 76 for formatter in self.formatters:
82 77 traceback[formatter.identifier] = formatter(*self.args)
83 78
84 79 message['traceback'] = traceback
85 80
General Comments 0
You need to be logged in to leave comments. Login now