##// END OF EJS Templates
Capture errors in user's input lines and raises meaningfull exceptions.
gvaroquaux -
Show More
@@ -1,311 +1,320 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import IPython
22 22 import sys
23 23 import codeop
24 import traceback
24 25
25 26 from frontendbase import FrontEndBase
26 27 from IPython.kernel.core.interpreter import Interpreter
27 28
28 29 def common_prefix(strings):
29 30 """ Given a list of strings, return the common prefix between all
30 31 these strings.
31 32 """
32 33 ref = strings[0]
33 34 prefix = ''
34 35 for size in range(len(ref)):
35 36 test_prefix = ref[:size+1]
36 37 for string in strings[1:]:
37 38 if not string.startswith(test_prefix):
38 39 return prefix
39 40 prefix = test_prefix
40 41
41 42 return prefix
42 43
43 44 #-------------------------------------------------------------------------------
44 45 # Base class for the line-oriented front ends
45 46 #-------------------------------------------------------------------------------
46 47 class LineFrontEndBase(FrontEndBase):
47 48 """ Concrete implementation of the FrontEndBase class. This is meant
48 49 to be the base class behind all the frontend that are line-oriented,
49 50 rather than block-oriented.
50 51 """
51 52
52 53 # We need to keep the prompt number, to be able to increment
53 54 # it when there is an exception.
54 55 prompt_number = 1
55 56
56 57 # We keep a reference to the last result: it helps testing and
57 58 # programatic control of the frontend.
58 59 last_result = dict(number=0)
59 60
60 61 # The input buffer being edited
61 62 input_buffer = ''
62 63
63 64 # Set to true for debug output
64 65 debug = False
65 66
66 67 # A banner to print at startup
67 68 banner = None
68 69
69 70 #--------------------------------------------------------------------------
70 71 # FrontEndBase interface
71 72 #--------------------------------------------------------------------------
72 73
73 74 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
74 75 if shell is None:
75 76 shell = Interpreter()
76 77 FrontEndBase.__init__(self, shell=shell, history=history)
77 78
78 79 if banner is not None:
79 80 self.banner = banner
80 81
81 82 def start(self):
82 83 """ Put the frontend in a state where it is ready for user
83 84 interaction.
84 85 """
85 86 if self.banner is not None:
86 87 self.write(self.banner, refresh=False)
87 88
88 89 self.new_prompt(self.input_prompt_template.substitute(number=1))
89 90
90 91
91 92 def complete(self, line):
92 93 """Complete line in engine's user_ns
93 94
94 95 Parameters
95 96 ----------
96 97 line : string
97 98
98 99 Result
99 100 ------
100 101 The replacement for the line and the list of possible completions.
101 102 """
102 103 completions = self.shell.complete(line)
103 104 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
104 105 if completions:
105 106 prefix = common_prefix(completions)
106 107 residual = complete_sep.split(line)[:-1]
107 108 line = line[:-len(residual)] + prefix
108 109 return line, completions
109 110
110 111
111 112 def render_result(self, result):
112 113 """ Frontend-specific rendering of the result of a calculation
113 114 that has been sent to an engine.
114 115 """
115 116 if 'stdout' in result and result['stdout']:
116 117 self.write('\n' + result['stdout'])
117 118 if 'display' in result and result['display']:
118 119 self.write("%s%s\n" % (
119 120 self.output_prompt_template.substitute(
120 121 number=result['number']),
121 122 result['display']['pprint']
122 123 ) )
123 124
124 125
125 126 def render_error(self, failure):
126 127 """ Frontend-specific rendering of error.
127 128 """
128 129 self.write('\n\n'+str(failure)+'\n\n')
129 130 return failure
130 131
131 132
132 133 def is_complete(self, string):
133 134 """ Check if a string forms a complete, executable set of
134 135 commands.
135 136
136 137 For the line-oriented frontend, multi-line code is not executed
137 138 as soon as it is complete: the users has to enter two line
138 139 returns.
139 140 """
140 141 if string in ('', '\n'):
141 142 # Prefiltering, eg through ipython0, may return an empty
142 143 # string although some operations have been accomplished. We
143 144 # thus want to consider an empty string as a complete
144 145 # statement.
145 146 return True
146 147 elif ( len(self.input_buffer.split('\n'))>2
147 148 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
148 149 return False
149 150 else:
150 # Add line returns here, to make sure that the statement is
151 # complete.
152 return codeop.compile_command(string.rstrip() + '\n\n',
153 "<string>", "exec")
151 self.capture_output()
152 try:
153 # Add line returns here, to make sure that the statement is
154 # complete.
155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 "<string>", "exec")
157 self.release_output()
158 except Exception, e:
159 # XXX: Hack: return True so that the
160 # code gets executed and the error captured.
161 is_complete = True
162 return is_complete
154 163
155 164
156 165 def write(self, string, refresh=True):
157 166 """ Write some characters to the display.
158 167
159 168 Subclass should overide this method.
160 169
161 170 The refresh keyword argument is used in frontends with an
162 171 event loop, to choose whether the write should trigget an UI
163 172 refresh, and thus be syncrhonous, or not.
164 173 """
165 174 print >>sys.__stderr__, string
166 175
167 176
168 177 def execute(self, python_string, raw_string=None):
169 178 """ Stores the raw_string in the history, and sends the
170 179 python string to the interpreter.
171 180 """
172 181 if raw_string is None:
173 182 raw_string = python_string
174 183 # Create a false result, in case there is an exception
175 184 self.last_result = dict(number=self.prompt_number)
176 185 try:
177 186 self.history.input_cache[-1] = raw_string.rstrip()
178 187 result = self.shell.execute(python_string)
179 188 self.last_result = result
180 189 self.render_result(result)
181 190 except:
182 191 self.show_traceback()
183 192 finally:
184 193 self.after_execute()
185 194
186 195 #--------------------------------------------------------------------------
187 196 # LineFrontEndBase interface
188 197 #--------------------------------------------------------------------------
189 198
190 199 def prefilter_input(self, string):
191 200 """ Prefilter the input to turn it in valid python.
192 201 """
193 202 string = string.replace('\r\n', '\n')
194 203 string = string.replace('\t', 4*' ')
195 204 # Clean the trailing whitespace
196 205 string = '\n'.join(l.rstrip() for l in string.split('\n'))
197 206 return string
198 207
199 208
200 209 def after_execute(self):
201 210 """ All the operations required after an execution to put the
202 211 terminal back in a shape where it is usable.
203 212 """
204 213 self.prompt_number += 1
205 214 self.new_prompt(self.input_prompt_template.substitute(
206 215 number=(self.last_result['number'] + 1)))
207 216 # Start a new empty history entry
208 217 self._add_history(None, '')
209 218 self.history_cursor = len(self.history.input_cache) - 1
210 219
211 220
212 221 def complete_current_input(self):
213 222 """ Do code completion on current line.
214 223 """
215 224 if self.debug:
216 225 print >>sys.__stdout__, "complete_current_input",
217 226 line = self.input_buffer
218 227 new_line, completions = self.complete(line)
219 228 if len(completions)>1:
220 229 self.write_completion(completions, new_line=new_line)
221 230 elif not line == new_line:
222 231 self.input_buffer = new_line
223 232 if self.debug:
224 233 print >>sys.__stdout__, 'line', line
225 234 print >>sys.__stdout__, 'new_line', new_line
226 235 print >>sys.__stdout__, completions
227 236
228 237
229 238 def get_line_width(self):
230 239 """ Return the width of the line in characters.
231 240 """
232 241 return 80
233 242
234 243
235 244 def write_completion(self, possibilities, new_line=None):
236 245 """ Write the list of possible completions.
237 246
238 247 new_line is the completed input line that should be displayed
239 248 after the completion are writen. If None, the input_buffer
240 249 before the completion is used.
241 250 """
242 251 if new_line is None:
243 252 new_line = self.input_buffer
244 253
245 254 self.write('\n')
246 255 max_len = len(max(possibilities, key=len)) + 1
247 256
248 257 # Now we check how much symbol we can put on a line...
249 258 chars_per_line = self.get_line_width()
250 259 symbols_per_line = max(1, chars_per_line/max_len)
251 260
252 261 pos = 1
253 262 buf = []
254 263 for symbol in possibilities:
255 264 if pos < symbols_per_line:
256 265 buf.append(symbol.ljust(max_len))
257 266 pos += 1
258 267 else:
259 268 buf.append(symbol.rstrip() + '\n')
260 269 pos = 1
261 270 self.write(''.join(buf))
262 271 self.new_prompt(self.input_prompt_template.substitute(
263 272 number=self.last_result['number'] + 1))
264 273 self.input_buffer = new_line
265 274
266 275
267 276 def new_prompt(self, prompt):
268 277 """ Prints a prompt and starts a new editing buffer.
269 278
270 279 Subclasses should use this method to make sure that the
271 280 terminal is put in a state favorable for a new line
272 281 input.
273 282 """
274 283 self.input_buffer = ''
275 284 self.write(prompt)
276 285
277 286
278 287 #--------------------------------------------------------------------------
279 288 # Private API
280 289 #--------------------------------------------------------------------------
281 290
282 291 def _on_enter(self):
283 292 """ Called when the return key is pressed in a line editing
284 293 buffer.
285 294 """
286 295 current_buffer = self.input_buffer
287 296 cleaned_buffer = self.prefilter_input(current_buffer)
288 297 if self.is_complete(cleaned_buffer):
289 298 self.execute(cleaned_buffer, raw_string=current_buffer)
290 299 else:
291 300 self.input_buffer += self._get_indent_string(
292 301 current_buffer[:-1])
293 302 if len(current_buffer.split('\n')) == 2:
294 303 self.input_buffer += '\t\t'
295 304 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
296 305 self.input_buffer += '\t'
297 306
298 307
299 308 def _get_indent_string(self, string):
300 309 """ Return the string of whitespace that prefixes a line. Used to
301 310 add the right amount of indendation when creating a new line.
302 311 """
303 312 string = string.replace('\t', ' '*4)
304 313 string = string.split('\n')[-1]
305 314 indent_chars = len(string) - len(string.lstrip())
306 315 indent_string = '\t'*(indent_chars // 4) + \
307 316 ' '*(indent_chars % 4)
308 317
309 318 return indent_string
310 319
311 320
@@ -1,512 +1,523 b''
1 1 # encoding: utf-8 -*- test-case-name:
2 2 # FIXME: Need to add tests.
3 3 # ipython1.frontend.wx.tests.test_wx_frontend -*-
4 4
5 5 """Classes to provide a Wx frontend to the
6 6 IPython.kernel.core.interpreter.
7 7
8 8 This class inherits from ConsoleWidget, that provides a console-like
9 9 widget to provide a text-rendering widget suitable for a terminal.
10 10 """
11 11
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24
25 25 # Major library imports
26 26 import re
27 27 import __builtin__
28 28 from time import sleep
29 29 import sys
30 30 from threading import Lock
31 31 import string
32 32
33 33 import wx
34 34 from wx import stc
35 35
36 36 # Ipython-specific imports.
37 37 from IPython.frontend._process import PipedProcess
38 38 from console_widget import ConsoleWidget
39 39 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
40 40
41 41 #-------------------------------------------------------------------------------
42 42 # Constants
43 43 #-------------------------------------------------------------------------------
44 44
45 45 _COMPLETE_BUFFER_BG = '#FAFAF1' # Nice green
46 46 _INPUT_BUFFER_BG = '#FDFFD3' # Nice yellow
47 47 _ERROR_BG = '#FFF1F1' # Nice red
48 48
49 49 _COMPLETE_BUFFER_MARKER = 31
50 50 _ERROR_MARKER = 30
51 51 _INPUT_MARKER = 29
52 52
53 53 prompt_in1 = \
54 54 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02$number\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
55 55
56 56 prompt_out = \
57 57 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02$number\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
58 58
59 59 #-------------------------------------------------------------------------------
60 60 # Classes to implement the Wx frontend
61 61 #-------------------------------------------------------------------------------
62 62 class WxController(ConsoleWidget, PrefilterFrontEnd):
63 63 """Classes to provide a Wx frontend to the
64 64 IPython.kernel.core.interpreter.
65 65
66 66 This class inherits from ConsoleWidget, that provides a console-like
67 67 widget to provide a text-rendering widget suitable for a terminal.
68 68 """
69 69
70 70 output_prompt_template = string.Template(prompt_out)
71 71
72 72 input_prompt_template = string.Template(prompt_in1)
73 73
74 74 # Print debug info on what is happening to the console.
75 75 debug = False
76 76
77 77 # The title of the terminal, as captured through the ANSI escape
78 78 # sequences.
79 79 def _set_title(self, title):
80 80 return self.Parent.SetTitle(title)
81 81
82 82 def _get_title(self):
83 83 return self.Parent.GetTitle()
84 84
85 85 title = property(_get_title, _set_title)
86 86
87 87
88 88 # The buffer being edited.
89 89 # We are duplicating the definition here because of multiple
90 90 # inheritence
91 91 def _set_input_buffer(self, string):
92 92 ConsoleWidget._set_input_buffer(self, string)
93 93 self._colorize_input_buffer()
94 94
95 95 def _get_input_buffer(self):
96 96 """ Returns the text in current edit buffer.
97 97 """
98 98 return ConsoleWidget._get_input_buffer(self)
99 99
100 100 input_buffer = property(_get_input_buffer, _set_input_buffer)
101 101
102 102
103 103 #--------------------------------------------------------------------------
104 104 # Private Attributes
105 105 #--------------------------------------------------------------------------
106 106
107 107 # A flag governing the behavior of the input. Can be:
108 108 #
109 109 # 'readline' for readline-like behavior with a prompt
110 110 # and an edit buffer.
111 111 # 'raw_input' similar to readline, but triggered by a raw-input
112 112 # call. Can be used by subclasses to act differently.
113 113 # 'subprocess' for sending the raw input directly to a
114 114 # subprocess.
115 115 # 'buffering' for buffering of the input, that will be used
116 116 # when the input state switches back to another state.
117 117 _input_state = 'readline'
118 118
119 119 # Attribute to store reference to the pipes of a subprocess, if we
120 120 # are running any.
121 121 _running_process = False
122 122
123 123 # A queue for writing fast streams to the screen without flooding the
124 124 # event loop
125 125 _out_buffer = []
126 126
127 127 # A lock to lock the _out_buffer to make sure we don't empty it
128 128 # while it is being swapped
129 129 _out_buffer_lock = Lock()
130 130
131 131 # The different line markers used to higlight the prompts.
132 132 _markers = dict()
133 133
134 134 #--------------------------------------------------------------------------
135 135 # Public API
136 136 #--------------------------------------------------------------------------
137 137
138 138 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
139 139 size=wx.DefaultSize,
140 140 style=wx.CLIP_CHILDREN|wx.WANTS_CHARS,
141 141 *args, **kwds):
142 142 """ Create Shell instance.
143 143 """
144 144 ConsoleWidget.__init__(self, parent, id, pos, size, style)
145 145 PrefilterFrontEnd.__init__(self, **kwds)
146 146
147 147 # Marker for complete buffer.
148 148 self.MarkerDefine(_COMPLETE_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
149 149 background=_COMPLETE_BUFFER_BG)
150 150 # Marker for current input buffer.
151 151 self.MarkerDefine(_INPUT_MARKER, stc.STC_MARK_BACKGROUND,
152 152 background=_INPUT_BUFFER_BG)
153 153 # Marker for tracebacks.
154 154 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
155 155 background=_ERROR_BG)
156 156
157 157 # A time for flushing the write buffer
158 158 BUFFER_FLUSH_TIMER_ID = 100
159 159 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
160 160 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
161 161
162 162 if 'debug' in kwds:
163 163 self.debug = kwds['debug']
164 164 kwds.pop('debug')
165 165
166 166 # Inject self in namespace, for debug
167 167 if self.debug:
168 168 self.shell.user_ns['self'] = self
169 169
170 170
171 171 def raw_input(self, prompt):
172 172 """ A replacement from python's raw_input.
173 173 """
174 174 self.new_prompt(prompt)
175 175 self._input_state = 'raw_input'
176 176 if hasattr(self, '_cursor'):
177 177 del self._cursor
178 178 self.SetCursor(wx.StockCursor(wx.CURSOR_CROSS))
179 179 self.waiting = True
180 180 self.__old_on_enter = self._on_enter
181 181 def my_on_enter():
182 182 self.waiting = False
183 183 self._on_enter = my_on_enter
184 184 # XXX: Busy waiting, ugly.
185 185 while self.waiting:
186 186 wx.Yield()
187 187 sleep(0.1)
188 188 self._on_enter = self.__old_on_enter
189 189 self._input_state = 'buffering'
190 190 self._cursor = wx.BusyCursor()
191 191 return self.input_buffer.rstrip('\n')
192 192
193 193
194 194 def system_call(self, command_string):
195 195 self._input_state = 'subprocess'
196 196 self._running_process = PipedProcess(command_string,
197 197 out_callback=self.buffered_write,
198 198 end_callback = self._end_system_call)
199 199 self._running_process.start()
200 200 # XXX: another one of these polling loops to have a blocking
201 201 # call
202 202 wx.Yield()
203 203 while self._running_process:
204 204 wx.Yield()
205 205 sleep(0.1)
206 206 # Be sure to flush the buffer.
207 207 self._buffer_flush(event=None)
208 208
209 209
210 210 def do_calltip(self):
211 211 """ Analyse current and displays useful calltip for it.
212 212 """
213 213 if self.debug:
214 214 print >>sys.__stdout__, "do_calltip"
215 215 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
216 216 symbol = self.input_buffer
217 217 symbol_string = separators.split(symbol)[-1]
218 218 base_symbol_string = symbol_string.split('.')[0]
219 219 if base_symbol_string in self.shell.user_ns:
220 220 symbol = self.shell.user_ns[base_symbol_string]
221 221 elif base_symbol_string in self.shell.user_global_ns:
222 222 symbol = self.shell.user_global_ns[base_symbol_string]
223 223 elif base_symbol_string in __builtin__.__dict__:
224 224 symbol = __builtin__.__dict__[base_symbol_string]
225 225 else:
226 226 return False
227 227 try:
228 228 for name in symbol_string.split('.')[1:] + ['__doc__']:
229 229 symbol = getattr(symbol, name)
230 230 self.AutoCompCancel()
231 231 wx.Yield()
232 232 self.CallTipShow(self.GetCurrentPos(), symbol)
233 233 except:
234 234 # The retrieve symbol couldn't be converted to a string
235 235 pass
236 236
237 237
238 238 def _popup_completion(self, create=False):
239 239 """ Updates the popup completion menu if it exists. If create is
240 240 true, open the menu.
241 241 """
242 242 if self.debug:
243 243 print >>sys.__stdout__, "_popup_completion"
244 244 line = self.input_buffer
245 245 if (self.AutoCompActive() and line and not line[-1] == '.') \
246 246 or create==True:
247 247 suggestion, completions = self.complete(line)
248 248 offset=0
249 249 if completions:
250 250 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
251 251 residual = complete_sep.split(line)[-1]
252 252 offset = len(residual)
253 253 self.pop_completion(completions, offset=offset)
254 254 if self.debug:
255 255 print >>sys.__stdout__, completions
256 256
257 257
258 258 def buffered_write(self, text):
259 259 """ A write method for streams, that caches the stream in order
260 260 to avoid flooding the event loop.
261 261
262 262 This can be called outside of the main loop, in separate
263 263 threads.
264 264 """
265 265 self._out_buffer_lock.acquire()
266 266 self._out_buffer.append(text)
267 267 self._out_buffer_lock.release()
268 268 if not self._buffer_flush_timer.IsRunning():
269 269 wx.CallAfter(self._buffer_flush_timer.Start,
270 270 milliseconds=100, oneShot=True)
271 271
272 272
273 273 #--------------------------------------------------------------------------
274 274 # LineFrontEnd interface
275 275 #--------------------------------------------------------------------------
276 276
277 277 def execute(self, python_string, raw_string=None):
278 278 self._input_state = 'buffering'
279 279 self.CallTipCancel()
280 280 self._cursor = wx.BusyCursor()
281 281 if raw_string is None:
282 282 raw_string = python_string
283 283 end_line = self.current_prompt_line \
284 284 + max(1, len(raw_string.split('\n'))-1)
285 285 for i in range(self.current_prompt_line, end_line):
286 286 if i in self._markers:
287 287 self.MarkerDeleteHandle(self._markers[i])
288 288 self._markers[i] = self.MarkerAdd(i, _COMPLETE_BUFFER_MARKER)
289 289 # Update the display:
290 290 wx.Yield()
291 291 self.GotoPos(self.GetLength())
292 292 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
293 293
294 294 def save_output_hooks(self):
295 295 self.__old_raw_input = __builtin__.raw_input
296 296 PrefilterFrontEnd.save_output_hooks(self)
297 297
298 298 def capture_output(self):
299 299 __builtin__.raw_input = self.raw_input
300 300 self.SetLexer(stc.STC_LEX_NULL)
301 301 PrefilterFrontEnd.capture_output(self)
302 302
303 303
304 304 def release_output(self):
305 305 __builtin__.raw_input = self.__old_raw_input
306 306 PrefilterFrontEnd.release_output(self)
307 307 self.SetLexer(stc.STC_LEX_PYTHON)
308 308
309 309
310 310 def after_execute(self):
311 311 PrefilterFrontEnd.after_execute(self)
312 312 # Clear the wait cursor
313 313 if hasattr(self, '_cursor'):
314 314 del self._cursor
315 315 self.SetCursor(wx.StockCursor(wx.CURSOR_CHAR))
316 316
317 317
318 318 def show_traceback(self):
319 319 start_line = self.GetCurrentLine()
320 320 PrefilterFrontEnd.show_traceback(self)
321 321 wx.Yield()
322 322 for i in range(start_line, self.GetCurrentLine()):
323 323 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
324 324
325 325
326 326 #--------------------------------------------------------------------------
327 # FrontEndBase interface
328 #--------------------------------------------------------------------------
329
330 def render_error(self, e):
331 start_line = self.GetCurrentLine()
332 self.write('\n' + e + '\n')
333 for i in range(start_line, self.GetCurrentLine()):
334 self._markers[i] = self.MarkerAdd(i, _ERROR_MARKER)
335
336
337 #--------------------------------------------------------------------------
327 338 # ConsoleWidget interface
328 339 #--------------------------------------------------------------------------
329 340
330 341 def new_prompt(self, prompt):
331 342 """ Display a new prompt, and start a new input buffer.
332 343 """
333 344 self._input_state = 'readline'
334 345 ConsoleWidget.new_prompt(self, prompt)
335 346 i = self.current_prompt_line
336 347 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
337 348
338 349
339 350 def write(self, *args, **kwargs):
340 351 # Avoid multiple inheritence, be explicit about which
341 352 # parent method class gets called
342 353 ConsoleWidget.write(self, *args, **kwargs)
343 354
344 355
345 356 def _on_key_down(self, event, skip=True):
346 357 """ Capture the character events, let the parent
347 358 widget handle them, and put our logic afterward.
348 359 """
349 360 # FIXME: This method needs to be broken down in smaller ones.
350 361 current_line_number = self.GetCurrentLine()
351 362 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
352 363 # Capture Control-C
353 364 if self._input_state == 'subprocess':
354 365 if self.debug:
355 366 print >>sys.__stderr__, 'Killing running process'
356 367 self._running_process.process.kill()
357 368 elif self._input_state == 'buffering':
358 369 if self.debug:
359 370 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
360 371 raise KeyboardInterrupt
361 372 # XXX: We need to make really sure we
362 373 # get back to a prompt.
363 374 elif self._input_state == 'subprocess' and (
364 375 ( event.KeyCode<256 and
365 376 not event.ControlDown() )
366 377 or
367 378 ( event.KeyCode in (ord('d'), ord('D')) and
368 379 event.ControlDown())):
369 380 # We are running a process, we redirect keys.
370 381 ConsoleWidget._on_key_down(self, event, skip=skip)
371 382 char = chr(event.KeyCode)
372 383 # Deal with some inconsistency in wx keycodes:
373 384 if char == '\r':
374 385 char = '\n'
375 386 elif not event.ShiftDown():
376 387 char = char.lower()
377 388 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
378 389 char = '\04'
379 390 self._running_process.process.stdin.write(char)
380 391 self._running_process.process.stdin.flush()
381 392 elif event.KeyCode in (ord('('), 57):
382 393 # Calltips
383 394 event.Skip()
384 395 self.do_calltip()
385 396 elif self.AutoCompActive() and not event.KeyCode == ord('\t'):
386 397 event.Skip()
387 398 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
388 399 wx.CallAfter(self._popup_completion, create=True)
389 400 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
390 401 wx.WXK_RIGHT, wx.WXK_ESCAPE):
391 402 wx.CallAfter(self._popup_completion)
392 403 else:
393 404 # Up history
394 405 if event.KeyCode == wx.WXK_UP and (
395 406 ( current_line_number == self.current_prompt_line and
396 407 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
397 408 or event.ControlDown() ):
398 409 new_buffer = self.get_history_previous(
399 410 self.input_buffer)
400 411 if new_buffer is not None:
401 412 self.input_buffer = new_buffer
402 413 if self.GetCurrentLine() > self.current_prompt_line:
403 414 # Go to first line, for seemless history up.
404 415 self.GotoPos(self.current_prompt_pos)
405 416 # Down history
406 417 elif event.KeyCode == wx.WXK_DOWN and (
407 418 ( current_line_number == self.LineCount -1 and
408 419 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
409 420 or event.ControlDown() ):
410 421 new_buffer = self.get_history_next()
411 422 if new_buffer is not None:
412 423 self.input_buffer = new_buffer
413 424 # Tab-completion
414 425 elif event.KeyCode == ord('\t'):
415 426 last_line = self.input_buffer.split('\n')[-1]
416 427 if not re.match(r'^\s*$', last_line):
417 428 self.complete_current_input()
418 429 if self.AutoCompActive():
419 430 wx.CallAfter(self._popup_completion, create=True)
420 431 else:
421 432 event.Skip()
422 433 else:
423 434 ConsoleWidget._on_key_down(self, event, skip=skip)
424 435
425 436
426 437 def _on_key_up(self, event, skip=True):
427 438 """ Called when any key is released.
428 439 """
429 440 if event.KeyCode in (59, ord('.')):
430 441 # Intercepting '.'
431 442 event.Skip()
432 443 wx.CallAfter(self._popup_completion, create=True)
433 444 else:
434 445 ConsoleWidget._on_key_up(self, event, skip=skip)
435 446
436 447
437 448 def _on_enter(self):
438 449 """ Called on return key down, in readline input_state.
439 450 """
440 451 if self.debug:
441 452 print >>sys.__stdout__, repr(self.input_buffer)
442 453 PrefilterFrontEnd._on_enter(self)
443 454
444 455
445 456 #--------------------------------------------------------------------------
446 457 # EditWindow API
447 458 #--------------------------------------------------------------------------
448 459
449 460 def OnUpdateUI(self, event):
450 461 """ Override the OnUpdateUI of the EditWindow class, to prevent
451 462 syntax highlighting both for faster redraw, and for more
452 463 consistent look and feel.
453 464 """
454 465 if not self._input_state == 'readline':
455 466 ConsoleWidget.OnUpdateUI(self, event)
456 467
457 468 #--------------------------------------------------------------------------
458 469 # Private API
459 470 #--------------------------------------------------------------------------
460 471
461 472 def _end_system_call(self):
462 473 """ Called at the end of a system call.
463 474 """
464 475 self._input_state = 'buffering'
465 476 self._running_process = False
466 477
467 478
468 479 def _buffer_flush(self, event):
469 480 """ Called by the timer to flush the write buffer.
470 481
471 482 This is always called in the mainloop, by the wx timer.
472 483 """
473 484 self._out_buffer_lock.acquire()
474 485 _out_buffer = self._out_buffer
475 486 self._out_buffer = []
476 487 self._out_buffer_lock.release()
477 488 self.write(''.join(_out_buffer), refresh=False)
478 489
479 490
480 491 def _colorize_input_buffer(self):
481 492 """ Keep the input buffer lines at a bright color.
482 493 """
483 494 if not self._input_state in ('readline', 'raw_input'):
484 495 return
485 496 end_line = self.GetCurrentLine()
486 497 if not sys.platform == 'win32':
487 498 end_line += 1
488 499 for i in range(self.current_prompt_line, end_line):
489 500 if i in self._markers:
490 501 self.MarkerDeleteHandle(self._markers[i])
491 502 self._markers[i] = self.MarkerAdd(i, _INPUT_MARKER)
492 503
493 504
494 505 if __name__ == '__main__':
495 506 class MainWindow(wx.Frame):
496 507 def __init__(self, parent, id, title):
497 508 wx.Frame.__init__(self, parent, id, title, size=(300,250))
498 509 self._sizer = wx.BoxSizer(wx.VERTICAL)
499 510 self.shell = WxController(self)
500 511 self._sizer.Add(self.shell, 1, wx.EXPAND)
501 512 self.SetSizer(self._sizer)
502 513 self.SetAutoLayout(1)
503 514 self.Show(True)
504 515
505 516 app = wx.PySimpleApp()
506 517 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
507 518 frame.shell.SetFocus()
508 519 frame.SetSize((680, 460))
509 520 self = frame.shell
510 521
511 522 app.MainLoop()
512 523
General Comments 0
You need to be logged in to leave comments. Login now