##// END OF EJS Templates
Clean up code, names, and docstrings.
gvaroquaux -
Show More
@@ -0,0 +1,19 b''
1 """
2 Package for dealing for process execution in a callback environment, in a
3 portable way.
4
5 killable_process.py is a wrapper of subprocess.Popen that allows the
6 subprocess and its children to be killed in a reliable way, including
7 under windows.
8
9 winprocess.py is required by killable_process.py to kill processes under
10 windows.
11
12 piped_process.py wraps process execution with callbacks to print output,
13 in a non-blocking way. It can be used to interact with a subprocess in eg
14 a GUI event loop.
15 """
16
17 from pipedprocess import PipedProcess
18
19
@@ -0,0 +1,34 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 """
4 zope.interface mock. If zope is installed, this module provides a zope
5 interface classes, if not it provides mocks for them.
6
7 Classes provided:
8 Interface, Attribute, implements, classProvides
9 """
10 __docformat__ = "restructuredtext en"
11
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22 import string
23 import uuid
24 import _ast
25
26 try:
27 from zope.interface import Interface, Attribute, implements, classProvides
28 except ImportError:
29 #zope.interface is not available
30 Interface = object
31 def Attribute(name, doc): pass
32 def implements(interface): pass
33 def classProvides(interface): pass
34
1 NO CONTENT: file renamed from IPython/frontend/killable_process.py to IPython/frontend/_process/killableprocess.py
@@ -1,55 +1,55 b''
1 1 # encoding: utf-8
2 2 """
3 3 Object for encapsulating process execution by using callbacks for stdout,
4 4 stderr and stdin.
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 from killable_process import Popen, PIPE
18 from killableprocess import Popen, PIPE
19 19 from threading import Thread
20 20 from time import sleep
21 21
22 22 class PipedProcess(Thread):
23 23
24 24 def __init__(self, command_string, out_callback,
25 25 end_callback=None,):
26 26 self.command_string = command_string
27 27 self.out_callback = out_callback
28 28 self.end_callback = end_callback
29 29 Thread.__init__(self)
30 30
31 31
32 32 def run(self):
33 33 """ Start the process and hook up the callbacks.
34 34 """
35 35 process = Popen((self.command_string + ' 2>&1', ), shell=True,
36 36 universal_newlines=True,
37 37 stdout=PIPE, stdin=PIPE, )
38 38 self.process = process
39 39 while True:
40 40 out_char = process.stdout.read(1)
41 41 if out_char == '':
42 42 if process.poll() is not None:
43 43 # The process has finished
44 44 break
45 45 else:
46 46 # The process is not giving any interesting
47 47 # output. No use polling it immediatly.
48 48 sleep(0.1)
49 49 else:
50 50 self.out_callback(out_char)
51 51
52 52 if self.end_callback is not None:
53 53 self.end_callback()
54 54
55 55
1 NO CONTENT: file renamed from IPython/frontend/winprocess.py to IPython/frontend/_process/winprocess.py
@@ -1,93 +1,92 b''
1 1 """
2 2 Base front end class for all async frontends.
3 3 """
4 4 __docformat__ = "restructuredtext en"
5 5
6 6 #-------------------------------------------------------------------------------
7 7 # Copyright (C) 2008 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-------------------------------------------------------------------------------
12 12
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17 17 import uuid
18 18
19 19 try:
20 20 from zope.interface import Interface, Attribute, implements, classProvides
21 except ImportError:
22 #zope.interface is not available
23 Interface = object
24 def Attribute(name, doc): pass
25 def implements(interface): pass
26 def classProvides(interface): pass
27
28
21 except ImportError, e:
22 e.message = """%s
23 ________________________________________________________________________________
24 zope.interface is required to run asynchronous frontends.""" % e.message
25 e.args = (e.message, ) + e.args[1:]
29 26
30 27 from frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
31 28
32 29 from IPython.kernel.engineservice import IEngineCore
33 30 from IPython.kernel.core.history import FrontEndHistory
34 31
35 32 try:
36 33 from twisted.python.failure import Failure
37 except ImportError:
38 #Twisted not available
39 Failure = Exception
34 except ImportError, e:
35 e.message = """%s
36 ________________________________________________________________________________
37 twisted is required to run asynchronous frontends.""" % e.message
38 e.args = (e.message, ) + e.args[1:]
40 39
41 40
42 41
43 42
44 43 class AsyncFrontEndBase(FrontEndBase):
45 44 """
46 45 Overrides FrontEndBase to wrap execute in a deferred result.
47 46 All callbacks are made as callbacks on the deferred result.
48 47 """
49 48
50 49 implements(IFrontEnd)
51 50 classProvides(IFrontEndFactory)
52 51
53 52 def __init__(self, engine=None, history=None):
54 53 assert(engine==None or IEngineCore.providedBy(engine))
55 54 self.engine = IEngineCore(engine)
56 55 if history is None:
57 56 self.history = FrontEndHistory(input_cache=[''])
58 57 else:
59 58 self.history = history
60 59
61 60
62 61 def execute(self, block, blockID=None):
63 62 """Execute the block and return the deferred result.
64 63
65 64 Parameters:
66 65 block : {str, AST}
67 66 blockID : any
68 67 Caller may provide an ID to identify this block.
69 68 result['blockID'] := blockID
70 69
71 70 Result:
72 71 Deferred result of self.interpreter.execute
73 72 """
74 73
75 74 if(not self.is_complete(block)):
76 75 return Failure(Exception("Block is not compilable"))
77 76
78 77 if(blockID == None):
79 78 blockID = uuid.uuid4() #random UUID
80 79
81 80 d = self.engine.execute(block)
82 81 d.addCallback(self._add_history, block=block)
83 82 d.addCallbacks(self._add_block_id_for_result,
84 83 errback=self._add_block_id_for_failure,
85 84 callbackArgs=(blockID,),
86 85 errbackArgs=(blockID,))
87 86 d.addBoth(self.update_cell_prompt, blockID=blockID)
88 87 d.addCallbacks(self.render_result,
89 88 errback=self.render_error)
90 89
91 90 return d
92 91
93 92
@@ -1,351 +1,365 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 frontendbase provides an interface and base class for GUI frontends for
5 5 IPython.kernel/IPython.kernel.core.
6 6
7 7 Frontend implementations will likely want to subclass FrontEndBase.
8 8
9 9 Author: Barry Wark
10 10 """
11 11 __docformat__ = "restructuredtext en"
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import string
24 24 import uuid
25 25 import _ast
26 26
27 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
30 #zope.interface is not available
31 Interface = object
32 def Attribute(name, doc): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
27 from zopeinterface import Interface, Attribute, implements, classProvides
35 28
36 29 from IPython.kernel.core.history import FrontEndHistory
37 30 from IPython.kernel.core.util import Bunch
38 31
39 32 ##############################################################################
40 33 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
41 34 # not
42 35
43 36 rc = Bunch()
44 37 rc.prompt_in1 = r'In [$number]: '
45 38 rc.prompt_in2 = r'...'
46 39 rc.prompt_out = r'Out [$number]: '
47 40
48 41 ##############################################################################
42 # Interface definitions
43 ##############################################################################
49 44
50 45 class IFrontEndFactory(Interface):
51 46 """Factory interface for frontends."""
52 47
53 48 def __call__(engine=None, history=None):
54 49 """
55 50 Parameters:
56 51 interpreter : IPython.kernel.engineservice.IEngineCore
57 52 """
58 53
59 54 pass
60 55
61 56
62
63 57 class IFrontEnd(Interface):
64 58 """Interface for frontends. All methods return t.i.d.Deferred"""
65 59
66 60 Attribute("input_prompt_template", "string.Template instance\
67 61 substituteable with execute result.")
68 62 Attribute("output_prompt_template", "string.Template instance\
69 63 substituteable with execute result.")
70 64 Attribute("continuation_prompt_template", "string.Template instance\
71 65 substituteable with execute result.")
72 66
73 67 def update_cell_prompt(result, blockID=None):
74 68 """Subclass may override to update the input prompt for a block.
75 Since this method will be called as a
76 twisted.internet.defer.Deferred's callback/errback,
77 implementations should return result when finished.
69
70 In asynchronous frontends, this method will be called as a
71 twisted.internet.defer.Deferred's callback/errback.
72 Implementations should thus return result when finished.
78 73
79 74 Result is a result dict in case of success, and a
80 75 twisted.python.util.failure.Failure in case of an error
81 76 """
82 77
83 78 pass
84 79
85
86 80 def render_result(result):
87 81 """Render the result of an execute call. Implementors may choose the
88 82 method of rendering.
89 83 For example, a notebook-style frontend might render a Chaco plot
90 84 inline.
91 85
92 86 Parameters:
93 87 result : dict (result of IEngineBase.execute )
94 88 blockID = result['blockID']
95 89
96 90 Result:
97 91 Output of frontend rendering
98 92 """
99 93
100 94 pass
101 95
102 96 def render_error(failure):
103 """Subclasses must override to render the failure. Since this method
104 will be called as a twisted.internet.defer.Deferred's callback,
105 implementations should return result when finished.
97 """Subclasses must override to render the failure.
98
99 In asynchronous frontend, since this method will be called as a
100 twisted.internet.defer.Deferred's callback. Implementations
101 should thus return result when finished.
106 102
107 103 blockID = failure.blockID
108 104 """
109 105
110 106 pass
111 107
112
113 108 def input_prompt(number=''):
114 109 """Returns the input prompt by subsituting into
115 110 self.input_prompt_template
116 111 """
117 112 pass
118 113
119 114 def output_prompt(number=''):
120 115 """Returns the output prompt by subsituting into
121 116 self.output_prompt_template
122 117 """
123 118
124 119 pass
125 120
126 121 def continuation_prompt():
127 122 """Returns the continuation prompt by subsituting into
128 123 self.continuation_prompt_template
129 124 """
130 125
131 126 pass
132 127
133 128 def is_complete(block):
134 129 """Returns True if block is complete, False otherwise."""
135 130
136 131 pass
137 132
138 133 def compile_ast(block):
139 134 """Compiles block to an _ast.AST"""
140 135
141 136 pass
142 137
143
144 138 def get_history_previous(current_block):
145 139 """Returns the block previous in the history. Saves currentBlock if
146 140 the history_cursor is currently at the end of the input history"""
147 141 pass
148 142
149 143 def get_history_next():
150 144 """Returns the next block in the history."""
151 145
152 146 pass
153 147
148 def complete(self, line):
149 """Returns the list of possible completions, and the completed
150 line.
151
152 The input argument is the full line to be completed. This method
153 returns both the line completed as much as possible, and the list
154 of further possible completions (full words).
155 """
156 pass
157
158
159 ##############################################################################
160 # Base class for all the frontends.
161 ##############################################################################
154 162
155 163 class FrontEndBase(object):
156 164 """
157 165 FrontEndBase manages the state tasks for a CLI frontend:
158 166 - Input and output history management
159 167 - Input/continuation and output prompt generation
160 168
161 169 Some issues (due to possibly unavailable engine):
162 170 - How do we get the current cell number for the engine?
163 171 - How do we handle completions?
164 172 """
165 173
166 174 history_cursor = 0
167 175
168 176 current_indent_level = 0
169 177
170 178
171 179 input_prompt_template = string.Template(rc.prompt_in1)
172 180 output_prompt_template = string.Template(rc.prompt_out)
173 181 continuation_prompt_template = string.Template(rc.prompt_in2)
174 182
175 183 def __init__(self, shell=None, history=None):
176 184 self.shell = shell
177 185 if history is None:
178 186 self.history = FrontEndHistory(input_cache=[''])
179 187 else:
180 188 self.history = history
181 189
182 190
183 191 def input_prompt(self, number=''):
184 192 """Returns the current input prompt
185 193
186 194 It would be great to use ipython1.core.prompts.Prompt1 here
187 195 """
188 196 return self.input_prompt_template.safe_substitute({'number':number})
189 197
190 198
191 199 def continuation_prompt(self):
192 200 """Returns the current continuation prompt"""
193 201
194 202 return self.continuation_prompt_template.safe_substitute()
195 203
196 204 def output_prompt(self, number=''):
197 205 """Returns the output prompt for result"""
198 206
199 207 return self.output_prompt_template.safe_substitute({'number':number})
200 208
201 209
202 210 def is_complete(self, block):
203 211 """Determine if block is complete.
204 212
205 213 Parameters
206 214 block : string
207 215
208 216 Result
209 217 True if block can be sent to the engine without compile errors.
210 218 False otherwise.
211 219 """
212 220
213 221 try:
214 222 ast = self.compile_ast(block)
215 223 except:
216 224 return False
217 225
218 226 lines = block.split('\n')
219 227 return (len(lines)==1 or str(lines[-1])=='')
220 228
221 229
222 230 def compile_ast(self, block):
223 231 """Compile block to an AST
224 232
225 233 Parameters:
226 234 block : str
227 235
228 236 Result:
229 237 AST
230 238
231 239 Throws:
232 240 Exception if block cannot be compiled
233 241 """
234 242
235 243 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
236 244
237 245
238 246 def execute(self, block, blockID=None):
239 247 """Execute the block and return the result.
240 248
241 249 Parameters:
242 250 block : {str, AST}
243 251 blockID : any
244 252 Caller may provide an ID to identify this block.
245 253 result['blockID'] := blockID
246 254
247 255 Result:
248 256 Deferred result of self.interpreter.execute
249 257 """
250 258
251 259 if(not self.is_complete(block)):
252 260 raise Exception("Block is not compilable")
253 261
254 262 if(blockID == None):
255 263 blockID = uuid.uuid4() #random UUID
256 264
257 265 try:
258 266 result = self.shell.execute(block)
259 267 except Exception,e:
260 268 e = self._add_block_id_for_failure(e, blockID=blockID)
261 269 e = self.update_cell_prompt(e, blockID=blockID)
262 270 e = self.render_error(e)
263 271 else:
264 272 result = self._add_block_id_for_result(result, blockID=blockID)
265 273 result = self.update_cell_prompt(result, blockID=blockID)
266 274 result = self.render_result(result)
267 275
268 276 return result
269 277
270 278
271 279 def _add_block_id_for_result(self, result, blockID):
272 280 """Add the blockID to result or failure. Unfortunatley, we have to
273 281 treat failures differently than result dicts.
274 282 """
275 283
276 284 result['blockID'] = blockID
277 285
278 286 return result
279 287
280 288 def _add_block_id_for_failure(self, failure, blockID):
281 289 """_add_block_id_for_failure"""
282 290
283 291 failure.blockID = blockID
284 292 return failure
285 293
286 294
287 295 def _add_history(self, result, block=None):
288 296 """Add block to the history"""
289 297
290 298 assert(block != None)
291 299 self.history.add_items([block])
292 300 self.history_cursor += 1
293 301
294 302 return result
295 303
296 304
297 305 def get_history_previous(self, current_block):
298 306 """ Returns previous history string and decrement history cursor.
299 307 """
300 308 command = self.history.get_history_item(self.history_cursor - 1)
301 309
302 310 if command is not None:
303 311 if(self.history_cursor+1 == len(self.history.input_cache)):
304 312 self.history.input_cache[self.history_cursor] = current_block
305 313 self.history_cursor -= 1
306 314 return command
307 315
308 316
309 317 def get_history_next(self):
310 318 """ Returns next history string and increment history cursor.
311 319 """
312 320 command = self.history.get_history_item(self.history_cursor+1)
313 321
314 322 if command is not None:
315 323 self.history_cursor += 1
316 324 return command
317 325
318 326 ###
319 327 # Subclasses probably want to override these methods...
320 328 ###
321 329
322 330 def update_cell_prompt(self, result, blockID=None):
323 331 """Subclass may override to update the input prompt for a block.
332
333 This method only really makes sens in asyncrhonous frontend.
324 334 Since this method will be called as a
325 335 twisted.internet.defer.Deferred's callback, implementations should
326 336 return result when finished.
327 337 """
328 338
329 339 return result
330 340
331 341
332 342 def render_result(self, result):
333 """Subclasses must override to render result. Since this method will
334 be called as a twisted.internet.defer.Deferred's callback,
335 implementations should return result when finished.
343 """Subclasses must override to render result.
344
345 In asynchronous frontends, this method will be called as a
346 twisted.internet.defer.Deferred's callback. Implementations
347 should thus return result when finished.
336 348 """
337 349
338 350 return result
339 351
340 352
341 353 def render_error(self, failure):
342 """Subclasses must override to render the failure. Since this method
343 will be called as a twisted.internet.defer.Deferred's callback,
344 implementations should return result when finished.
354 """Subclasses must override to render the failure.
355
356 In asynchronous frontends, this method will be called as a
357 twisted.internet.defer.Deferred's callback. Implementations
358 should thus return result when finished.
345 359 """
346 360
347 361 return failure
348 362
349 363
350 364
351 365
@@ -1,176 +1,207 b''
1 1 """
2 Base front end class for all line-oriented frontends.
2 Base front end class for all line-oriented frontends, rather than
3 block-oriented.
3 4
4 5 Currently this focuses on synchronous frontends.
5 6 """
6 7 __docformat__ = "restructuredtext en"
7 8
8 9 #-------------------------------------------------------------------------------
9 10 # Copyright (C) 2008 The IPython Development Team
10 11 #
11 12 # Distributed under the terms of the BSD License. The full license is in
12 13 # the file COPYING, distributed as part of this software.
13 14 #-------------------------------------------------------------------------------
14 15
15 16 #-------------------------------------------------------------------------------
16 17 # Imports
17 18 #-------------------------------------------------------------------------------
18 19 import re
19 20
20 21 import IPython
21 22
22 23 from frontendbase import FrontEndBase
23 24 from IPython.kernel.core.interpreter import Interpreter
24 25
25 26 def common_prefix(strings):
27 """ Given a list of strings, return the common prefix between all
28 these strings.
29 """
26 30 ref = strings[0]
27 31 prefix = ''
28 32 for size in range(len(ref)):
29 33 test_prefix = ref[:size+1]
30 34 for string in strings[1:]:
31 35 if not string.startswith(test_prefix):
32 36 return prefix
33 37 prefix = test_prefix
34 38
35 39 return prefix
36 40
37 41 #-------------------------------------------------------------------------------
38 42 # Base class for the line-oriented front ends
39 43 #-------------------------------------------------------------------------------
40 44 class LineFrontEndBase(FrontEndBase):
45 """ Concrete implementation of the FrontEndBase class. This is meant
46 to be the base class behind all the frontend that are line-oriented,
47 rather than block-oriented.
48 """
41 49
42 50 # We need to keep the prompt number, to be able to increment
43 51 # it when there is an exception.
44 52 prompt_number = 1
45 53
46 54 # To bootstrap
47 55 last_result = dict(number=0)
48 56
49 57 #--------------------------------------------------------------------------
50 # Public API
58 # FrontEndBase interface
51 59 #--------------------------------------------------------------------------
52 60
53 61 def __init__(self, shell=None, history=None):
54 62 if shell is None:
55 63 shell = Interpreter()
56 64 FrontEndBase.__init__(self, shell=shell, history=history)
57 65
58 66 #FIXME: print banner.
59 67 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
60 68 % IPython.__version__
61 69
62 70
63 71 def complete(self, line):
64 72 """Complete line in engine's user_ns
65 73
66 74 Parameters
67 75 ----------
68 76 line : string
69 77
70 78 Result
71 79 ------
72 80 The replacement for the line and the list of possible completions.
73 81 """
74 82 completions = self.shell.complete(line)
75 83 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
76 84 if completions:
77 85 prefix = common_prefix(completions)
78 86 residual = complete_sep.split(line)[:-1]
79 87 line = line[:-len(residual)] + prefix
80 88 return line, completions
81 89
82 90
83 91 def render_result(self, result):
92 """ Frontend-specific rendering of the result of a calculation
93 that has been sent to an engine.
94 """
84 95 if 'stdout' in result and result['stdout']:
85 96 self.write('\n' + result['stdout'])
86 97 if 'display' in result and result['display']:
87 98 self.write("%s%s\n" % (
88 99 self.output_prompt % result['number'],
89 100 result['display']['pprint']
90 101 ) )
91 102
92 103
93 104 def render_error(self, failure):
105 """ Frontend-specific rendering of error.
106 """
94 107 self.insert_text('\n\n'+str(failure)+'\n\n')
95 108 return failure
96 109
97 110
98 def prefilter_input(self, string):
99 string = string.replace('\r\n', '\n')
100 string = string.replace('\t', 4*' ')
101 # Clean the trailing whitespace
102 string = '\n'.join(l.rstrip() for l in string.split('\n'))
103 return string
104
105
106 111 def is_complete(self, string):
112 """ Check if a string forms a complete, executable set of
113 commands.
114
115 For the line-oriented frontend, multi-line code is not executed
116 as soon as it is complete: the users has to enter two line
117 returns.
118 """
107 119 if string in ('', '\n'):
120 # Prefiltering, eg through ipython0, may return an empty
121 # string although some operations have been accomplished. We
122 # thus want to consider an empty string as a complete
123 # statement.
108 124 return True
109 125 elif ( len(self.get_current_edit_buffer().split('\n'))>2
110 126 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
111 127 return False
112 128 else:
113 129 # Add line returns here, to make sure that the statement is
114 130 # complete.
115 131 return FrontEndBase.is_complete(self, string.rstrip() + '\n\n')
116 132
117 133
118 134 def execute(self, python_string, raw_string=None):
119 """ Send the python_string to the interpreter, stores the
120 raw_string in the history and starts a new prompt.
135 """ Stores the raw_string in the history, and sends the
136 python string to the interpreter.
121 137 """
122 138 if raw_string is None:
123 139 raw_string = python_string
124 140 # Create a false result, in case there is an exception
125 141 self.last_result = dict(number=self.prompt_number)
126 142 try:
127 143 self.history.input_cache[-1] = raw_string.rstrip()
128 144 result = self.shell.execute(python_string)
129 145 self.last_result = result
130 146 self.render_result(result)
131 147 except:
132 148 self.show_traceback()
133 149 finally:
134 150 self.after_execute()
135 151
152 #--------------------------------------------------------------------------
153 # LineFrontEndBase interface
154 #--------------------------------------------------------------------------
155
156 def prefilter_input(self, string):
157 """ Priflter the input to turn it in valid python.
158 """
159 string = string.replace('\r\n', '\n')
160 string = string.replace('\t', 4*' ')
161 # Clean the trailing whitespace
162 string = '\n'.join(l.rstrip() for l in string.split('\n'))
163 return string
136 164
137 165 def after_execute(self):
138 166 """ All the operations required after an execution to put the
139 167 terminal back in a shape where it is usable.
140 168 """
141 169 self.prompt_number += 1
142 170 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
143 171 # Start a new empty history entry
144 172 self._add_history(None, '')
145 173 self.history_cursor = len(self.history.input_cache) - 1
146 174
147 175
176 #--------------------------------------------------------------------------
177 # Private API
178 #--------------------------------------------------------------------------
179
148 180 def _on_enter(self):
149 181 """ Called when the return key is pressed in a line editing
150 182 buffer.
151 183 """
152 184 current_buffer = self.get_current_edit_buffer()
153 185 cleaned_buffer = self.prefilter_input(current_buffer)
154 186 if self.is_complete(cleaned_buffer):
155 187 self.execute(cleaned_buffer, raw_string=current_buffer)
156 188 else:
157 189 self.write(self._get_indent_string(
158 190 current_buffer[:-1]))
159 191 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
160 192 self.write('\t')
161 193
162 194
163 #--------------------------------------------------------------------------
164 # Private API
165 #--------------------------------------------------------------------------
166
167 195 def _get_indent_string(self, string):
196 """ Return the string of whitespace that prefixes a line. Used to
197 add the right amount of indendation when creating a new line.
198 """
168 199 string = string.replace('\t', ' '*4)
169 200 string = string.split('\n')[-1]
170 201 indent_chars = len(string) - len(string.lstrip())
171 202 indent_string = '\t'*(indent_chars // 4) + \
172 203 ' '*(indent_chars % 4)
173 204
174 205 return indent_string
175 206
176 207
@@ -1,162 +1,192 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
6 This is a transitory class, used here to do the transition between
7 ipython0 and ipython1. This class is meant to be short-lived as more
8 functionnality is abstracted out of ipython0 in reusable functions and
9 is added on the interpreter. This class can be a used to guide this
10 refactoring.
5 11 """
6 12 __docformat__ = "restructuredtext en"
7 13
8 14 #-------------------------------------------------------------------------------
9 15 # Copyright (C) 2008 The IPython Development Team
10 16 #
11 17 # Distributed under the terms of the BSD License. The full license is in
12 18 # the file COPYING, distributed as part of this software.
13 19 #-------------------------------------------------------------------------------
14 20
15 21 #-------------------------------------------------------------------------------
16 22 # Imports
17 23 #-------------------------------------------------------------------------------
18 24 import sys
19 25
20 26 from linefrontendbase import LineFrontEndBase, common_prefix
21 27
22 28 from IPython.ipmaker import make_IPython
23 29 from IPython.ipapi import IPApi
24 30 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
25 31
26 32 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
27 33
28 34 from IPython.genutils import Term
29 35 import pydoc
36 import os
37
30 38
31 39 def mk_system_call(system_call_function, command):
32 40 """ given a os.system replacement, and a leading string command,
33 41 returns a function that will execute the command with the given
34 42 argument string.
35 43 """
36 44 def my_system_call(args):
37 45 system_call_function("%s %s" % (command, args))
38 46 return my_system_call
39 47
40 48 #-------------------------------------------------------------------------------
41 49 # Frontend class using ipython0 to do the prefiltering.
42 50 #-------------------------------------------------------------------------------
43 51 class PrefilterFrontEnd(LineFrontEndBase):
52 """ Class that uses ipython0 to do prefilter the input, do the
53 completion and the magics.
54
55 The core trick is to use an ipython0 instance to prefilter the
56 input, and share the namespace between the interpreter instance used
57 to execute the statements and the ipython0 used for code
58 completion...
59 """
44 60
45 61 def __init__(self, *args, **kwargs):
46 62 LineFrontEndBase.__init__(self, *args, **kwargs)
47 63 # Instanciate an IPython0 interpreter to be able to use the
48 64 # prefiltering.
49 65 self.ipython0 = make_IPython()
50 66 # Set the pager:
51 67 self.ipython0.set_hook('show_in_pager',
52 68 lambda s, string: self.write("\n"+string))
53 69 self.ipython0.write = self.write
54 70 self._ip = _ip = IPApi(self.ipython0)
55 71 # XXX: Hack: mix the two namespaces
56 72 self.shell.user_ns = self.ipython0.user_ns
57 73 self.shell.user_global_ns = self.ipython0.user_global_ns
58 74 # Make sure the raw system call doesn't get called, as we don't
59 75 # have a stdin accessible.
60 76 self._ip.system = self.system_call
61 77 # XXX: Muck around with magics so that they work better
62 78 # in our environment
63 79 self.ipython0.magic_ls = mk_system_call(self.system_call,
64 80 'ls -CF')
65 81 self.shell.output_trap = RedirectorOutputTrap(
66 82 out_callback=self.write,
67 83 err_callback=self.write,
68 84 )
69 85 self.shell.traceback_trap = SyncTracebackTrap(
70 formatters=self.shell.traceback_trap.formatters
86 formatters=self.shell.traceback_trap.formatters,
71 87 )
72 88 # Capture and release the outputs, to make sure all the
73 89 # shadow variables are set
74 90 self.capture_output()
75 91 self.release_output()
76 92
77
78 def prefilter_input(self, input_string):
79 """ Using IPython0 to prefilter the commands.
80 """
81 input_string = LineFrontEndBase.prefilter_input(self, input_string)
82 filtered_lines = []
83 # The IPython0 prefilters sometime produce output. We need to
84 # capture it.
85 self.capture_output()
86 self.last_result = dict(number=self.prompt_number)
87 try:
88 for line in input_string.split('\n'):
89 filtered_lines.append(self.ipython0.prefilter(line, False))
90 except:
91 # XXX: probably not the right thing to do.
92 self.ipython0.showsyntaxerror()
93 self.after_execute()
94 finally:
95 self.release_output()
96
97 filtered_string = '\n'.join(filtered_lines)
98 return filtered_string
99
93 #--------------------------------------------------------------------------
94 # FrontEndBase interface
95 #--------------------------------------------------------------------------
100 96
101 97 def show_traceback(self):
98 """ Use ipython0 to capture the last traceback and display it.
99 """
102 100 self.capture_output()
103 101 self.ipython0.showtraceback()
104 102 self.release_output()
105 103
106 104
107 105 def execute(self, python_string, raw_string=None):
108 106 self.capture_output()
109 107 LineFrontEndBase.execute(self, python_string,
110 108 raw_string=raw_string)
111 109 self.release_output()
112 110
113 111
114 def system_call(self, command):
115 """ Allows for frontend to define their own system call, to be
116 able capture output and redirect input.
117 """
118 return os.system(command, args)
119
120
121 112 def capture_output(self):
122 113 """ Capture all the output mechanisms we can think of.
123 114 """
124 115 self.__old_cout_write = Term.cout.write
125 116 self.__old_err_write = Term.cerr.write
126 117 Term.cout.write = self.write
127 118 Term.cerr.write = self.write
128 119 self.__old_stdout = sys.stdout
129 120 self.__old_stderr= sys.stderr
130 121 sys.stdout = Term.cout
131 122 sys.stderr = Term.cerr
132 123 self.__old_help_output = pydoc.help.output
133 124 pydoc.help.output = self.shell.output_trap.out
134 125
135 126
136 127 def release_output(self):
137 128 """ Release all the different captures we have made.
138 129 """
139 130 Term.cout.write = self.__old_cout_write
140 131 Term.cerr.write = self.__old_err_write
141 132 sys.stdout = self.__old_stdout
142 133 sys.stderr = self.__old_stderr
143 134 pydoc.help.output = self.__old_help_output
144 135
145 136
146 137 def complete(self, line):
147 138 word = line.split('\n')[-1].split(' ')[-1]
148 139 completions = self.ipython0.complete(word)
149 140 # FIXME: The proper sort should be done in the complete method.
150 141 key = lambda x: x.replace('_', '')
151 142 completions.sort(key=key)
152 143 if completions:
153 144 prefix = common_prefix(completions)
154 145 line = line[:-len(word)] + prefix
155 146 return line, completions
156 147
157 148
149 #--------------------------------------------------------------------------
150 # LineFrontEndBase interface
151 #--------------------------------------------------------------------------
152
153 def prefilter_input(self, input_string):
154 """ Using IPython0 to prefilter the commands to turn them
155 in executable statements that are valid Python strings.
156 """
157 input_string = LineFrontEndBase.prefilter_input(self, input_string)
158 filtered_lines = []
159 # The IPython0 prefilters sometime produce output. We need to
160 # capture it.
161 self.capture_output()
162 self.last_result = dict(number=self.prompt_number)
163 try:
164 for line in input_string.split('\n'):
165 filtered_lines.append(self.ipython0.prefilter(line, False))
166 except:
167 # XXX: probably not the right thing to do.
168 self.ipython0.showsyntaxerror()
169 self.after_execute()
170 finally:
171 self.release_output()
172
173 filtered_string = '\n'.join(filtered_lines)
174 return filtered_string
175
176
177 #--------------------------------------------------------------------------
178 # PrefilterLineFrontEnd interface
179 #--------------------------------------------------------------------------
180
181 def system_call(self, command_string):
182 """ Allows for frontend to define their own system call, to be
183 able capture output and redirect input.
184 """
185 return os.system(command_string)
186
187
158 188 def do_exit(self):
159 189 """ Exit the shell, cleanup and save the history.
160 190 """
161 191 self.ipython0.atexit_operations()
162 192
@@ -1,441 +1,441 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 # This is where the title captured from the ANSI escape sequences are
80 # stored.
79 81 title = 'Console'
80 82
81 83 style = _DEFAULT_STYLE.copy()
82 84
83 85 # Translation table from ANSI escape sequences to color. Override
84 86 # this to specify your colors.
85 87 ANSI_STYLES = {'0;30': [0, 'BLACK'], '0;31': [1, 'RED'],
86 88 '0;32': [2, 'GREEN'], '0;33': [3, 'BROWN'],
87 89 '0;34': [4, 'BLUE'], '0;35': [5, 'PURPLE'],
88 90 '0;36': [6, 'CYAN'], '0;37': [7, 'LIGHT GREY'],
89 91 '1;30': [8, 'DARK GREY'], '1;31': [9, 'RED'],
90 92 '1;32': [10, 'SEA GREEN'], '1;33': [11, 'YELLOW'],
91 93 '1;34': [12, 'LIGHT BLUE'], '1;35':
92 94 [13, 'MEDIUM VIOLET RED'],
93 95 '1;36': [14, 'LIGHT STEEL BLUE'], '1;37': [15, 'YELLOW']}
94 96
95 97 # The color of the carret (call _apply_style() after setting)
96 98 carret_color = 'BLACK'
97 99
98 100 #--------------------------------------------------------------------------
99 101 # Public API
100 102 #--------------------------------------------------------------------------
101 103
102 104 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
103 105 size=wx.DefaultSize, style=0, ):
104 106 editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
105 self.configure_scintilla()
107 self._configure_scintilla()
106 108
107 109 # FIXME: we need to retrieve this from the interpreter.
108 110 self.prompt = \
109 111 '\n\x01\x1b[0;34m\x02In [\x01\x1b[1;34m\x02%i\x01\x1b[0;34m\x02]: \x01\x1b[0m\x02'
110 112 self.new_prompt(self.prompt % 1)
111 113
112 114 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
113 115 self.Bind(wx.EVT_KEY_UP, self._on_key_up)
114 116
115 117
116 def configure_scintilla(self):
117 self.SetEOLMode(stc.STC_EOL_LF)
118
119 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
120 # the widget
121 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
122 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
123 # Also allow Ctrl Shift "=" for poor non US keyboard users.
124 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
125 stc.STC_CMD_ZOOMIN)
126
127 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
128 # stc.STC_CMD_PAGEUP)
129
130 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
131 # stc.STC_CMD_PAGEDOWN)
132
133 # Keys: we need to clear some of the keys the that don't play
134 # well with a console.
135 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
136 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
137 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
138
139
140 self.SetEOLMode(stc.STC_EOL_CRLF)
141 self.SetWrapMode(stc.STC_WRAP_CHAR)
142 self.SetWrapMode(stc.STC_WRAP_WORD)
143 self.SetBufferedDraw(True)
144 self.SetUseAntiAliasing(True)
145 self.SetLayoutCache(stc.STC_CACHE_PAGE)
146 self.SetUndoCollection(False)
147 self.SetUseTabs(True)
148 self.SetIndent(4)
149 self.SetTabWidth(4)
150
151 self.EnsureCaretVisible()
152 # we don't want scintilla's autocompletion to choose
153 # automaticaly out of a single choice list, as we pop it up
154 # automaticaly
155 self.AutoCompSetChooseSingle(False)
156 self.AutoCompSetMaxHeight(10)
157
158 self.SetMargins(3, 3) #text is moved away from border with 3px
159 # Suppressing Scintilla margins
160 self.SetMarginWidth(0, 0)
161 self.SetMarginWidth(1, 0)
162 self.SetMarginWidth(2, 0)
163
164 self._apply_style()
165
166 # Xterm escape sequences
167 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
168 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
169
170 #self.SetEdgeMode(stc.STC_EDGE_LINE)
171 #self.SetEdgeColumn(80)
172
173 # styles
174 p = self.style
175 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
176 self.StyleClearAll()
177 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
178 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
179 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
180
181 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
182 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
183 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
184 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
185 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
186 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
187 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
188 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
189 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
190 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
191 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
192 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
193 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
194 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
195
196
197 118 def write(self, text, refresh=True):
198 119 """ Write given text to buffer, while translating the ansi escape
199 120 sequences.
200 121 """
201 122 # XXX: do not put print statements to sys.stdout/sys.stderr in
202 123 # this method, the print statements will call this method, as
203 124 # you will end up with an infinit loop
204 125 if self.debug:
205 126 print >>sys.__stderr__, text
206 127 title = self.title_pat.split(text)
207 128 if len(title)>1:
208 129 self.title = title[-2]
209 130
210 131 text = self.title_pat.sub('', text)
211 132 segments = self.color_pat.split(text)
212 133 segment = segments.pop(0)
213 134 self.GotoPos(self.GetLength())
214 135 self.StartStyling(self.GetLength(), 0xFF)
215 136 try:
216 137 self.AppendText(segment)
217 138 except UnicodeDecodeError:
218 139 # XXX: Do I really want to skip the exception?
219 140 pass
220 141
221 142 if segments:
222 143 for ansi_tag, text in zip(segments[::2], segments[1::2]):
223 144 self.StartStyling(self.GetLength(), 0xFF)
224 145 try:
225 146 self.AppendText(text)
226 147 except UnicodeDecodeError:
227 148 # XXX: Do I really want to skip the exception?
228 149 pass
229 150
230 151 if ansi_tag not in self.ANSI_STYLES:
231 152 style = 0
232 153 else:
233 154 style = self.ANSI_STYLES[ansi_tag][0]
234 155
235 156 self.SetStyling(len(text), style)
236 157
237 158 self.GotoPos(self.GetLength())
238 159 if refresh:
239 160 wx.Yield()
240 161
241 162
242 163 def new_prompt(self, prompt):
243 164 """ Prints a prompt at start of line, and move the start of the
244 165 current block there.
245 166
246 167 The prompt can be give with ascii escape sequences.
247 168 """
248 169 self.write(prompt)
249 170 # now we update our cursor giving end of prompt
250 171 self.current_prompt_pos = self.GetLength()
251 172 self.current_prompt_line = self.GetCurrentLine()
252 173 wx.Yield()
253 174 self.EnsureCaretVisible()
254 175
255 176
256 177 def replace_current_edit_buffer(self, text):
257 178 """ Replace currently entered command line with given text.
258 179 """
259 180 self.SetSelection(self.current_prompt_pos, self.GetLength())
260 181 self.ReplaceSelection(text)
261 182 self.GotoPos(self.GetLength())
262 183
263 184
264 185 def get_current_edit_buffer(self):
265 186 """ Returns the text in current edit buffer.
266 187 """
267 188 current_edit_buffer = self.GetTextRange(self.current_prompt_pos,
268 189 self.GetLength())
269 190 current_edit_buffer = current_edit_buffer.replace(LINESEP, '\n')
270 191 return current_edit_buffer
271 192
272 193
273 #--------------------------------------------------------------------------
274 # Private API
275 #--------------------------------------------------------------------------
276
277 def _apply_style(self):
278 """ Applies the colors for the different text elements and the
279 carret.
280 """
281 self.SetCaretForeground(self.carret_color)
194 def scroll_to_bottom(self):
195 maxrange = self.GetScrollRange(wx.VERTICAL)
196 self.ScrollLines(maxrange)
282 197
283 #self.StyleClearAll()
284 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
285 "fore:#FF0000,back:#0000FF,bold")
286 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
287 "fore:#000000,back:#FF0000,bold")
288 198
289 for style in self.ANSI_STYLES.values():
290 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
199 def pop_completion(self, possibilities, offset=0):
200 """ Pops up an autocompletion menu. Offset is the offset
201 in characters of the position at which the menu should
202 appear, relativ to the cursor.
203 """
204 self.AutoCompSetIgnoreCase(False)
205 self.AutoCompSetAutoHide(False)
206 self.AutoCompSetMaxHeight(len(possibilities))
207 self.AutoCompShow(offset, " ".join(possibilities))
291 208
292 209
293 210 def write_completion(self, possibilities):
294 211 # FIXME: This is non Wx specific and needs to be moved into
295 212 # the base class.
296 213 current_buffer = self.get_current_edit_buffer()
297 214
298 215 self.write('\n')
299 216 max_len = len(max(possibilities, key=len)) + 1
300 217
301 218 #now we check how much symbol we can put on a line...
302 219 chars_per_line = self.GetSize()[0]/self.GetCharWidth()
303 220 symbols_per_line = max(1, chars_per_line/max_len)
304 221
305 222 pos = 1
306 223 buf = []
307 224 for symbol in possibilities:
308 225 if pos < symbols_per_line:
309 226 buf.append(symbol.ljust(max_len))
310 227 pos += 1
311 228 else:
312 229 buf.append(symbol.rstrip() + '\n')
313 230 pos = 1
314 231 self.write(''.join(buf))
315 232 self.new_prompt(self.prompt % (self.last_result['number'] + 1))
316 233 self.replace_current_edit_buffer(current_buffer)
317 234
235 #--------------------------------------------------------------------------
236 # Private API
237 #--------------------------------------------------------------------------
318 238
319 def pop_completion(self, possibilities, offset=0):
320 """ Pops up an autocompletion menu. Offset is the offset
321 in characters of the position at which the menu should
322 appear, relativ to the cursor.
239 def _apply_style(self):
240 """ Applies the colors for the different text elements and the
241 carret.
323 242 """
324 self.AutoCompSetIgnoreCase(False)
325 self.AutoCompSetAutoHide(False)
326 self.AutoCompSetMaxHeight(len(possibilities))
327 self.AutoCompShow(offset, " ".join(possibilities))
243 self.SetCaretForeground(self.carret_color)
328 244
245 #self.StyleClearAll()
246 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,
247 "fore:#FF0000,back:#0000FF,bold")
248 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,
249 "fore:#000000,back:#FF0000,bold")
250
251 for style in self.ANSI_STYLES.values():
252 self.StyleSetSpec(style[0], "bold,fore:%s" % style[1])
329 253
330 def scroll_to_bottom(self):
331 maxrange = self.GetScrollRange(wx.VERTICAL)
332 self.ScrollLines(maxrange)
254
255 def _configure_scintilla(self):
256 self.SetEOLMode(stc.STC_EOL_LF)
257
258 # Ctrl"+" or Ctrl "-" can be used to zoomin/zoomout the text inside
259 # the widget
260 self.CmdKeyAssign(ord('+'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
261 self.CmdKeyAssign(ord('-'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
262 # Also allow Ctrl Shift "=" for poor non US keyboard users.
263 self.CmdKeyAssign(ord('='), stc.STC_SCMOD_CTRL|stc.STC_SCMOD_SHIFT,
264 stc.STC_CMD_ZOOMIN)
265
266 #self.CmdKeyAssign(stc.STC_KEY_PRIOR, stc.STC_SCMOD_SHIFT,
267 # stc.STC_CMD_PAGEUP)
268
269 #self.CmdKeyAssign(stc.STC_KEY_NEXT, stc.STC_SCMOD_SHIFT,
270 # stc.STC_CMD_PAGEDOWN)
271
272 # Keys: we need to clear some of the keys the that don't play
273 # well with a console.
274 self.CmdKeyClear(ord('D'), stc.STC_SCMOD_CTRL)
275 self.CmdKeyClear(ord('L'), stc.STC_SCMOD_CTRL)
276 self.CmdKeyClear(ord('T'), stc.STC_SCMOD_CTRL)
277
278
279 self.SetEOLMode(stc.STC_EOL_CRLF)
280 self.SetWrapMode(stc.STC_WRAP_CHAR)
281 self.SetWrapMode(stc.STC_WRAP_WORD)
282 self.SetBufferedDraw(True)
283 self.SetUseAntiAliasing(True)
284 self.SetLayoutCache(stc.STC_CACHE_PAGE)
285 self.SetUndoCollection(False)
286 self.SetUseTabs(True)
287 self.SetIndent(4)
288 self.SetTabWidth(4)
289
290 self.EnsureCaretVisible()
291 # we don't want scintilla's autocompletion to choose
292 # automaticaly out of a single choice list, as we pop it up
293 # automaticaly
294 self.AutoCompSetChooseSingle(False)
295 self.AutoCompSetMaxHeight(10)
296
297 self.SetMargins(3, 3) #text is moved away from border with 3px
298 # Suppressing Scintilla margins
299 self.SetMarginWidth(0, 0)
300 self.SetMarginWidth(1, 0)
301 self.SetMarginWidth(2, 0)
302
303 self._apply_style()
304
305 # Xterm escape sequences
306 self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?')
307 self.title_pat = re.compile('\x1b]0;(.*?)\x07')
308
309 #self.SetEdgeMode(stc.STC_EDGE_LINE)
310 #self.SetEdgeColumn(80)
311
312 # styles
313 p = self.style
314 self.StyleSetSpec(stc.STC_STYLE_DEFAULT, p['default'])
315 self.StyleClearAll()
316 self.StyleSetSpec(_STDOUT_STYLE, p['stdout'])
317 self.StyleSetSpec(_STDERR_STYLE, p['stderr'])
318 self.StyleSetSpec(_TRACE_STYLE, p['trace'])
319
320 self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, p['bracegood'])
321 self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, p['bracebad'])
322 self.StyleSetSpec(stc.STC_P_COMMENTLINE, p['comment'])
323 self.StyleSetSpec(stc.STC_P_NUMBER, p['number'])
324 self.StyleSetSpec(stc.STC_P_STRING, p['string'])
325 self.StyleSetSpec(stc.STC_P_CHARACTER, p['char'])
326 self.StyleSetSpec(stc.STC_P_WORD, p['keyword'])
327 self.StyleSetSpec(stc.STC_P_WORD2, p['keyword'])
328 self.StyleSetSpec(stc.STC_P_TRIPLE, p['triple'])
329 self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, p['tripledouble'])
330 self.StyleSetSpec(stc.STC_P_CLASSNAME, p['class'])
331 self.StyleSetSpec(stc.STC_P_DEFNAME, p['def'])
332 self.StyleSetSpec(stc.STC_P_OPERATOR, p['operator'])
333 self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, p['comment'])
333 334
334 335
335 336 def _on_key_down(self, event, skip=True):
336 337 """ Key press callback used for correcting behavior for
337 338 console-like interfaces: the cursor is constraint to be after
338 339 the last prompt.
339 340
340 341 Return True if event as been catched.
341 342 """
342 343 catched = True
343 344 # Intercept some specific keys.
344 345 if event.KeyCode == ord('L') and event.ControlDown() :
345 346 self.scroll_to_bottom()
346 347 elif event.KeyCode == ord('K') and event.ControlDown() :
347 348 self.replace_current_edit_buffer('')
348 349 elif event.KeyCode == wx.WXK_PAGEUP and event.ShiftDown():
349 350 self.ScrollPages(-1)
350 351 elif event.KeyCode == wx.WXK_PAGEDOWN and event.ShiftDown():
351 352 self.ScrollPages(1)
352 353 elif event.KeyCode == wx.WXK_UP and event.ShiftDown():
353 354 self.ScrollLines(-1)
354 355 elif event.KeyCode == wx.WXK_DOWN and event.ShiftDown():
355 356 self.ScrollLines(1)
356 357 else:
357 358 catched = False
358 359
359 360 if self.AutoCompActive():
360 361 event.Skip()
361 362 else:
362 363 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
363 364 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
364 365 catched = True
365 366 self.CallTipCancel()
366 367 self.write('\n')
367 368 # Under windows scintilla seems to be doing funny stuff to the
368 369 # line returns here, but get_current_edit_buffer filters this
369 370 # out.
370 371 if sys.platform == 'win32':
371 372 self.replace_current_edit_buffer(
372 373 self.get_current_edit_buffer())
373 374 self._on_enter()
374 375
375 376 elif event.KeyCode == wx.WXK_HOME:
376 377 if event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
377 378 self.GotoPos(self.current_prompt_pos)
378 379 catched = True
379 380
380 381 elif event.Modifiers in (wx.MOD_SHIFT, wx.MOD_WIN) :
381 382 # FIXME: This behavior is not ideal: if the selection
382 383 # is already started, it will jump.
383 384 self.SetSelectionStart(self.current_prompt_pos)
384 385 self.SetSelectionEnd(self.GetCurrentPos())
385 386 catched = True
386 387
387 388 elif event.KeyCode == wx.WXK_UP:
388 389 if self.GetCurrentLine() > self.current_prompt_line:
389 390 if self.GetCurrentLine() == self.current_prompt_line + 1 \
390 391 and self.GetColumn(self.GetCurrentPos()) < \
391 392 self.GetColumn(self.current_prompt_pos):
392 393 self.GotoPos(self.current_prompt_pos)
393 394 else:
394 395 event.Skip()
395 396 catched = True
396 397
397 398 elif event.KeyCode in (wx.WXK_LEFT, wx.WXK_BACK):
398 399 if self.GetCurrentPos() > self.current_prompt_pos:
399 400 event.Skip()
400 401 catched = True
401 402
402 403 if skip and not catched:
403 404 # Put the cursor back in the edit region
404 405 if self.GetCurrentPos() < self.current_prompt_pos:
405 406 self.GotoPos(self.current_prompt_pos)
406 407 else:
407 408 event.Skip()
408 409
409 410 return catched
410 411
411 412
412 413 def _on_key_up(self, event, skip=True):
413 414 """ If cursor is outside the editing region, put it back.
414 415 """
415 416 event.Skip()
416 417 if self.GetCurrentPos() < self.current_prompt_pos:
417 418 self.GotoPos(self.current_prompt_pos)
418 419
419 420
420 421
421
422 422 if __name__ == '__main__':
423 423 # Some simple code to test the console widget.
424 424 class MainWindow(wx.Frame):
425 425 def __init__(self, parent, id, title):
426 426 wx.Frame.__init__(self, parent, id, title, size=(300,250))
427 427 self._sizer = wx.BoxSizer(wx.VERTICAL)
428 428 self.console_widget = ConsoleWidget(self)
429 429 self._sizer.Add(self.console_widget, 1, wx.EXPAND)
430 430 self.SetSizer(self._sizer)
431 431 self.SetAutoLayout(1)
432 432 self.Show(True)
433 433
434 434 app = wx.PySimpleApp()
435 435 w = MainWindow(None, wx.ID_ANY, 'ConsoleWidget')
436 436 w.SetSize((780, 460))
437 437 w.Show()
438 438
439 439 app.MainLoop()
440 440
441 441
@@ -1,408 +1,438 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 This class inherits from ConsoleWidget, that provides a console-like
9 widget to provide a text-rendering widget suitable for a terminal.
8 10 """
9 11
10 12 __docformat__ = "restructuredtext en"
11 13
12 14 #-------------------------------------------------------------------------------
13 15 # Copyright (C) 2008 The IPython Development Team
14 16 #
15 17 # Distributed under the terms of the BSD License. The full license is in
16 18 # the file COPYING, distributed as part of this software.
17 19 #-------------------------------------------------------------------------------
18 20
19 21 #-------------------------------------------------------------------------------
20 22 # Imports
21 23 #-------------------------------------------------------------------------------
22 24
23
24 import wx
25 # Major library imports
25 26 import re
26 from wx import stc
27 from console_widget import ConsoleWidget
28 27 import __builtin__
29 28 from time import sleep
30 29 import sys
31 import signal
32
33 30 from threading import Lock
34 31
35 from IPython.frontend.piped_process import PipedProcess
32 import wx
33 from wx import stc
34
35 # Ipython-specific imports.
36 from IPython.frontend._process import PipedProcess
37 from console_widget import ConsoleWidget
36 38 from IPython.frontend.prefilterfrontend import PrefilterFrontEnd
37 39
40 #-------------------------------------------------------------------------------
41 # Constants
42 #-------------------------------------------------------------------------------
43
38 44 #_COMMAND_BG = '#FAFAF1' # Nice green
39 45 _RUNNING_BUFFER_BG = '#FDFFD3' # Nice yellow
40 46 _ERROR_BG = '#FFF1F1' # Nice red
41 47
42 48 _RUNNING_BUFFER_MARKER = 31
43 49 _ERROR_MARKER = 30
44 50
45 51 #-------------------------------------------------------------------------------
46 52 # Classes to implement the Wx frontend
47 53 #-------------------------------------------------------------------------------
48 54 class WxController(PrefilterFrontEnd, ConsoleWidget):
55 """Classes to provide a Wx frontend to the
56 IPython.kernel.core.interpreter.
57
58 This class inherits from ConsoleWidget, that provides a console-like
59 widget to provide a text-rendering widget suitable for a terminal.
60 """
49 61
62 # FIXME: this shouldn't be there.
50 63 output_prompt = \
51 64 '\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
52 65
53 66 # Print debug info on what is happening to the console.
54 67 debug = True
55 68
56 69 # The title of the terminal, as captured through the ANSI escape
57 70 # sequences.
58
59 71 def _set_title(self, title):
60 72 return self.Parent.SetTitle(title)
61 73
62 74 def _get_title(self):
63 75 return self.Parent.GetTitle()
64 76
65 77 title = property(_get_title, _set_title)
66 78
67 79 #--------------------------------------------------------------------------
68 80 # Private Attributes
69 81 #--------------------------------------------------------------------------
70 82
71 83 # A flag governing the behavior of the input. Can be:
72 84 #
73 85 # 'readline' for readline-like behavior with a prompt
74 86 # and an edit buffer.
75 87 # 'subprocess' for sending the raw input directly to a
76 88 # subprocess.
77 89 # 'buffering' for buffering of the input, that will be used
78 90 # when the input state switches back to another state.
79 91 _input_state = 'readline'
80 92
81 93 # Attribute to store reference to the pipes of a subprocess, if we
82 94 # are running any.
83 95 _running_process = False
84 96
85 97 # A queue for writing fast streams to the screen without flooding the
86 98 # event loop
87 99 _out_buffer = []
88 100
89 101 # A lock to lock the _out_buffer to make sure we don't empty it
90 102 # while it is being swapped
91 103 _out_buffer_lock = Lock()
92 104
93 105 #--------------------------------------------------------------------------
94 106 # Public API
95 107 #--------------------------------------------------------------------------
96 108
97 109 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
98 110 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
99 111 *args, **kwds):
100 112 """ Create Shell instance.
101 113 """
102 114 ConsoleWidget.__init__(self, parent, id, pos, size, style)
103 115 PrefilterFrontEnd.__init__(self)
104 116
105 117 # Marker for running buffer.
106 118 self.MarkerDefine(_RUNNING_BUFFER_MARKER, stc.STC_MARK_BACKGROUND,
107 119 background=_RUNNING_BUFFER_BG)
108 120 # Marker for tracebacks.
109 121 self.MarkerDefine(_ERROR_MARKER, stc.STC_MARK_BACKGROUND,
110 122 background=_ERROR_BG)
111 123
112 124 # A time for flushing the write buffer
113 125 BUFFER_FLUSH_TIMER_ID = 100
114 126 self._buffer_flush_timer = wx.Timer(self, BUFFER_FLUSH_TIMER_ID)
115 127 wx.EVT_TIMER(self, BUFFER_FLUSH_TIMER_ID, self._buffer_flush)
116 128
129
130 def raw_input(self, prompt):
131 """ A replacement from python's raw_input.
132 """
133 self.new_prompt(prompt)
134 self.waiting = True
135 self.__old_on_enter = self._on_enter
136 def my_on_enter():
137 self.waiting = False
138 self._on_enter = my_on_enter
139 # XXX: Busy waiting, ugly.
140 while self.waiting:
141 wx.Yield()
142 sleep(0.1)
143 self._on_enter = self.__old_on_enter
144 self._input_state = 'buffering'
145 return self.get_current_edit_buffer().rstrip('\n')
146
147
148 def system_call(self, command_string):
149 self._input_state = 'subprocess'
150 self._running_process = PipedProcess(command_string,
151 out_callback=self.buffered_write,
152 end_callback = self._end_system_call)
153 self._running_process.start()
154 # XXX: another one of these polling loops to have a blocking
155 # call
156 wx.Yield()
157 while self._running_process:
158 wx.Yield()
159 sleep(0.1)
160 # Be sure to flush the buffer.
161 self._buffer_flush(event=None)
162
163
117 164 def do_completion(self):
118 """ Do code completion.
165 """ Do code completion on current line.
119 166 """
120 167 if self.debug:
121 168 print >>sys.__stdout__, "do_completion",
122 169 line = self.get_current_edit_buffer()
123 170 new_line, completions = self.complete(line)
124 171 if len(completions)>1:
125 172 self.write_completion(completions)
126 173 self.replace_current_edit_buffer(new_line)
127 174 if self.debug:
128 175 print >>sys.__stdout__, completions
129 176
130 177
131 178 def do_calltip(self):
179 """ Analyse current and displays useful calltip for it.
180 """
132 181 if self.debug:
133 182 print >>sys.__stdout__, "do_calltip"
134 183 separators = re.compile('[\s\{\}\[\]\(\)\= ,:]')
135 184 symbol = self.get_current_edit_buffer()
136 185 symbol_string = separators.split(symbol)[-1]
137 186 base_symbol_string = symbol_string.split('.')[0]
138 187 if base_symbol_string in self.shell.user_ns:
139 188 symbol = self.shell.user_ns[base_symbol_string]
140 189 elif base_symbol_string in self.shell.user_global_ns:
141 190 symbol = self.shell.user_global_ns[base_symbol_string]
142 191 elif base_symbol_string in __builtin__.__dict__:
143 192 symbol = __builtin__.__dict__[base_symbol_string]
144 193 else:
145 194 return False
146 195 for name in symbol_string.split('.')[1:] + ['__doc__']:
147 196 symbol = getattr(symbol, name)
148 197 try:
149 198 self.AutoCompCancel()
150 199 wx.Yield()
151 200 self.CallTipShow(self.GetCurrentPos(), symbol)
152 201 except:
153 202 # The retrieve symbol couldn't be converted to a string
154 203 pass
155 204
156 205
157 def popup_completion(self, create=False):
206 def _popup_completion(self, create=False):
158 207 """ Updates the popup completion menu if it exists. If create is
159 208 true, open the menu.
160 209 """
161 210 if self.debug:
162 print >>sys.__stdout__, "popup_completion",
211 print >>sys.__stdout__, "_popup_completion",
163 212 line = self.get_current_edit_buffer()
164 213 if (self.AutoCompActive() and not line[-1] == '.') \
165 214 or create==True:
166 215 suggestion, completions = self.complete(line)
167 216 offset=0
168 217 if completions:
169 218 complete_sep = re.compile('[\s\{\}\[\]\(\)\= ,:]')
170 219 residual = complete_sep.split(line)[-1]
171 220 offset = len(residual)
172 221 self.pop_completion(completions, offset=offset)
173 222 if self.debug:
174 223 print >>sys.__stdout__, completions
175 224
176 225
177 def new_prompt(self, prompt):
178 self._input_state = 'readline'
179 ConsoleWidget.new_prompt(self, prompt)
180
226 def buffered_write(self, text):
227 """ A write method for streams, that caches the stream in order
228 to avoid flooding the event loop.
181 229
182 def raw_input(self, prompt):
183 """ A replacement from python's raw_input.
230 This can be called outside of the main loop, in separate
231 threads.
184 232 """
185 self.new_prompt(prompt)
186 self.waiting = True
187 self.__old_on_enter = self._on_enter
188 def my_on_enter():
189 self.waiting = False
190 self._on_enter = my_on_enter
191 # XXX: Busy waiting, ugly.
192 while self.waiting:
193 wx.Yield()
194 sleep(0.1)
195 self._on_enter = self.__old_on_enter
196 self._input_state = 'buffering'
197 return self.get_current_edit_buffer().rstrip('\n')
233 self._out_buffer_lock.acquire()
234 self._out_buffer.append(text)
235 self._out_buffer_lock.release()
236 if not self._buffer_flush_timer.IsRunning():
237 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
198 238
199 239
240 #--------------------------------------------------------------------------
241 # LineFrontEnd interface
242 #--------------------------------------------------------------------------
243
200 244 def execute(self, python_string, raw_string=None):
201 245 self._input_state = 'buffering'
202 246 self.CallTipCancel()
203 247 self._cursor = wx.BusyCursor()
204 248 if raw_string is None:
205 249 raw_string = python_string
206 250 end_line = self.current_prompt_line \
207 251 + max(1, len(raw_string.split('\n'))-1)
208 252 for i in range(self.current_prompt_line, end_line):
209 253 self.MarkerAdd(i, _RUNNING_BUFFER_MARKER)
210 254 # Update the display:
211 255 wx.Yield()
212 256 self.GotoPos(self.GetLength())
213 257 PrefilterFrontEnd.execute(self, python_string, raw_string=raw_string)
214 258
215 259
216 260 def capture_output(self):
217 261 self.__old_raw_input = __builtin__.raw_input
218 262 __builtin__.raw_input = self.raw_input
219 263 PrefilterFrontEnd.capture_output(self)
220 264
221 265
222 266 def release_output(self):
223 267 __builtin__.raw_input = self.__old_raw_input
224 268 PrefilterFrontEnd.capture_output(self)
225 269
226 270
227 271 def after_execute(self):
228 272 PrefilterFrontEnd.after_execute(self)
273 # Clear the wait cursor
229 274 if hasattr(self, '_cursor'):
230 275 del self._cursor
231 276
232 277
233 def system_call(self, command_string):
234 self._input_state = 'subprocess'
235 self._running_process = PipedProcess(command_string,
236 out_callback=self.buffered_write,
237 end_callback = self._end_system_call)
238 self._running_process.start()
239 # XXX: another one of these polling loops to have a blocking
240 # call
241 wx.Yield()
242 while self._running_process:
243 wx.Yield()
244 sleep(0.1)
245 # Be sure to flush the buffer.
246 self._buffer_flush(event=None)
247
248
249 def buffered_write(self, text):
250 """ A write method for streams, that caches the stream in order
251 to avoid flooding the event loop.
252
253 This can be called outside of the main loop, in separate
254 threads.
255 """
256 self._out_buffer_lock.acquire()
257 self._out_buffer.append(text)
258 self._out_buffer_lock.release()
259 if not self._buffer_flush_timer.IsRunning():
260 wx.CallAfter(self._buffer_flush_timer.Start, 100) # milliseconds
261
262
263 278 def show_traceback(self):
264 279 start_line = self.GetCurrentLine()
265 280 PrefilterFrontEnd.show_traceback(self)
266 281 wx.Yield()
267 282 for i in range(start_line, self.GetCurrentLine()):
268 283 self.MarkerAdd(i, _ERROR_MARKER)
269 284
270 285
271 286 #--------------------------------------------------------------------------
272 # Private API
287 # ConsoleWidget interface
273 288 #--------------------------------------------------------------------------
274 289
290 def new_prompt(self, prompt):
291 """ Display a new prompt, and start a new input buffer.
292 """
293 self._input_state = 'readline'
294 ConsoleWidget.new_prompt(self, prompt)
295
296
275 297 def _on_key_down(self, event, skip=True):
276 298 """ Capture the character events, let the parent
277 299 widget handle them, and put our logic afterward.
278 300 """
279 301 # FIXME: This method needs to be broken down in smaller ones.
280 302 current_line_number = self.GetCurrentLine()
281 303 if event.KeyCode in (ord('c'), ord('C')) and event.ControlDown():
282 304 # Capture Control-C
283 305 if self._input_state == 'subprocess':
284 306 if self.debug:
285 307 print >>sys.__stderr__, 'Killing running process'
286 308 self._running_process.process.kill()
287 309 elif self._input_state == 'buffering':
288 310 if self.debug:
289 print >>sys.__stderr__, 'Raising KeyboardException'
290 raise KeyboardException
311 print >>sys.__stderr__, 'Raising KeyboardInterrupt'
312 raise KeyboardInterrupt
291 313 # XXX: We need to make really sure we
292 314 # get back to a prompt.
293 315 elif self._input_state == 'subprocess' and (
294 316 ( event.KeyCode<256 and
295 317 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN, wx.MOD_SHIFT))
296 318 or
297 319 ( event.KeyCode in (ord('d'), ord('D')) and
298 320 event.ControlDown())):
299 321 # We are running a process, we redirect keys.
300 322 ConsoleWidget._on_key_down(self, event, skip=skip)
301 323 char = chr(event.KeyCode)
302 324 # Deal with some inconsistency in wx keycodes:
303 325 if char == '\r':
304 326 char = '\n'
305 327 elif not event.ShiftDown():
306 328 char = char.lower()
307 329 if event.ControlDown() and event.KeyCode in (ord('d'), ord('D')):
308 330 char = '\04'
309 331 self._running_process.process.stdin.write(char)
310 332 self._running_process.process.stdin.flush()
311 333 elif event.KeyCode in (ord('('), 57):
312 334 # Calltips
313 335 event.Skip()
314 336 self.do_calltip()
315 337 elif self.AutoCompActive():
316 338 event.Skip()
317 339 if event.KeyCode in (wx.WXK_BACK, wx.WXK_DELETE):
318 wx.CallAfter(self.popup_completion, create=True)
340 wx.CallAfter(self._popup_completion, create=True)
319 341 elif not event.KeyCode in (wx.WXK_UP, wx.WXK_DOWN, wx.WXK_LEFT,
320 342 wx.WXK_RIGHT):
321 wx.CallAfter(self.popup_completion)
343 wx.CallAfter(self._popup_completion)
322 344 else:
323 345 # Up history
324 346 if event.KeyCode == wx.WXK_UP and (
325 347 ( current_line_number == self.current_prompt_line and
326 348 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
327 349 or event.ControlDown() ):
328 350 new_buffer = self.get_history_previous(
329 351 self.get_current_edit_buffer())
330 352 if new_buffer is not None:
331 353 self.replace_current_edit_buffer(new_buffer)
332 354 if self.GetCurrentLine() > self.current_prompt_line:
333 355 # Go to first line, for seemless history up.
334 356 self.GotoPos(self.current_prompt_pos)
335 357 # Down history
336 358 elif event.KeyCode == wx.WXK_DOWN and (
337 359 ( current_line_number == self.LineCount -1 and
338 360 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
339 361 or event.ControlDown() ):
340 362 new_buffer = self.get_history_next()
341 363 if new_buffer is not None:
342 364 self.replace_current_edit_buffer(new_buffer)
343 365 # Tab-completion
344 366 elif event.KeyCode == ord('\t'):
345 367 last_line = self.get_current_edit_buffer().split('\n')[-1]
346 368 if not re.match(r'^\s*$', last_line):
347 369 self.do_completion()
348 370 else:
349 371 event.Skip()
350 372 else:
351 373 ConsoleWidget._on_key_down(self, event, skip=skip)
352 374
353 375
354 376 def _on_key_up(self, event, skip=True):
377 """ Called when any key is released.
378 """
355 379 if event.KeyCode in (59, ord('.')):
356 380 # Intercepting '.'
357 381 event.Skip()
358 self.popup_completion(create=True)
382 self._popup_completion(create=True)
359 383 else:
360 384 ConsoleWidget._on_key_up(self, event, skip=skip)
361 385
362 386
363 387 def _on_enter(self):
388 """ Called on return key down, in readline input_state.
389 """
364 390 if self.debug:
365 391 print >>sys.__stdout__, repr(self.get_current_edit_buffer())
366 392 PrefilterFrontEnd._on_enter(self)
367 393
368 394
395 #--------------------------------------------------------------------------
396 # Private API
397 #--------------------------------------------------------------------------
398
369 399 def _end_system_call(self):
370 400 """ Called at the end of a system call.
371 401 """
372 402 print >>sys.__stderr__, 'End of system call'
373 403 self._input_state = 'buffering'
374 404 self._running_process = False
375 405
376 406
377 407 def _buffer_flush(self, event):
378 408 """ Called by the timer to flush the write buffer.
379 409
380 410 This is always called in the mainloop, by the wx timer.
381 411 """
382 412 self._out_buffer_lock.acquire()
383 413 _out_buffer = self._out_buffer
384 414 self._out_buffer = []
385 415 self._out_buffer_lock.release()
386 416 self.write(''.join(_out_buffer), refresh=False)
387 417 self._buffer_flush_timer.Stop()
388 418
389 419
390 420 if __name__ == '__main__':
391 421 class MainWindow(wx.Frame):
392 422 def __init__(self, parent, id, title):
393 423 wx.Frame.__init__(self, parent, id, title, size=(300,250))
394 424 self._sizer = wx.BoxSizer(wx.VERTICAL)
395 425 self.shell = WxController(self)
396 426 self._sizer.Add(self.shell, 1, wx.EXPAND)
397 427 self.SetSizer(self._sizer)
398 428 self.SetAutoLayout(1)
399 429 self.Show(True)
400 430
401 431 app = wx.PySimpleApp()
402 432 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
403 433 frame.shell.SetFocus()
404 434 frame.SetSize((680, 460))
405 435 self = frame.shell
406 436
407 437 app.MainLoop()
408 438
@@ -1,41 +1,45 b''
1 1 # encoding: utf-8
2 2
3 3 """Object to manage sys.excepthook().
4 4
5 5 Synchronous version: prints errors when called.
6 6 """
7 7
8 8 __docformat__ = "restructuredtext en"
9 9
10 10 #-------------------------------------------------------------------------------
11 11 # Copyright (C) 2008 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-------------------------------------------------------------------------------
16 16
17 17 #-------------------------------------------------------------------------------
18 18 # Imports
19 19 #-------------------------------------------------------------------------------
20 20 from traceback_trap import TracebackTrap
21 21 from IPython.ultraTB import ColorTB
22 22
23 23 class SyncTracebackTrap(TracebackTrap):
24 24
25 def __init__(self, sync_formatter=None, formatters=None):
25 def __init__(self, sync_formatter=None, formatters=None,
26 raiseException=True):
26 27 TracebackTrap.__init__(self, formatters=formatters)
27 28 if sync_formatter is None:
28 29 sync_formatter = ColorTB(color_scheme='LightBG')
29 30 self.sync_formatter = sync_formatter
31 self.raiseException = raiseException
30 32
31 33
32 34 def hook(self, *args):
33 35 """ This method actually implements the hook.
34 36 """
35 37 self.args = args
36
38 if not self.raiseException:
37 39 print self.sync_formatter(*self.args)
40 else:
41 raise
38 42
39 43
40 44
41 45
General Comments 0
You need to be logged in to leave comments. Login now