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