##// END OF EJS Templates
Split the frontend base class in different files and add a base class...
Gael Varoquaux -
Show More
@@ -0,0 +1,93 b''
1 """
2 Base front end class for all async frontends.
3 """
4 __docformat__ = "restructuredtext en"
5
6 #-------------------------------------------------------------------------------
7 # Copyright (C) 2008 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-------------------------------------------------------------------------------
12
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17 import uuid
18
19 try:
20 from zope.interface import Interface, Attribute, implements, classProvides
21 except ImportError:
22 #zope.interface is not available
23 Interface = object
24 def Attribute(name, doc): pass
25 def implements(interface): pass
26 def classProvides(interface): pass
27
28
29
30 from frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
31
32 from IPython.kernel.engineservice import IEngineCore
33 from IPython.kernel.core.history import FrontEndHistory
34
35 try:
36 from twisted.python.failure import Failure
37 except ImportError:
38 #Twisted not available
39 Failure = Exception
40
41
42
43
44 class AsyncFrontEndBase(FrontEndBase):
45 """
46 Overrides FrontEndBase to wrap execute in a deferred result.
47 All callbacks are made as callbacks on the deferred result.
48 """
49
50 implements(IFrontEnd)
51 classProvides(IFrontEndFactory)
52
53 def __init__(self, engine=None, history=None):
54 assert(engine==None or IEngineCore.providedBy(engine))
55 self.engine = IEngineCore(engine)
56 if history is None:
57 self.history = FrontEndHistory(input_cache=[''])
58 else:
59 self.history = history
60
61
62 def execute(self, block, blockID=None):
63 """Execute the block and return the deferred result.
64
65 Parameters:
66 block : {str, AST}
67 blockID : any
68 Caller may provide an ID to identify this block.
69 result['blockID'] := blockID
70
71 Result:
72 Deferred result of self.interpreter.execute
73 """
74
75 if(not self.is_complete(block)):
76 return Failure(Exception("Block is not compilable"))
77
78 if(blockID == None):
79 blockID = uuid.uuid4() #random UUID
80
81 d = self.engine.execute(block)
82 d.addCallback(self._add_history, block=block)
83 d.addCallbacks(self._add_block_id_for_result,
84 errback=self._add_block_id_for_failure,
85 callbackArgs=(blockID,),
86 errbackArgs=(blockID,))
87 d.addBoth(self.update_cell_prompt, blockID=blockID)
88 d.addCallbacks(self.render_result,
89 errback=self.render_error)
90
91 return d
92
93
@@ -0,0 +1,123 b''
1 """
2 Base front end class for all line-oriented frontends.
3
4 Currently this focuses on synchronous frontends.
5 """
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18 import re
19
20 import IPython
21
22
23 from frontendbase import FrontEndBase
24 from IPython.kernel.core.interpreter import Interpreter
25
26 #-------------------------------------------------------------------------------
27 # Base class for the line-oriented front ends
28 #-------------------------------------------------------------------------------
29 class LineFrontEndBase(FrontEndBase):
30
31 # Are we entering multi line input?
32 multi_line_input = False
33
34 # The added tab stop to the string. It may, for instance, come from
35 # copy and pasting something with tabs.
36 tab_stop = 0
37 # FIXME: We still have to deal with this.
38
39 #--------------------------------------------------------------------------
40 # Public API
41 #--------------------------------------------------------------------------
42
43 def __init__(self, shell=None, history=None):
44 if shell is None:
45 shell = Interpreter()
46 FrontEndBase.__init__(self, shell=shell, history=history)
47
48 #FIXME: print banner.
49 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
50 % IPython.__version__
51
52
53 def complete(self, token):
54 """Complete token in engine's user_ns
55
56 Parameters
57 ----------
58 token : string
59
60 Result
61 ------
62 Deferred result of
63 IPython.kernel.engineservice.IEngineBase.complete
64 """
65
66 return self.shell.complete(token)
67
68
69 def render_result(self, result):
70 if 'stdout' in result and result['stdout']:
71 self.write('\n' + result['stdout'])
72 if 'display' in result and result['display']:
73 self.write("%s%s\n" % (
74 self.output_prompt % result['number'],
75 result['display']['pprint']
76 ) )
77
78
79 def render_error(self, failure):
80 self.insert_text('\n\n'+str(failure)+'\n\n')
81 return failure
82
83
84 def on_enter(self):
85 """ Called when the return key is pressed in a line editing
86 buffer.
87 """
88 current_buffer = self.get_current_edit_buffer()
89 current_buffer = current_buffer.replace('\r\n', '\n')
90 current_buffer = current_buffer.replace('\t', 4*' ')
91 cleaned_buffer = '\n'.join(l.rstrip()
92 for l in current_buffer.split('\n'))
93 if ( not self.multi_line_input
94 or re.findall(r"\n[\t ]*$", cleaned_buffer)):
95 if self.is_complete(cleaned_buffer):
96 self._add_history(None, cleaned_buffer.rstrip())
97 result = self.shell.execute(cleaned_buffer)
98 self.render_result(result)
99 self.new_prompt(self.prompt % (result['number'] + 1))
100 self.multi_line_input = False
101 else:
102 if self.multi_line_input:
103 self.write('\n' + self._get_indent_string(current_buffer))
104 else:
105 self.multi_line_input = True
106 self.write('\n\t')
107 else:
108 self.write('\n'+self._get_indent_string(current_buffer))
109
110
111 #--------------------------------------------------------------------------
112 # Private API
113 #--------------------------------------------------------------------------
114
115 def _get_indent_string(self, string):
116 string = string.split('\n')[-1]
117 indent_chars = len(string) - len(string.lstrip())
118 indent_string = '\t'*(indent_chars // 4) + \
119 ' '*(indent_chars % 4)
120
121 return indent_string
122
123
@@ -1,505 +1,505 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5 IPython.kernel.engineservice.IEngineBase.
6
6
7 To add an IPython interpreter to a cocoa app, instantiate an
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
9 NSTextView instance in your UI. That's it.
10
10
11 Author: Barry Wark
11 Author: Barry Wark
12 """
12 """
13
13
14 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
17 # Copyright (C) 2008 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import sys
27 import sys
28 import objc
28 import objc
29 import uuid
29 import uuid
30
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
34 NSString, NSAutoreleasePool
35
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
38
38
39 from pprint import saferepr
39 from pprint import saferepr
40
40
41 import IPython
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44
44
45 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
47
47
48 #------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
49 # Classes to implement the Cocoa frontend
49 # Classes to implement the Cocoa frontend
50 #------------------------------------------------------------------------------
50 #------------------------------------------------------------------------------
51
51
52 # TODO:
52 # TODO:
53 # 1. use MultiEngineClient and out-of-process engine rather than
53 # 1. use MultiEngineClient and out-of-process engine rather than
54 # ThreadedEngineService?
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
56
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
58 """Wrap all blocks in an NSAutoreleasePool"""
59
59
60 def wrapped_execute(self, msg, lines):
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
61 """wrapped_execute"""
62 try:
62 try:
63 p = NSAutoreleasePool.alloc().init()
63 p = NSAutoreleasePool.alloc().init()
64 result = self.shell.execute(lines)
64 result = self.shell.execute(lines)
65 except Exception,e:
65 except Exception,e:
66 # This gives the following:
66 # This gives the following:
67 # et=exception class
67 # et=exception class
68 # ev=exception class instance
68 # ev=exception class instance
69 # tb=traceback object
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
73 # Add another attribute
74
74
75 # Create a new exception with the new attributes
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
77 e._ipython_engine_info = msg
78
78
79 # Re-raise
79 # Re-raise
80 raise e
80 raise e
81 finally:
81 finally:
82 p.drain()
82 p.drain()
83
83
84 return result
84 return result
85
85
86 def execute(self, lines):
86 def execute(self, lines):
87 # Only import this if we are going to use this class
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
88 from twisted.internet import threads
89
89
90 msg = {'engineid':self.id,
90 msg = {'engineid':self.id,
91 'method':'execute',
91 'method':'execute',
92 'args':[lines]}
92 'args':[lines]}
93
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
95 d.addCallback(self.addIDToResult)
96 return d
96 return d
97
97
98
98
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 waitingForEngine = objc.ivar().bool()
101 waitingForEngine = objc.ivar().bool()
102 textView = objc.IBOutlet()
102 textView = objc.IBOutlet()
103
103
104 def init(self):
104 def init(self):
105 self = super(IPythonCocoaController, self).init()
105 self = super(IPythonCocoaController, self).init()
106 AsyncFrontEndBase.__init__(self,
106 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
107 engine=AutoreleasePoolWrappedThreadedEngineService())
108 if(self != None):
108 if(self != None):
109 self._common_init()
109 self._common_init()
110
110
111 return self
111 return self
112
112
113 def _common_init(self):
113 def _common_init(self):
114 """_common_init"""
114 """_common_init"""
115
115
116 self.userNS = NSMutableDictionary.dictionary()
116 self.userNS = NSMutableDictionary.dictionary()
117 self.waitingForEngine = False
117 self.waitingForEngine = False
118
118
119 self.lines = {}
119 self.lines = {}
120 self.tabSpaces = 4
120 self.tabSpaces = 4
121 self.tabUsesSpaces = True
121 self.tabUsesSpaces = True
122 self.currentBlockID = self.next_block_ID()
122 self.currentBlockID = self.next_block_ID()
123 self.blockRanges = {} # blockID=>NSRange
123 self.blockRanges = {} # blockID=>NSRange
124
124
125
125
126 def awakeFromNib(self):
126 def awakeFromNib(self):
127 """awakeFromNib"""
127 """awakeFromNib"""
128
128
129 self._common_init()
129 self._common_init()
130
130
131 # Start the IPython engine
131 # Start the IPython engine
132 self.engine.startService()
132 self.engine.startService()
133 NSLog('IPython engine started')
133 NSLog('IPython engine started')
134
134
135 # Register for app termination
135 # Register for app termination
136 nc = NSNotificationCenter.defaultCenter()
136 nc = NSNotificationCenter.defaultCenter()
137 nc.addObserver_selector_name_object_(
137 nc.addObserver_selector_name_object_(
138 self,
138 self,
139 'appWillTerminate:',
139 'appWillTerminate:',
140 NSApplicationWillTerminateNotification,
140 NSApplicationWillTerminateNotification,
141 None)
141 None)
142
142
143 self.textView.setDelegate_(self)
143 self.textView.setDelegate_(self)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 self.textView.enclosingScrollView(),
146 self.textView.enclosingScrollView(),
147 NSVerticalRuler)
147 NSVerticalRuler)
148 self.verticalRulerView = r
148 self.verticalRulerView = r
149 self.verticalRulerView.setClientView_(self.textView)
149 self.verticalRulerView.setClientView_(self.textView)
150 self._start_cli_banner()
150 self._start_cli_banner()
151
151
152
152
153 def appWillTerminate_(self, notification):
153 def appWillTerminate_(self, notification):
154 """appWillTerminate"""
154 """appWillTerminate"""
155
155
156 self.engine.stopService()
156 self.engine.stopService()
157
157
158
158
159 def complete(self, token):
159 def complete(self, token):
160 """Complete token in engine's user_ns
160 """Complete token in engine's user_ns
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 token : string
164 token : string
165
165
166 Result
166 Result
167 ------
167 ------
168 Deferred result of
168 Deferred result of
169 IPython.kernel.engineservice.IEngineBase.complete
169 IPython.kernel.engineservice.IEngineBase.complete
170 """
170 """
171
171
172 return self.engine.complete(token)
172 return self.engine.complete(token)
173
173
174
174
175 def execute(self, block, blockID=None):
175 def execute(self, block, blockID=None):
176 self.waitingForEngine = True
176 self.waitingForEngine = True
177 self.willChangeValueForKey_('commandHistory')
177 self.willChangeValueForKey_('commandHistory')
178 d = super(IPythonCocoaController, self).execute(block,
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
179 blockID)
180 d.addBoth(self._engine_done)
180 d.addBoth(self._engine_done)
181 d.addCallback(self._update_user_ns)
181 d.addCallback(self._update_user_ns)
182
182
183 return d
183 return d
184
184
185
185
186 def push_(self, namespace):
186 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
187 """Push dictionary of key=>values to python namespace"""
188
188
189 self.waitingForEngine = True
189 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
190 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
191 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
192 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
193 d.addCallback(self._update_user_ns)
194
194
195
195
196 def pull_(self, keys):
196 def pull_(self, keys):
197 """Pull keys from python namespace"""
197 """Pull keys from python namespace"""
198
198
199 self.waitingForEngine = True
199 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
200 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
201 self.waitingForEngine = False
202
202
203 @objc.signature('v@:@I')
203 @objc.signature('v@:@I')
204 def executeFileAtPath_encoding_(self, path, encoding):
204 def executeFileAtPath_encoding_(self, path, encoding):
205 """Execute file at path in an empty namespace. Update the engine
205 """Execute file at path in an empty namespace. Update the engine
206 user_ns with the resulting locals."""
206 user_ns with the resulting locals."""
207
207
208 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
208 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
209 path,
209 path,
210 encoding,
210 encoding,
211 None)
211 None)
212 self.engine.execute(lines)
212 self.engine.execute(lines)
213
213
214
214
215 def _engine_done(self, x):
215 def _engine_done(self, x):
216 self.waitingForEngine = False
216 self.waitingForEngine = False
217 self.didChangeValueForKey_('commandHistory')
217 self.didChangeValueForKey_('commandHistory')
218 return x
218 return x
219
219
220 def _update_user_ns(self, result):
220 def _update_user_ns(self, result):
221 """Update self.userNS from self.engine's namespace"""
221 """Update self.userNS from self.engine's namespace"""
222 d = self.engine.keys()
222 d = self.engine.keys()
223 d.addCallback(self._get_engine_namespace_values_for_keys)
223 d.addCallback(self._get_engine_namespace_values_for_keys)
224
224
225 return result
225 return result
226
226
227
227
228 def _get_engine_namespace_values_for_keys(self, keys):
228 def _get_engine_namespace_values_for_keys(self, keys):
229 d = self.engine.pull(keys)
229 d = self.engine.pull(keys)
230 d.addCallback(self._store_engine_namespace_values, keys=keys)
230 d.addCallback(self._store_engine_namespace_values, keys=keys)
231
231
232
232
233 def _store_engine_namespace_values(self, values, keys=[]):
233 def _store_engine_namespace_values(self, values, keys=[]):
234 assert(len(values) == len(keys))
234 assert(len(values) == len(keys))
235 self.willChangeValueForKey_('userNS')
235 self.willChangeValueForKey_('userNS')
236 for (k,v) in zip(keys,values):
236 for (k,v) in zip(keys,values):
237 self.userNS[k] = saferepr(v)
237 self.userNS[k] = saferepr(v)
238 self.didChangeValueForKey_('userNS')
238 self.didChangeValueForKey_('userNS')
239
239
240
240
241 def update_cell_prompt(self, result, blockID=None):
241 def update_cell_prompt(self, result, blockID=None):
242 if(isinstance(result, Failure)):
242 if(isinstance(result, Failure)):
243 self.insert_text(self.input_prompt(),
243 self.insert_text(self.input_prompt(),
244 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
244 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
245 scrollToVisible=False
245 scrollToVisible=False
246 )
246 )
247 else:
247 else:
248 self.insert_text(self.input_prompt(number=result['number']),
248 self.insert_text(self.input_prompt(number=result['number']),
249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
250 scrollToVisible=False
250 scrollToVisible=False
251 )
251 )
252
252
253 return result
253 return result
254
254
255
255
256 def render_result(self, result):
256 def render_result(self, result):
257 blockID = result['blockID']
257 blockID = result['blockID']
258 inputRange = self.blockRanges[blockID]
258 inputRange = self.blockRanges[blockID]
259 del self.blockRanges[blockID]
259 del self.blockRanges[blockID]
260
260
261 #print inputRange,self.current_block_range()
261 #print inputRange,self.current_block_range()
262 self.insert_text('\n' +
262 self.insert_text('\n' +
263 self.output_prompt(number=result['number']) +
263 self.output_prompt(number=result['number']) +
264 result.get('display',{}).get('pprint','') +
264 result.get('display',{}).get('pprint','') +
265 '\n\n',
265 '\n\n',
266 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 textRange=NSMakeRange(inputRange.location+inputRange.length,
267 0))
267 0))
268 return result
268 return result
269
269
270
270
271 def render_error(self, failure):
271 def render_error(self, failure):
272 self.insert_text('\n' +
272 self.insert_text('\n' +
273 self.output_prompt() +
273 self.output_prompt() +
274 '\n' +
274 '\n' +
275 failure.getErrorMessage() +
275 failure.getErrorMessage() +
276 '\n\n')
276 '\n\n')
277 self.start_new_block()
277 self.start_new_block()
278 return failure
278 return failure
279
279
280
280
281 def _start_cli_banner(self):
281 def _start_cli_banner(self):
282 """Print banner"""
282 """Print banner"""
283
283
284 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
285 IPython.__version__
285 IPython.__version__
286
286
287 self.insert_text(banner + '\n\n')
287 self.insert_text(banner + '\n\n')
288
288
289
289
290 def start_new_block(self):
290 def start_new_block(self):
291 """"""
291 """"""
292
292
293 self.currentBlockID = self.next_block_ID()
293 self.currentBlockID = self.next_block_ID()
294
294
295
295
296
296
297 def next_block_ID(self):
297 def next_block_ID(self):
298
298
299 return uuid.uuid4()
299 return uuid.uuid4()
300
300
301 def current_block_range(self):
301 def current_block_range(self):
302 return self.blockRanges.get(self.currentBlockID,
302 return self.blockRanges.get(self.currentBlockID,
303 NSMakeRange(self.textView.textStorage().length(),
303 NSMakeRange(self.textView.textStorage().length(),
304 0))
304 0))
305
305
306 def current_block(self):
306 def current_block(self):
307 """The current block's text"""
307 """The current block's text"""
308
308
309 return self.text_for_range(self.current_block_range())
309 return self.text_for_range(self.current_block_range())
310
310
311 def text_for_range(self, textRange):
311 def text_for_range(self, textRange):
312 """text_for_range"""
312 """text_for_range"""
313
313
314 ts = self.textView.textStorage()
314 ts = self.textView.textStorage()
315 return ts.string().substringWithRange_(textRange)
315 return ts.string().substringWithRange_(textRange)
316
316
317 def current_line(self):
317 def current_line(self):
318 block = self.text_for_range(self.current_block_range())
318 block = self.text_for_range(self.current_block_range())
319 block = block.split('\n')
319 block = block.split('\n')
320 return block[-1]
320 return block[-1]
321
321
322
322
323 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
324 """Insert text into textView at textRange, updating blockRanges
324 """Insert text into textView at textRange, updating blockRanges
325 as necessary
325 as necessary
326 """
326 """
327
327
328 if(textRange == None):
328 if(textRange == None):
329 #range for end of text
329 #range for end of text
330 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
331
331
332 for r in self.blockRanges.itervalues():
332 for r in self.blockRanges.itervalues():
333 intersection = NSIntersectionRange(r,textRange)
333 intersection = NSIntersectionRange(r,textRange)
334 if(intersection.length == 0): #ranges don't intersect
334 if(intersection.length == 0): #ranges don't intersect
335 if r.location >= textRange.location:
335 if r.location >= textRange.location:
336 r.location += len(string)
336 r.location += len(string)
337 else: #ranges intersect
337 else: #ranges intersect
338 if(r.location <= textRange.location):
338 if(r.location <= textRange.location):
339 assert(intersection.length == textRange.length)
339 assert(intersection.length == textRange.length)
340 r.length += textRange.length
340 r.length += textRange.length
341 else:
341 else:
342 r.location += intersection.length
342 r.location += intersection.length
343
343
344 self.textView.replaceCharactersInRange_withString_(
344 self.textView.replaceCharactersInRange_withString_(
345 textRange, string)
345 textRange, string)
346 self.textView.setSelectedRange_(
346 self.textView.setSelectedRange_(
347 NSMakeRange(textRange.location+len(string), 0))
347 NSMakeRange(textRange.location+len(string), 0))
348 if(scrollToVisible):
348 if(scrollToVisible):
349 self.textView.scrollRangeToVisible_(textRange)
349 self.textView.scrollRangeToVisible_(textRange)
350
350
351
351
352
352
353
353
354 def replace_current_block_with_string(self, textView, string):
354 def replace_current_block_with_string(self, textView, string):
355 textView.replaceCharactersInRange_withString_(
355 textView.replaceCharactersInRange_withString_(
356 self.current_block_range(),
356 self.current_block_range(),
357 string)
357 string)
358 self.current_block_range().length = len(string)
358 self.current_block_range().length = len(string)
359 r = NSMakeRange(textView.textStorage().length(), 0)
359 r = NSMakeRange(textView.textStorage().length(), 0)
360 textView.scrollRangeToVisible_(r)
360 textView.scrollRangeToVisible_(r)
361 textView.setSelectedRange_(r)
361 textView.setSelectedRange_(r)
362
362
363
363
364 def current_indent_string(self):
364 def current_indent_string(self):
365 """returns string for indent or None if no indent"""
365 """returns string for indent or None if no indent"""
366
366
367 return self._indent_for_block(self.current_block())
367 return self._indent_for_block(self.current_block())
368
368
369
369
370 def _indent_for_block(self, block):
370 def _indent_for_block(self, block):
371 lines = block.split('\n')
371 lines = block.split('\n')
372 if(len(lines) > 1):
372 if(len(lines) > 1):
373 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
373 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
374 if(currentIndent == 0):
374 if(currentIndent == 0):
375 currentIndent = self.tabSpaces
375 currentIndent = self.tabSpaces
376
376
377 if(self.tabUsesSpaces):
377 if(self.tabUsesSpaces):
378 result = ' ' * currentIndent
378 result = ' ' * currentIndent
379 else:
379 else:
380 result = '\t' * (currentIndent/self.tabSpaces)
380 result = '\t' * (currentIndent/self.tabSpaces)
381 else:
381 else:
382 result = None
382 result = None
383
383
384 return result
384 return result
385
385
386
386
387 # NSTextView delegate methods...
387 # NSTextView delegate methods...
388 def textView_doCommandBySelector_(self, textView, selector):
388 def textView_doCommandBySelector_(self, textView, selector):
389 assert(textView == self.textView)
389 assert(textView == self.textView)
390 NSLog("textView_doCommandBySelector_: "+selector)
390 NSLog("textView_doCommandBySelector_: "+selector)
391
391
392
392
393 if(selector == 'insertNewline:'):
393 if(selector == 'insertNewline:'):
394 indent = self.current_indent_string()
394 indent = self.current_indent_string()
395 if(indent):
395 if(indent):
396 line = indent + self.current_line()
396 line = indent + self.current_line()
397 else:
397 else:
398 line = self.current_line()
398 line = self.current_line()
399
399
400 if(self.is_complete(self.current_block())):
400 if(self.is_complete(self.current_block())):
401 self.execute(self.current_block(),
401 self.execute(self.current_block(),
402 blockID=self.currentBlockID)
402 blockID=self.currentBlockID)
403 self.start_new_block()
403 self.start_new_block()
404
404
405 return True
405 return True
406
406
407 return False
407 return False
408
408
409 elif(selector == 'moveUp:'):
409 elif(selector == 'moveUp:'):
410 prevBlock = self.get_history_previous(self.current_block())
410 prevBlock = self.get_history_previous(self.current_block())
411 if(prevBlock != None):
411 if(prevBlock != None):
412 self.replace_current_block_with_string(textView, prevBlock)
412 self.replace_current_block_with_string(textView, prevBlock)
413 else:
413 else:
414 NSBeep()
414 NSBeep()
415 return True
415 return True
416
416
417 elif(selector == 'moveDown:'):
417 elif(selector == 'moveDown:'):
418 nextBlock = self.get_history_next()
418 nextBlock = self.get_history_next()
419 if(nextBlock != None):
419 if(nextBlock != None):
420 self.replace_current_block_with_string(textView, nextBlock)
420 self.replace_current_block_with_string(textView, nextBlock)
421 else:
421 else:
422 NSBeep()
422 NSBeep()
423 return True
423 return True
424
424
425 elif(selector == 'moveToBeginningOfParagraph:'):
425 elif(selector == 'moveToBeginningOfParagraph:'):
426 textView.setSelectedRange_(NSMakeRange(
426 textView.setSelectedRange_(NSMakeRange(
427 self.current_block_range().location,
427 self.current_block_range().location,
428 0))
428 0))
429 return True
429 return True
430 elif(selector == 'moveToEndOfParagraph:'):
430 elif(selector == 'moveToEndOfParagraph:'):
431 textView.setSelectedRange_(NSMakeRange(
431 textView.setSelectedRange_(NSMakeRange(
432 self.current_block_range().location + \
432 self.current_block_range().location + \
433 self.current_block_range().length, 0))
433 self.current_block_range().length, 0))
434 return True
434 return True
435 elif(selector == 'deleteToEndOfParagraph:'):
435 elif(selector == 'deleteToEndOfParagraph:'):
436 if(textView.selectedRange().location <= \
436 if(textView.selectedRange().location <= \
437 self.current_block_range().location):
437 self.current_block_range().location):
438 # Intersect the selected range with the current line range
438 # Intersect the selected range with the current line range
439 if(self.current_block_range().length < 0):
439 if(self.current_block_range().length < 0):
440 self.blockRanges[self.currentBlockID].length = 0
440 self.blockRanges[self.currentBlockID].length = 0
441
441
442 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
443 self.current_block_range())
443 self.current_block_range())
444
444
445 if(r.length > 0): #no intersection
445 if(r.length > 0): #no intersection
446 textView.setSelectedRange_(r)
446 textView.setSelectedRange_(r)
447
447
448 return False # don't actually handle the delete
448 return False # don't actually handle the delete
449
449
450 elif(selector == 'insertTab:'):
450 elif(selector == 'insertTab:'):
451 if(len(self.current_line().strip()) == 0): #only white space
451 if(len(self.current_line().strip()) == 0): #only white space
452 return False
452 return False
453 else:
453 else:
454 self.textView.complete_(self)
454 self.textView.complete_(self)
455 return True
455 return True
456
456
457 elif(selector == 'deleteBackward:'):
457 elif(selector == 'deleteBackward:'):
458 #if we're at the beginning of the current block, ignore
458 #if we're at the beginning of the current block, ignore
459 if(textView.selectedRange().location == \
459 if(textView.selectedRange().location == \
460 self.current_block_range().location):
460 self.current_block_range().location):
461 return True
461 return True
462 else:
462 else:
463 self.current_block_range().length-=1
463 self.current_block_range().length-=1
464 return False
464 return False
465 return False
465 return False
466
466
467
467
468 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 def textView_shouldChangeTextInRanges_replacementStrings_(self,
469 textView, ranges, replacementStrings):
469 textView, ranges, replacementStrings):
470 """
470 """
471 Delegate method for NSTextView.
471 Delegate method for NSTextView.
472
472
473 Refuse change text in ranges not at end, but make those changes at
473 Refuse change text in ranges not at end, but make those changes at
474 end.
474 end.
475 """
475 """
476
476
477 assert(len(ranges) == len(replacementStrings))
477 assert(len(ranges) == len(replacementStrings))
478 allow = True
478 allow = True
479 for r,s in zip(ranges, replacementStrings):
479 for r,s in zip(ranges, replacementStrings):
480 r = r.rangeValue()
480 r = r.rangeValue()
481 if(textView.textStorage().length() > 0 and
481 if(textView.textStorage().length() > 0 and
482 r.location < self.current_block_range().location):
482 r.location < self.current_block_range().location):
483 self.insert_text(s)
483 self.insert_text(s)
484 allow = False
484 allow = False
485
485
486
486
487 self.blockRanges.setdefault(self.currentBlockID,
487 self.blockRanges.setdefault(self.currentBlockID,
488 self.current_block_range()).length +=\
488 self.current_block_range()).length +=\
489 len(s)
489 len(s)
490
490
491 return allow
491 return allow
492
492
493 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
494 textView, words, charRange, index):
494 textView, words, charRange, index):
495 try:
495 try:
496 ts = textView.textStorage()
496 ts = textView.textStorage()
497 token = ts.string().substringWithRange_(charRange)
497 token = ts.string().substringWithRange_(charRange)
498 completions = blockingCallFromThread(self.complete, token)
498 completions = blockingCallFromThread(self.complete, token)
499 except:
499 except:
500 completions = objc.nil
500 completions = objc.nil
501 NSBeep()
501 NSBeep()
502
502
503 return (completions,0)
503 return (completions,0)
504
504
505
505
@@ -1,407 +1,352 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 try:
28 from zope.interface import Interface, Attribute, implements, classProvides
28 from zope.interface import Interface, Attribute, implements, classProvides
29 except ImportError:
29 except ImportError:
30 #zope.interface is not available
30 #zope.interface is not available
31 Interface = object
31 Interface = object
32 def Attribute(name, doc): pass
32 def Attribute(name, doc): pass
33 def implements(interface): pass
33 def implements(interface): pass
34 def classProvides(interface): pass
34 def classProvides(interface): pass
35
35
36 from IPython.kernel.core.history import FrontEndHistory
36 from IPython.kernel.core.history import FrontEndHistory
37 from IPython.kernel.core.util import Bunch
37 from IPython.kernel.core.util import Bunch
38 from IPython.kernel.engineservice import IEngineCore
38 from IPython.kernel.engineservice import IEngineCore
39
39
40 try:
41 from twisted.python.failure import Failure
42 except ImportError:
43 #Twisted not available
44 Failure = Exception
45
46 ##############################################################################
40 ##############################################################################
47 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
41 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
48 # not
42 # not
49
43
50 rc = Bunch()
44 rc = Bunch()
51 rc.prompt_in1 = r'In [$number]: '
45 rc.prompt_in1 = r'In [$number]: '
52 rc.prompt_in2 = r'...'
46 rc.prompt_in2 = r'...'
53 rc.prompt_out = r'Out [$number]: '
47 rc.prompt_out = r'Out [$number]: '
54
48
55 ##############################################################################
49 ##############################################################################
56
50
57 class IFrontEndFactory(Interface):
51 class IFrontEndFactory(Interface):
58 """Factory interface for frontends."""
52 """Factory interface for frontends."""
59
53
60 def __call__(engine=None, history=None):
54 def __call__(engine=None, history=None):
61 """
55 """
62 Parameters:
56 Parameters:
63 interpreter : IPython.kernel.engineservice.IEngineCore
57 interpreter : IPython.kernel.engineservice.IEngineCore
64 """
58 """
65
59
66 pass
60 pass
67
61
68
62
69
63
70 class IFrontEnd(Interface):
64 class IFrontEnd(Interface):
71 """Interface for frontends. All methods return t.i.d.Deferred"""
65 """Interface for frontends. All methods return t.i.d.Deferred"""
72
66
73 Attribute("input_prompt_template", "string.Template instance\
67 Attribute("input_prompt_template", "string.Template instance\
74 substituteable with execute result.")
68 substituteable with execute result.")
75 Attribute("output_prompt_template", "string.Template instance\
69 Attribute("output_prompt_template", "string.Template instance\
76 substituteable with execute result.")
70 substituteable with execute result.")
77 Attribute("continuation_prompt_template", "string.Template instance\
71 Attribute("continuation_prompt_template", "string.Template instance\
78 substituteable with execute result.")
72 substituteable with execute result.")
79
73
80 def update_cell_prompt(result, blockID=None):
74 def update_cell_prompt(result, blockID=None):
81 """Subclass may override to update the input prompt for a block.
75 """Subclass may override to update the input prompt for a block.
82 Since this method will be called as a
76 Since this method will be called as a
83 twisted.internet.defer.Deferred's callback/errback,
77 twisted.internet.defer.Deferred's callback/errback,
84 implementations should return result when finished.
78 implementations should return result when finished.
85
79
86 Result is a result dict in case of success, and a
80 Result is a result dict in case of success, and a
87 twisted.python.util.failure.Failure in case of an error
81 twisted.python.util.failure.Failure in case of an error
88 """
82 """
89
83
90 pass
84 pass
91
85
92
86
93 def render_result(result):
87 def render_result(result):
94 """Render the result of an execute call. Implementors may choose the
88 """Render the result of an execute call. Implementors may choose the
95 method of rendering.
89 method of rendering.
96 For example, a notebook-style frontend might render a Chaco plot
90 For example, a notebook-style frontend might render a Chaco plot
97 inline.
91 inline.
98
92
99 Parameters:
93 Parameters:
100 result : dict (result of IEngineBase.execute )
94 result : dict (result of IEngineBase.execute )
101 blockID = result['blockID']
95 blockID = result['blockID']
102
96
103 Result:
97 Result:
104 Output of frontend rendering
98 Output of frontend rendering
105 """
99 """
106
100
107 pass
101 pass
108
102
109 def render_error(failure):
103 def render_error(failure):
110 """Subclasses must override to render the failure. Since this method
104 """Subclasses must override to render the failure. Since this method
111 will be called as a twisted.internet.defer.Deferred's callback,
105 will be called as a twisted.internet.defer.Deferred's callback,
112 implementations should return result when finished.
106 implementations should return result when finished.
113
107
114 blockID = failure.blockID
108 blockID = failure.blockID
115 """
109 """
116
110
117 pass
111 pass
118
112
119
113
120 def input_prompt(number=''):
114 def input_prompt(number=''):
121 """Returns the input prompt by subsituting into
115 """Returns the input prompt by subsituting into
122 self.input_prompt_template
116 self.input_prompt_template
123 """
117 """
124 pass
118 pass
125
119
126 def output_prompt(number=''):
120 def output_prompt(number=''):
127 """Returns the output prompt by subsituting into
121 """Returns the output prompt by subsituting into
128 self.output_prompt_template
122 self.output_prompt_template
129 """
123 """
130
124
131 pass
125 pass
132
126
133 def continuation_prompt():
127 def continuation_prompt():
134 """Returns the continuation prompt by subsituting into
128 """Returns the continuation prompt by subsituting into
135 self.continuation_prompt_template
129 self.continuation_prompt_template
136 """
130 """
137
131
138 pass
132 pass
139
133
140 def is_complete(block):
134 def is_complete(block):
141 """Returns True if block is complete, False otherwise."""
135 """Returns True if block is complete, False otherwise."""
142
136
143 pass
137 pass
144
138
145 def compile_ast(block):
139 def compile_ast(block):
146 """Compiles block to an _ast.AST"""
140 """Compiles block to an _ast.AST"""
147
141
148 pass
142 pass
149
143
150
144
151 def get_history_previous(currentBlock):
145 def get_history_previous(currentBlock):
152 """Returns the block previous in the history. Saves currentBlock if
146 """Returns the block previous in the history. Saves currentBlock if
153 the history_cursor is currently at the end of the input history"""
147 the history_cursor is currently at the end of the input history"""
154 pass
148 pass
155
149
156 def get_history_next():
150 def get_history_next():
157 """Returns the next block in the history."""
151 """Returns the next block in the history."""
158
152
159 pass
153 pass
160
154
161
155
162 class FrontEndBase(object):
156 class FrontEndBase(object):
163 """
157 """
164 FrontEndBase manages the state tasks for a CLI frontend:
158 FrontEndBase manages the state tasks for a CLI frontend:
165 - Input and output history management
159 - Input and output history management
166 - Input/continuation and output prompt generation
160 - Input/continuation and output prompt generation
167
161
168 Some issues (due to possibly unavailable engine):
162 Some issues (due to possibly unavailable engine):
169 - How do we get the current cell number for the engine?
163 - How do we get the current cell number for the engine?
170 - How do we handle completions?
164 - How do we handle completions?
171 """
165 """
172
166
173 history_cursor = 0
167 history_cursor = 0
174
168
175 current_indent_level = 0
169 current_indent_level = 0
176
170
177
171
178 input_prompt_template = string.Template(rc.prompt_in1)
172 input_prompt_template = string.Template(rc.prompt_in1)
179 output_prompt_template = string.Template(rc.prompt_out)
173 output_prompt_template = string.Template(rc.prompt_out)
180 continuation_prompt_template = string.Template(rc.prompt_in2)
174 continuation_prompt_template = string.Template(rc.prompt_in2)
181
175
182 def __init__(self, shell=None, history=None):
176 def __init__(self, shell=None, history=None):
183 self.shell = shell
177 self.shell = shell
184 if history is None:
178 if history is None:
185 self.history = FrontEndHistory(input_cache=[''])
179 self.history = FrontEndHistory(input_cache=[''])
186 else:
180 else:
187 self.history = history
181 self.history = history
188
182
189
183
190 def input_prompt(self, number=''):
184 def input_prompt(self, number=''):
191 """Returns the current input prompt
185 """Returns the current input prompt
192
186
193 It would be great to use ipython1.core.prompts.Prompt1 here
187 It would be great to use ipython1.core.prompts.Prompt1 here
194 """
188 """
195 return self.input_prompt_template.safe_substitute({'number':number})
189 return self.input_prompt_template.safe_substitute({'number':number})
196
190
197
191
198 def continuation_prompt(self):
192 def continuation_prompt(self):
199 """Returns the current continuation prompt"""
193 """Returns the current continuation prompt"""
200
194
201 return self.continuation_prompt_template.safe_substitute()
195 return self.continuation_prompt_template.safe_substitute()
202
196
203 def output_prompt(self, number=''):
197 def output_prompt(self, number=''):
204 """Returns the output prompt for result"""
198 """Returns the output prompt for result"""
205
199
206 return self.output_prompt_template.safe_substitute({'number':number})
200 return self.output_prompt_template.safe_substitute({'number':number})
207
201
208
202
209 def is_complete(self, block):
203 def is_complete(self, block):
210 """Determine if block is complete.
204 """Determine if block is complete.
211
205
212 Parameters
206 Parameters
213 block : string
207 block : string
214
208
215 Result
209 Result
216 True if block can be sent to the engine without compile errors.
210 True if block can be sent to the engine without compile errors.
217 False otherwise.
211 False otherwise.
218 """
212 """
219
213
220 try:
214 try:
221 ast = self.compile_ast(block)
215 ast = self.compile_ast(block)
222 except:
216 except:
223 return False
217 return False
224
218
225 lines = block.split('\n')
219 lines = block.split('\n')
226 return (len(lines)==1 or str(lines[-1])=='')
220 return (len(lines)==1 or str(lines[-1])=='')
227
221
228
222
229 def compile_ast(self, block):
223 def compile_ast(self, block):
230 """Compile block to an AST
224 """Compile block to an AST
231
225
232 Parameters:
226 Parameters:
233 block : str
227 block : str
234
228
235 Result:
229 Result:
236 AST
230 AST
237
231
238 Throws:
232 Throws:
239 Exception if block cannot be compiled
233 Exception if block cannot be compiled
240 """
234 """
241
235
242 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
236 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
243
237
244
238
245 def execute(self, block, blockID=None):
239 def execute(self, block, blockID=None):
246 """Execute the block and return the result.
240 """Execute the block and return the result.
247
241
248 Parameters:
242 Parameters:
249 block : {str, AST}
243 block : {str, AST}
250 blockID : any
244 blockID : any
251 Caller may provide an ID to identify this block.
245 Caller may provide an ID to identify this block.
252 result['blockID'] := blockID
246 result['blockID'] := blockID
253
247
254 Result:
248 Result:
255 Deferred result of self.interpreter.execute
249 Deferred result of self.interpreter.execute
256 """
250 """
257
251
258 if(not self.is_complete(block)):
252 if(not self.is_complete(block)):
259 raise Exception("Block is not compilable")
253 raise Exception("Block is not compilable")
260
254
261 if(blockID == None):
255 if(blockID == None):
262 blockID = uuid.uuid4() #random UUID
256 blockID = uuid.uuid4() #random UUID
263
257
264 try:
258 try:
265 result = self.shell.execute(block)
259 result = self.shell.execute(block)
266 except Exception,e:
260 except Exception,e:
267 e = self._add_block_id_for_failure(e, blockID=blockID)
261 e = self._add_block_id_for_failure(e, blockID=blockID)
268 e = self.update_cell_prompt(e, blockID=blockID)
262 e = self.update_cell_prompt(e, blockID=blockID)
269 e = self.render_error(e)
263 e = self.render_error(e)
270 else:
264 else:
271 result = self._add_block_id_for_result(result, blockID=blockID)
265 result = self._add_block_id_for_result(result, blockID=blockID)
272 result = self.update_cell_prompt(result, blockID=blockID)
266 result = self.update_cell_prompt(result, blockID=blockID)
273 result = self.render_result(result)
267 result = self.render_result(result)
274
268
275 return result
269 return result
276
270
277
271
278 def _add_block_id_for_result(self, result, blockID):
272 def _add_block_id_for_result(self, result, blockID):
279 """Add the blockID to result or failure. Unfortunatley, we have to
273 """Add the blockID to result or failure. Unfortunatley, we have to
280 treat failures differently than result dicts.
274 treat failures differently than result dicts.
281 """
275 """
282
276
283 result['blockID'] = blockID
277 result['blockID'] = blockID
284
278
285 return result
279 return result
286
280
287 def _add_block_id_for_failure(self, failure, blockID):
281 def _add_block_id_for_failure(self, failure, blockID):
288 """_add_block_id_for_failure"""
282 """_add_block_id_for_failure"""
289
283
290 failure.blockID = blockID
284 failure.blockID = blockID
291 return failure
285 return failure
292
286
293
287
294 def _add_history(self, result, block=None):
288 def _add_history(self, result, block=None):
295 """Add block to the history"""
289 """Add block to the history"""
296
290
297 assert(block != None)
291 assert(block != None)
298 self.history.add_items([block])
292 self.history.add_items([block])
299 self.history_cursor += 1
293 self.history_cursor += 1
300
294
301 return result
295 return result
302
296
303
297
304 def get_history_previous(self, currentBlock):
298 def get_history_previous(self, currentBlock):
305 """ Returns previous history string and decrement history cursor.
299 """ Returns previous history string and decrement history cursor.
306 """
300 """
307 command = self.history.get_history_item(self.history_cursor - 1)
301 command = self.history.get_history_item(self.history_cursor - 1)
308
302
309 if command is not None:
303 if command is not None:
310 if(self.history_cursor == len(self.history.input_cache)):
304 if(self.history_cursor == len(self.history.input_cache)):
311 self.history.input_cache[self.history_cursor] = currentBlock
305 self.history.input_cache[self.history_cursor] = currentBlock
312 self.history_cursor -= 1
306 self.history_cursor -= 1
313 return command
307 return command
314
308
315
309
316 def get_history_next(self):
310 def get_history_next(self):
317 """ Returns next history string and increment history cursor.
311 """ Returns next history string and increment history cursor.
318 """
312 """
319 command = self.history.get_history_item(self.history_cursor+1)
313 command = self.history.get_history_item(self.history_cursor+1)
320
314
321 if command is not None:
315 if command is not None:
322 self.history_cursor += 1
316 self.history_cursor += 1
323 return command
317 return command
324
318
325 ###
319 ###
326 # Subclasses probably want to override these methods...
320 # Subclasses probably want to override these methods...
327 ###
321 ###
328
322
329 def update_cell_prompt(self, result, blockID=None):
323 def update_cell_prompt(self, result, blockID=None):
330 """Subclass may override to update the input prompt for a block.
324 """Subclass may override to update the input prompt for a block.
331 Since this method will be called as a
325 Since this method will be called as a
332 twisted.internet.defer.Deferred's callback, implementations should
326 twisted.internet.defer.Deferred's callback, implementations should
333 return result when finished.
327 return result when finished.
334 """
328 """
335
329
336 return result
330 return result
337
331
338
332
339 def render_result(self, result):
333 def render_result(self, result):
340 """Subclasses must override to render result. Since this method will
334 """Subclasses must override to render result. Since this method will
341 be called as a twisted.internet.defer.Deferred's callback,
335 be called as a twisted.internet.defer.Deferred's callback,
342 implementations should return result when finished.
336 implementations should return result when finished.
343 """
337 """
344
338
345 return result
339 return result
346
340
347
341
348 def render_error(self, failure):
342 def render_error(self, failure):
349 """Subclasses must override to render the failure. Since this method
343 """Subclasses must override to render the failure. Since this method
350 will be called as a twisted.internet.defer.Deferred's callback,
344 will be called as a twisted.internet.defer.Deferred's callback,
351 implementations should return result when finished.
345 implementations should return result when finished.
352 """
346 """
353
347
354 return failure
348 return failure
355
349
356
350
357
351
358 class AsyncFrontEndBase(FrontEndBase):
359 """
360 Overrides FrontEndBase to wrap execute in a deferred result.
361 All callbacks are made as callbacks on the deferred result.
362 """
363
364 implements(IFrontEnd)
365 classProvides(IFrontEndFactory)
366
367 def __init__(self, engine=None, history=None):
368 assert(engine==None or IEngineCore.providedBy(engine))
369 self.engine = IEngineCore(engine)
370 if history is None:
371 self.history = FrontEndHistory(input_cache=[''])
372 else:
373 self.history = history
374
375
376 def execute(self, block, blockID=None):
377 """Execute the block and return the deferred result.
378
379 Parameters:
380 block : {str, AST}
381 blockID : any
382 Caller may provide an ID to identify this block.
383 result['blockID'] := blockID
384
385 Result:
386 Deferred result of self.interpreter.execute
387 """
388
389 if(not self.is_complete(block)):
390 return Failure(Exception("Block is not compilable"))
391
392 if(blockID == None):
393 blockID = uuid.uuid4() #random UUID
394
395 d = self.engine.execute(block)
396 d.addCallback(self._add_history, block=block)
397 d.addCallbacks(self._add_block_id_for_result,
398 errback=self._add_block_id_for_failure,
399 callbackArgs=(blockID,),
400 errbackArgs=(blockID,))
401 d.addBoth(self.update_cell_prompt, blockID=blockID)
402 d.addCallbacks(self.render_result,
403 errback=self.render_error)
404
405 return d
406
407
352
@@ -1,207 +1,111 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 ipython1.kernel.engineservice.EngineService.
6 ipython1.kernel.engineservice.EngineService.
7
7
8 """
8 """
9
9
10 __docformat__ = "restructuredtext en"
10 __docformat__ = "restructuredtext en"
11
11
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
13 # Copyright (C) 2008 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
17 #-------------------------------------------------------------------------------
18
18
19 #-------------------------------------------------------------------------------
19 #-------------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-------------------------------------------------------------------------------
21 #-------------------------------------------------------------------------------
22
22
23
23
24 import wx
24 import wx
25 from console_widget import ConsoleWidget
25 from console_widget import ConsoleWidget
26 import re
27
26
28 import IPython
27 from IPython.frontend.linefrontendbase import LineFrontEndBase
29 from IPython.kernel.engineservice import EngineService
30 from IPython.frontend.frontendbase import FrontEndBase
31
28
32 #-------------------------------------------------------------------------------
29 #-------------------------------------------------------------------------------
33 # Classes to implement the Wx frontend
30 # Classes to implement the Wx frontend
34 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
35
32
36
33
37
34
38
35
39 class IPythonWxController(FrontEndBase, ConsoleWidget):
36 class IPythonWxController(LineFrontEndBase, ConsoleWidget):
40
37
41 output_prompt = \
38 output_prompt = \
42 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
39 '\n\x01\x1b[0;31m\x02Out[\x01\x1b[1;31m\x02%i\x01\x1b[0;31m\x02]: \x01\x1b[0m\x02'
43
40
44 # Are we entering multi line input?
45 multi_line_input = False
46
47 # The added tab stop to the string. It may, for instance, come from
48 # copy and pasting something with tabs.
49 tab_stop = 0
50 # FIXME: We still have to deal with this.
51
52 #--------------------------------------------------------------------------
41 #--------------------------------------------------------------------------
53 # Public API
42 # Public API
54 #--------------------------------------------------------------------------
43 #--------------------------------------------------------------------------
55
44
56 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
45 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
57 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
46 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
58 *args, **kwds):
47 *args, **kwds):
59 """ Create Shell instance.
48 """ Create Shell instance.
60 """
49 """
61 ConsoleWidget.__init__(self, parent, id, pos, size, style)
50 ConsoleWidget.__init__(self, parent, id, pos, size, style)
62 FrontEndBase.__init__(self, engine=EngineService(),
51 LineFrontEndBase.__init__(self)
63 )
64
65 # FIXME: Something is wrong with the history, I instanciate it
66 # with an empty cache, but this is not the way to do.
67 self.lines = {}
68
52
69 # Start the IPython engine
70 self.engine.startService()
71
72 # Capture Character keys
53 # Capture Character keys
73 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
54 self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
74
55
75 #FIXME: print banner.
76 banner = """IPython1 %s -- An enhanced Interactive Python.""" \
77 % IPython.__version__
78
79
80 def appWillTerminate_(self, notification):
81 """appWillTerminate"""
82
83 self.engine.stopService()
84
85
86 def complete(self, token):
87 """Complete token in engine's user_ns
88
89 Parameters
90 ----------
91 token : string
92
93 Result
94 ------
95 Deferred result of
96 IPython.kernel.engineservice.IEngineBase.complete
97 """
98
99 return self.engine.complete(token)
100
101
102 def render_result(self, result):
103 if 'stdout' in result and result['stdout']:
104 self.write('\n' + result['stdout'])
105 if 'display' in result and result['display']:
106 self.write("%s%s\n" % (
107 self.output_prompt % result['number'],
108 result['display']['pprint']
109 ) )
110
111
112 def render_error(self, failure):
113 self.insert_text('\n\n'+str(failure)+'\n\n')
114 return failure
115
116
117 #--------------------------------------------------------------------------
56 #--------------------------------------------------------------------------
118 # Private API
57 # Private API
119 #--------------------------------------------------------------------------
58 #--------------------------------------------------------------------------
120
59
121
60
122 def _on_key_down(self, event, skip=True):
61 def _on_key_down(self, event, skip=True):
123 """ Capture the character events, let the parent
62 """ Capture the character events, let the parent
124 widget handle them, and put our logic afterward.
63 widget handle them, and put our logic afterward.
125 """
64 """
126 current_line_number = self.GetCurrentLine()
65 current_line_number = self.GetCurrentLine()
127 # Capture enter
66 # Capture enter
128 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
67 if event.KeyCode in (13, wx.WXK_NUMPAD_ENTER) and \
129 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
68 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN):
130 self._on_enter()
69 self.on_enter()
131 # Up history
70 # Up history
132 elif event.KeyCode == wx.WXK_UP and (
71 elif event.KeyCode == wx.WXK_UP and (
133 ( current_line_number == self.current_prompt_line and
72 ( current_line_number == self.current_prompt_line and
134 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
73 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
135 or event.ControlDown() ):
74 or event.ControlDown() ):
136 new_buffer = self.get_history_previous(
75 new_buffer = self.get_history_previous(
137 self.get_current_edit_buffer())
76 self.get_current_edit_buffer())
138 if new_buffer is not None:
77 if new_buffer is not None:
139 self.replace_current_edit_buffer(new_buffer)
78 self.replace_current_edit_buffer(new_buffer)
140 # Down history
79 # Down history
141 elif event.KeyCode == wx.WXK_DOWN and (
80 elif event.KeyCode == wx.WXK_DOWN and (
142 ( current_line_number == self.LineCount -1 and
81 ( current_line_number == self.LineCount -1 and
143 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
82 event.Modifiers in (wx.MOD_NONE, wx.MOD_WIN) )
144 or event.ControlDown() ):
83 or event.ControlDown() ):
145 new_buffer = self.get_history_next()
84 new_buffer = self.get_history_next()
146 if new_buffer is not None:
85 if new_buffer is not None:
147 self.replace_current_edit_buffer(new_buffer)
86 self.replace_current_edit_buffer(new_buffer)
148 else:
87 else:
149 ConsoleWidget._on_key_down(self, event, skip=True)
88 ConsoleWidget._on_key_down(self, event, skip=True)
150
89
151
90
152 def _on_enter(self):
91
153 """ Called when the return key is pressed in a line editing
154 buffer.
155 """
156 current_buffer = self.get_current_edit_buffer()
157 current_buffer = current_buffer.replace('\r\n', '\n')
158 current_buffer = current_buffer.replace('\t', 4*' ')
159 cleaned_buffer = '\n'.join(l.rstrip()
160 for l in current_buffer.split('\n'))
161 if ( not self.multi_line_input
162 or re.findall(r"\n[\t ]*$", cleaned_buffer)):
163 if self.is_complete(cleaned_buffer):
164 self._add_history(None, cleaned_buffer.rstrip())
165 result = self.engine.shell.execute(cleaned_buffer)
166 self.render_result(result)
167 self.new_prompt(self.prompt % (result['number'] + 1))
168 self.multi_line_input = False
169 else:
170 if self.multi_line_input:
171 self.write('\n' + self._get_indent_string(current_buffer))
172 else:
173 self.multi_line_input = True
174 self.write('\n\t')
175 else:
176 self.write('\n'+self._get_indent_string(current_buffer))
177
178
179 def _get_indent_string(self, string):
180 string = string.split('\n')[-1]
181 indent_chars = len(string) - len(string.lstrip())
182 indent_string = '\t'*(indent_chars // 4) + \
183 ' '*(indent_chars % 4)
184
185 return indent_string
186
187
188
92
189 if __name__ == '__main__':
93 if __name__ == '__main__':
190 class MainWindow(wx.Frame):
94 class MainWindow(wx.Frame):
191 def __init__(self, parent, id, title):
95 def __init__(self, parent, id, title):
192 wx.Frame.__init__(self, parent, id, title, size=(300,250))
96 wx.Frame.__init__(self, parent, id, title, size=(300,250))
193 self._sizer = wx.BoxSizer(wx.VERTICAL)
97 self._sizer = wx.BoxSizer(wx.VERTICAL)
194 self.shell = IPythonWxController(self)
98 self.shell = IPythonWxController(self)
195 self._sizer.Add(self.shell, 1, wx.EXPAND)
99 self._sizer.Add(self.shell, 1, wx.EXPAND)
196 self.SetSizer(self._sizer)
100 self.SetSizer(self._sizer)
197 self.SetAutoLayout(1)
101 self.SetAutoLayout(1)
198 self.Show(True)
102 self.Show(True)
199
103
200 app = wx.PySimpleApp()
104 app = wx.PySimpleApp()
201 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
105 frame = MainWindow(None, wx.ID_ANY, 'Ipython')
202 frame.shell.SetFocus()
106 frame.shell.SetFocus()
203 frame.SetSize((660, 460))
107 frame.SetSize((660, 460))
204 self = frame.shell
108 self = frame.shell
205
109
206 app.MainLoop()
110 app.MainLoop()
207
111
General Comments 0
You need to be logged in to leave comments. Login now