##// END OF EJS Templates
fixed frontendbase.FrontEndBase.is_complete
Barry Wark -
Show More
@@ -1,369 +1,370 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: ipython1.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the ipython1.kernel.engineservice.EngineService.
4 """PyObjC classes to provide a Cocoa frontend to the ipython1.kernel.engineservice.EngineService.
5
5
6 The Cocoa frontend is divided into two classes:
6 The Cocoa frontend is divided into two classes:
7 - IPythonCocoaController
7 - IPythonCocoaController
8 - IPythonCLITextViewDelegate
8 - IPythonCLITextViewDelegate
9
9
10 To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH]
10 To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH]
11 """
11 """
12
12
13 __docformat__ = "restructuredtext en"
13 __docformat__ = "restructuredtext en"
14
14
15 #-------------------------------------------------------------------------------
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 Barry Wark <barrywark@gmail.com>
16 # Copyright (C) 2008 Barry Wark <barrywark@gmail.com>
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21
21
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-------------------------------------------------------------------------------
24 #-------------------------------------------------------------------------------
25
25
26 import objc
26 import objc
27 import uuid
27 import uuid
28
28
29 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
29 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
30 NSLog, NSNotificationCenter, NSMakeRange,\
30 NSLog, NSNotificationCenter, NSMakeRange,\
31 NSLocalizedString, NSIntersectionRange
31 NSLocalizedString, NSIntersectionRange
32 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
32 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
33 NSTextView, NSRulerView, NSVerticalRuler
33 NSTextView, NSRulerView, NSVerticalRuler
34
34
35 from pprint import saferepr
35 from pprint import saferepr
36
36
37 import IPython
37 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
38 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
38 from IPython.frontend.frontendbase import FrontEndBase
39 from IPython.frontend.frontendbase import FrontEndBase
39
40
40 from twisted.internet.threads import blockingCallFromThread
41 from twisted.internet.threads import blockingCallFromThread
41
42
42 #-------------------------------------------------------------------------------
43 #-------------------------------------------------------------------------------
43 # Classes to implement the Cocoa frontend
44 # Classes to implement the Cocoa frontend
44 #-------------------------------------------------------------------------------
45 #-------------------------------------------------------------------------------
45
46
46 # TODO:
47 # TODO:
47 # 1. use MultiEngineClient and out-of-process engine rather than ThreadedEngineService?
48 # 1. use MultiEngineClient and out-of-process engine rather than ThreadedEngineService?
48 # 2. integrate Xgrid launching of engines
49 # 2. integrate Xgrid launching of engines
49
50
50
51
51
52
52
53
53 class IPythonCocoaController(NSObject, FrontEndBase):
54 class IPythonCocoaController(NSObject, FrontEndBase):
54 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
55 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
55 waitingForEngine = objc.ivar().bool()
56 waitingForEngine = objc.ivar().bool()
56 textView = objc.IBOutlet()
57 textView = objc.IBOutlet()
57
58
58 def init(self):
59 def init(self):
59 self = super(IPythonCocoaController, self).init()
60 self = super(IPythonCocoaController, self).init()
60 FrontEndBase.__init__(self, engine=ThreadedEngineService())
61 FrontEndBase.__init__(self, engine=ThreadedEngineService())
61 if(self != None):
62 if(self != None):
62 self._common_init()
63 self._common_init()
63
64
64 return self
65 return self
65
66
66 def _common_init(self):
67 def _common_init(self):
67 """_common_init"""
68 """_common_init"""
68
69
69 self.userNS = NSMutableDictionary.dictionary()
70 self.userNS = NSMutableDictionary.dictionary()
70 self.waitingForEngine = False
71 self.waitingForEngine = False
71
72
72 self.lines = {}
73 self.lines = {}
73 self.tabSpaces = 4
74 self.tabSpaces = 4
74 self.tabUsesSpaces = True
75 self.tabUsesSpaces = True
75 self.currentBlockID = self.nextBlockID()
76 self.currentBlockID = self.nextBlockID()
76 self.blockRanges = {} # blockID=>NSRange
77 self.blockRanges = {} # blockID=>NSRange
77
78
78
79
79 def awakeFromNib(self):
80 def awakeFromNib(self):
80 """awakeFromNib"""
81 """awakeFromNib"""
81
82
82 self._common_init()
83 self._common_init()
83
84
84 # Start the IPython engine
85 # Start the IPython engine
85 self.engine.startService()
86 self.engine.startService()
86 NSLog('IPython engine started')
87 NSLog('IPython engine started')
87
88
88 # Register for app termination
89 # Register for app termination
89 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self,
90 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self,
90 'appWillTerminate:',
91 'appWillTerminate:',
91 NSApplicationWillTerminateNotification,
92 NSApplicationWillTerminateNotification,
92 None)
93 None)
93
94
94 self.textView.setDelegate_(self)
95 self.textView.setDelegate_(self)
95 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
96 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
96 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
97 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
97 self.textView.enclosingScrollView(),
98 self.textView.enclosingScrollView(),
98 NSVerticalRuler)
99 NSVerticalRuler)
99 self.verticalRulerView.setClientView_(self.textView)
100 self.verticalRulerView.setClientView_(self.textView)
100 self.startCLIForTextView()
101 self.startCLIForTextView()
101
102
102
103
103 def appWillTerminate_(self, notification):
104 def appWillTerminate_(self, notification):
104 """appWillTerminate"""
105 """appWillTerminate"""
105
106
106 self.engine.stopService()
107 self.engine.stopService()
107
108
108
109
109 def complete(self, token):
110 def complete(self, token):
110 """Complete token in engine's user_ns
111 """Complete token in engine's user_ns
111
112
112 Parameters
113 Parameters
113 ----------
114 ----------
114 token : string
115 token : string
115
116
116 Result
117 Result
117 ------
118 ------
118 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
119 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
119 """
120 """
120
121
121 return self.engine.complete(token)
122 return self.engine.complete(token)
122
123
123
124
124 def execute(self, block, blockID=None):
125 def execute(self, block, blockID=None):
125 self.waitingForEngine = True
126 self.waitingForEngine = True
126 self.willChangeValueForKey_('commandHistory')
127 self.willChangeValueForKey_('commandHistory')
127 d = super(IPythonCocoaController, self).execute(block, blockID)
128 d = super(IPythonCocoaController, self).execute(block, blockID)
128 d.addBoth(self._engineDone)
129 d.addBoth(self._engineDone)
129 d.addCallback(self._updateUserNS)
130 d.addCallback(self._updateUserNS)
130
131
131 return d
132 return d
132
133
133
134
134 def _engineDone(self, x):
135 def _engineDone(self, x):
135 self.waitingForEngine = False
136 self.waitingForEngine = False
136 self.didChangeValueForKey_('commandHistory')
137 self.didChangeValueForKey_('commandHistory')
137 return x
138 return x
138
139
139 def _updateUserNS(self, result):
140 def _updateUserNS(self, result):
140 """Update self.userNS from self.engine's namespace"""
141 """Update self.userNS from self.engine's namespace"""
141 d = self.engine.keys()
142 d = self.engine.keys()
142 d.addCallback(self._getEngineNamepsaceValuesForKeys)
143 d.addCallback(self._getEngineNamepsaceValuesForKeys)
143
144
144 return result
145 return result
145
146
146
147
147 def _getEngineNamepsaceValuesForKeys(self, keys):
148 def _getEngineNamepsaceValuesForKeys(self, keys):
148 d = self.engine.pull(keys)
149 d = self.engine.pull(keys)
149 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
150 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
150
151
151
152
152 def _storeEngineNamespaceValues(self, values, keys=[]):
153 def _storeEngineNamespaceValues(self, values, keys=[]):
153 assert(len(values) == len(keys))
154 assert(len(values) == len(keys))
154 self.willChangeValueForKey_('userNS')
155 self.willChangeValueForKey_('userNS')
155 for (k,v) in zip(keys,values):
156 for (k,v) in zip(keys,values):
156 self.userNS[k] = saferepr(v)
157 self.userNS[k] = saferepr(v)
157 self.didChangeValueForKey_('userNS')
158 self.didChangeValueForKey_('userNS')
158
159
159
160
160 def startCLIForTextView(self):
161 def startCLIForTextView(self):
161 """Print banner"""
162 """Print banner"""
162
163
163 banner = """IPython1 0.X -- An enhanced Interactive Python."""
164 banner = """IPython1 %s -- An enhanced Interactive Python.""" % IPython.__version__
164
165
165 self.insert_text(banner + '\n\n')
166 self.insert_text(banner + '\n\n')
166
167
167 # NSTextView/IPythonTextView delegate methods
168 # NSTextView/IPythonTextView delegate methods
168 def textView_doCommandBySelector_(self, textView, selector):
169 def textView_doCommandBySelector_(self, textView, selector):
169 assert(textView == self.textView)
170 assert(textView == self.textView)
170 NSLog("textView_doCommandBySelector_: "+selector)
171 NSLog("textView_doCommandBySelector_: "+selector)
171
172
172
173
173 if(selector == 'insertNewline:'):
174 if(selector == 'insertNewline:'):
174 indent = self.currentIndentString()
175 indent = self.currentIndentString()
175 if(indent):
176 if(indent):
176 line = indent + self.currentLine()
177 line = indent + self.currentLine()
177 else:
178 else:
178 line = self.currentLine()
179 line = self.currentLine()
179
180
180 if(self.is_complete(self.currentBlock())):
181 if(self.is_complete(self.currentBlock())):
181 self.execute(self.currentBlock(),
182 self.execute(self.currentBlock(),
182 blockID=self.currentBlockID)
183 blockID=self.currentBlockID)
183 self.currentBlockID = self.nextBlockID()
184 self.currentBlockID = self.nextBlockID()
184 return True
185 return True
185
186
186 return False
187 return False
187
188
188 elif(selector == 'moveUp:'):
189 elif(selector == 'moveUp:'):
189 self.replaceCurrentBlockWithString(textView, self.get_history_item_previous(self.currentBlock()))
190 self.replaceCurrentBlockWithString(textView, self.get_history_item_previous(self.currentBlock()))
190 return True
191 return True
191
192
192 elif(selector == 'moveDown:'):
193 elif(selector == 'moveDown:'):
193 self.replaceCurrentBlockWithString(textView, self.get_history_item_next(self.currentBlock()))
194 self.replaceCurrentBlockWithString(textView, self.get_history_item_next(self.currentBlock()))
194 return True
195 return True
195
196
196 elif(selector == 'moveToBeginningOfParagraph:'):
197 elif(selector == 'moveToBeginningOfParagraph:'):
197 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location, 0))
198 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location, 0))
198 return True
199 return True
199 elif(selector == 'moveToEndOfParagraph:'):
200 elif(selector == 'moveToEndOfParagraph:'):
200 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location + \
201 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location + \
201 self.currentBlockRange().length, 0))
202 self.currentBlockRange().length, 0))
202 return True
203 return True
203 elif(selector == 'deleteToEndOfParagraph:'):
204 elif(selector == 'deleteToEndOfParagraph:'):
204 if(textView.selectedRange().location <= self.currentBlockRange().location):
205 if(textView.selectedRange().location <= self.currentBlockRange().location):
205 # Intersect the selected range with the current line range
206 # Intersect the selected range with the current line range
206 if(self.currentBlockRange().length < 0):
207 if(self.currentBlockRange().length < 0):
207 self.blockRanges[self.currentBlockID].length = 0
208 self.blockRanges[self.currentBlockID].length = 0
208
209
209 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
210 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
210 self.currentBlockRange())
211 self.currentBlockRange())
211
212
212 if(r.length > 0): #no intersection
213 if(r.length > 0): #no intersection
213 textView.setSelectedRange_(r)
214 textView.setSelectedRange_(r)
214
215
215 return False # don't actually handle the delete
216 return False # don't actually handle the delete
216
217
217 elif(selector == 'insertTab:'):
218 elif(selector == 'insertTab:'):
218 if(len(self.currentLine().strip()) == 0): #only white space
219 if(len(self.currentLine().strip()) == 0): #only white space
219 return False
220 return False
220 else:
221 else:
221 self.textView.complete_(self)
222 self.textView.complete_(self)
222 return True
223 return True
223
224
224 elif(selector == 'deleteBackward:'):
225 elif(selector == 'deleteBackward:'):
225 #if we're at the beginning of the current block, ignore
226 #if we're at the beginning of the current block, ignore
226 if(textView.selectedRange().location == self.currentBlockRange().location):
227 if(textView.selectedRange().location == self.currentBlockRange().location):
227 return True
228 return True
228 else:
229 else:
229 self.currentBlockRange().length-=1
230 self.currentBlockRange().length-=1
230 return False
231 return False
231 return False
232 return False
232
233
233
234
234 def textView_shouldChangeTextInRanges_replacementStrings_(self, textView, ranges, replacementStrings):
235 def textView_shouldChangeTextInRanges_replacementStrings_(self, textView, ranges, replacementStrings):
235 """
236 """
236 Delegate method for NSTextView.
237 Delegate method for NSTextView.
237
238
238 Refuse change text in ranges not at end, but make those changes at end.
239 Refuse change text in ranges not at end, but make those changes at end.
239 """
240 """
240
241
241 #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings
242 #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings
242 assert(len(ranges) == len(replacementStrings))
243 assert(len(ranges) == len(replacementStrings))
243 allow = True
244 allow = True
244 for r,s in zip(ranges, replacementStrings):
245 for r,s in zip(ranges, replacementStrings):
245 r = r.rangeValue()
246 r = r.rangeValue()
246 if(textView.textStorage().length() > 0 and
247 if(textView.textStorage().length() > 0 and
247 r.location < self.currentBlockRange().location):
248 r.location < self.currentBlockRange().location):
248 self.insert_text(s)
249 self.insert_text(s)
249 allow = False
250 allow = False
250
251
251
252
252 self.blockRanges.setdefault(self.currentBlockID, self.currentBlockRange()).length += len(s)
253 self.blockRanges.setdefault(self.currentBlockID, self.currentBlockRange()).length += len(s)
253
254
254 return allow
255 return allow
255
256
256 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, textView, words, charRange, index):
257 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, textView, words, charRange, index):
257 try:
258 try:
258 token = textView.textStorage().string().substringWithRange_(charRange)
259 token = textView.textStorage().string().substringWithRange_(charRange)
259 completions = blockingCallFromThread(self.complete, token)
260 completions = blockingCallFromThread(self.complete, token)
260 except:
261 except:
261 completions = objc.nil
262 completions = objc.nil
262 NSBeep()
263 NSBeep()
263
264
264 return (completions,0)
265 return (completions,0)
265
266
266
267
267 def nextBlockID(self):
268 def nextBlockID(self):
268
269
269 return uuid.uuid4()
270 return uuid.uuid4()
270
271
271 def currentBlockRange(self):
272 def currentBlockRange(self):
272 return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0))
273 return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0))
273
274
274 def currentBlock(self):
275 def currentBlock(self):
275 """The current block's text"""
276 """The current block's text"""
276
277
277 return self.textForRange(self.currentBlockRange())
278 return self.textForRange(self.currentBlockRange())
278
279
279 def textForRange(self, textRange):
280 def textForRange(self, textRange):
280 """textForRange"""
281 """textForRange"""
281
282
282 return self.textView.textStorage().string().substringWithRange_(textRange)
283 return self.textView.textStorage().string().substringWithRange_(textRange)
283
284
284 def currentLine(self):
285 def currentLine(self):
285 block = self.textForRange(self.currentBlockRange())
286 block = self.textForRange(self.currentBlockRange())
286 block = block.split('\n')
287 block = block.split('\n')
287 return block[-1]
288 return block[-1]
288
289
289 def update_cell_prompt(self, result):
290 def update_cell_prompt(self, result):
290 blockID = result['blockID']
291 blockID = result['blockID']
291 self.insert_text(self.inputPrompt(result=result),
292 self.insert_text(self.inputPrompt(result=result),
292 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
293 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
293 scrollToVisible=False
294 scrollToVisible=False
294 )
295 )
295
296
296 return result
297 return result
297
298
298
299
299 def render_result(self, result):
300 def render_result(self, result):
300 blockID = result['blockID']
301 blockID = result['blockID']
301 inputRange = self.blockRanges[blockID]
302 inputRange = self.blockRanges[blockID]
302 del self.blockRanges[blockID]
303 del self.blockRanges[blockID]
303
304
304 #print inputRange,self.currentBlockRange()
305 #print inputRange,self.currentBlockRange()
305 self.insert_text('\n' +
306 self.insert_text('\n' +
306 self.outputPrompt(result) +
307 self.outputPrompt(result) +
307 result.get('display',{}).get('pprint','') +
308 result.get('display',{}).get('pprint','') +
308 '\n\n',
309 '\n\n',
309 textRange=NSMakeRange(inputRange.location+inputRange.length, 0))
310 textRange=NSMakeRange(inputRange.location+inputRange.length, 0))
310 return result
311 return result
311
312
312
313
313 def render_error(self, failure):
314 def render_error(self, failure):
314 self.insert_text(str(failure))
315 self.insert_text(str(failure))
315 return failure
316 return failure
316
317
317
318
318 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
319 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
319 """Insert text into textView at textRange, updating blockRanges as necessary"""
320 """Insert text into textView at textRange, updating blockRanges as necessary"""
320
321
321 if(textRange == None):
322 if(textRange == None):
322 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
323 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
323
324
324 for r in self.blockRanges.itervalues():
325 for r in self.blockRanges.itervalues():
325 intersection = NSIntersectionRange(r,textRange)
326 intersection = NSIntersectionRange(r,textRange)
326 if(intersection.length == 0): #ranges don't intersect
327 if(intersection.length == 0): #ranges don't intersect
327 if r.location >= textRange.location:
328 if r.location >= textRange.location:
328 r.location += len(string)
329 r.location += len(string)
329 else: #ranges intersect
330 else: #ranges intersect
330 if(r.location <= textRange.location):
331 if(r.location <= textRange.location):
331 assert(intersection.length == textRange.length)
332 assert(intersection.length == textRange.length)
332 r.length += textRange.length
333 r.length += textRange.length
333 else:
334 else:
334 r.location += intersection.length
335 r.location += intersection.length
335
336
336 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
337 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
337 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
338 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
338 if(scrollToVisible):
339 if(scrollToVisible):
339 self.textView.scrollRangeToVisible_(textRange)
340 self.textView.scrollRangeToVisible_(textRange)
340
341
341
342
342
343
343 def replaceCurrentBlockWithString(self, textView, string):
344 def replaceCurrentBlockWithString(self, textView, string):
344 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
345 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
345 string)
346 string)
346 r = NSMakeRange(textView.textStorage().length(), 0)
347 r = NSMakeRange(textView.textStorage().length(), 0)
347 textView.scrollRangeToVisible_(r)
348 textView.scrollRangeToVisible_(r)
348 textView.setSelectedRange_(r)
349 textView.setSelectedRange_(r)
349
350
350
351
351 def currentIndentString(self):
352 def currentIndentString(self):
352 """returns string for indent or None if no indent"""
353 """returns string for indent or None if no indent"""
353
354
354 if(len(self.currentBlock()) > 0):
355 if(len(self.currentBlock()) > 0):
355 lines = self.currentBlock().split('\n')
356 lines = self.currentBlock().split('\n')
356 currentIndent = len(lines[-1]) - len(lines[-1])
357 currentIndent = len(lines[-1]) - len(lines[-1])
357 if(currentIndent == 0):
358 if(currentIndent == 0):
358 currentIndent = self.tabSpaces
359 currentIndent = self.tabSpaces
359
360
360 if(self.tabUsesSpaces):
361 if(self.tabUsesSpaces):
361 result = ' ' * currentIndent
362 result = ' ' * currentIndent
362 else:
363 else:
363 result = '\t' * (currentIndent/self.tabSpaces)
364 result = '\t' * (currentIndent/self.tabSpaces)
364 else:
365 else:
365 result = None
366 result = None
366
367
367 return result
368 return result
368
369
369
370
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,295 +1,297 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 frontendbase provides an interface and base class for GUI frontends for IPython.kernel/IPython.kernel.core.
3 frontendbase provides an interface and base class for GUI frontends for IPython.kernel/IPython.kernel.core.
4
4
5 Frontend implementations will likely want to subclass FrontEndBase.
5 Frontend implementations will likely want to subclass FrontEndBase.
6
6
7 Author: Barry Wark
7 Author: Barry Wark
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 in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
16 #-------------------------------------------------------------------------------
17
17
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 import string
21 import string
22 import uuid
22 import uuid
23
23
24
24
25 from IPython.kernel.core.history import FrontEndHistory
25 from IPython.kernel.core.history import FrontEndHistory
26 from IPython.kernel.core.util import Bunch
26 from IPython.kernel.core.util import Bunch
27
27
28 from IPython.kernel.engineservice import IEngineCore
28 from IPython.kernel.engineservice import IEngineCore
29
29
30 import zope.interface as zi
30 import zope.interface as zi
31
31
32 import _ast
32 import _ast
33
33
34 ##############################################################################
34 ##############################################################################
35 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
35 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
36 # not
36 # not
37
37
38 rc = Bunch()
38 rc = Bunch()
39 rc.prompt_in1 = r'In [$number]: '
39 rc.prompt_in1 = r'In [$number]: '
40 rc.prompt_in2 = r'...'
40 rc.prompt_in2 = r'...'
41 rc.prompt_out = r'Out [$number]: '
41 rc.prompt_out = r'Out [$number]: '
42
42
43 ##############################################################################
43 ##############################################################################
44
44
45 class IFrontEndFactory(zi.Interface):
45 class IFrontEndFactory(zi.Interface):
46 """Factory interface for frontends."""
46 """Factory interface for frontends."""
47
47
48 def __call__(engine=None, history=None):
48 def __call__(engine=None, history=None):
49 """
49 """
50 Parameters:
50 Parameters:
51 interpreter : IPython.kernel.engineservice.IEngineCore
51 interpreter : IPython.kernel.engineservice.IEngineCore
52 """
52 """
53
53
54 pass
54 pass
55
55
56
56
57
57
58 class IFrontEnd(zi.Interface):
58 class IFrontEnd(zi.Interface):
59 """Interface for frontends. All methods return t.i.d.Deferred"""
59 """Interface for frontends. All methods return t.i.d.Deferred"""
60
60
61 zi.Attribute("input_prompt_template", "string.Template instance substituteable with execute result.")
61 zi.Attribute("input_prompt_template", "string.Template instance substituteable with execute result.")
62 zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.")
62 zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.")
63 zi.Attribute("continuation_prompt_template", "string.Template instance substituteable with execute result.")
63 zi.Attribute("continuation_prompt_template", "string.Template instance substituteable with execute result.")
64
64
65 def update_cell_prompt(self, result):
65 def update_cell_prompt(self, result):
66 """Subclass may override to update the input prompt for a block.
66 """Subclass may override to update the input prompt for a block.
67 Since this method will be called as a twisted.internet.defer.Deferred's callback,
67 Since this method will be called as a twisted.internet.defer.Deferred's callback,
68 implementations should return result when finished."""
68 implementations should return result when finished."""
69
69
70 pass
70 pass
71
71
72 def render_result(self, result):
72 def render_result(self, result):
73 """Render the result of an execute call. Implementors may choose the method of rendering.
73 """Render the result of an execute call. Implementors may choose the method of rendering.
74 For example, a notebook-style frontend might render a Chaco plot inline.
74 For example, a notebook-style frontend might render a Chaco plot inline.
75
75
76 Parameters:
76 Parameters:
77 result : dict (result of IEngineBase.execute )
77 result : dict (result of IEngineBase.execute )
78
78
79 Result:
79 Result:
80 Output of frontend rendering
80 Output of frontend rendering
81 """
81 """
82
82
83 pass
83 pass
84
84
85 def render_error(self, failure):
85 def render_error(self, failure):
86 """Subclasses must override to render the failure. Since this method will be called as a
86 """Subclasses must override to render the failure. Since this method will be called as a
87 twisted.internet.defer.Deferred's callback, implementations should return result
87 twisted.internet.defer.Deferred's callback, implementations should return result
88 when finished."""
88 when finished."""
89
89
90 pass
90 pass
91
91
92
92
93 def inputPrompt(result={}):
93 def inputPrompt(result={}):
94 """Returns the input prompt by subsituting into self.input_prompt_template"""
94 """Returns the input prompt by subsituting into self.input_prompt_template"""
95 pass
95 pass
96
96
97 def outputPrompt(result):
97 def outputPrompt(result):
98 """Returns the output prompt by subsituting into self.output_prompt_template"""
98 """Returns the output prompt by subsituting into self.output_prompt_template"""
99
99
100 pass
100 pass
101
101
102 def continuationPrompt():
102 def continuationPrompt():
103 """Returns the continuation prompt by subsituting into self.continuation_prompt_template"""
103 """Returns the continuation prompt by subsituting into self.continuation_prompt_template"""
104
104
105 pass
105 pass
106
106
107 def is_complete(block):
107 def is_complete(block):
108 """Returns True if block is complete, False otherwise."""
108 """Returns True if block is complete, False otherwise."""
109
109
110 pass
110 pass
111
111
112 def compile_ast(block):
112 def compile_ast(block):
113 """Compiles block to an _ast.AST"""
113 """Compiles block to an _ast.AST"""
114
114
115 pass
115 pass
116
116
117
117
118 def get_history_item_previous(currentBlock):
118 def get_history_item_previous(currentBlock):
119 """Returns the block previous in the history."""
119 """Returns the block previous in the history."""
120 pass
120 pass
121
121
122 def get_history_item_next(currentBlock):
122 def get_history_item_next(currentBlock):
123 """Returns the next block in the history."""
123 """Returns the next block in the history."""
124
124
125 pass
125 pass
126
126
127
127
128 class FrontEndBase(object):
128 class FrontEndBase(object):
129 """
129 """
130 FrontEndBase manages the state tasks for a CLI frontend:
130 FrontEndBase manages the state tasks for a CLI frontend:
131 - Input and output history management
131 - Input and output history management
132 - Input/continuation and output prompt generation
132 - Input/continuation and output prompt generation
133
133
134 Some issues (due to possibly unavailable engine):
134 Some issues (due to possibly unavailable engine):
135 - How do we get the current cell number for the engine?
135 - How do we get the current cell number for the engine?
136 - How do we handle completions?
136 - How do we handle completions?
137 """
137 """
138
138
139 zi.implements(IFrontEnd)
139 zi.implements(IFrontEnd)
140 zi.classProvides(IFrontEndFactory)
140 zi.classProvides(IFrontEndFactory)
141
141
142 history_cursor = 0
142 history_cursor = 0
143
143
144 current_indent_level = 0
144 current_indent_level = 0
145
145
146
146
147 input_prompt_template = string.Template(rc.prompt_in1)
147 input_prompt_template = string.Template(rc.prompt_in1)
148 output_prompt_template = string.Template(rc.prompt_out)
148 output_prompt_template = string.Template(rc.prompt_out)
149 continuation_prompt_template = string.Template(rc.prompt_in2)
149 continuation_prompt_template = string.Template(rc.prompt_in2)
150
150
151 def __init__(self, engine=None, history=None):
151 def __init__(self, engine=None, history=None):
152 assert(engine==None or IEngineCore.providedBy(engine))
152 assert(engine==None or IEngineCore.providedBy(engine))
153 self.engine = IEngineCore(engine)
153 self.engine = IEngineCore(engine)
154 if history is None:
154 if history is None:
155 self.history = FrontEndHistory(input_cache=[''])
155 self.history = FrontEndHistory(input_cache=[''])
156 else:
156 else:
157 self.history = history
157 self.history = history
158
158
159
159
160 def inputPrompt(self, result={}):
160 def inputPrompt(self, result={}):
161 """Returns the current input prompt
161 """Returns the current input prompt
162
162
163 It would be great to use ipython1.core.prompts.Prompt1 here
163 It would be great to use ipython1.core.prompts.Prompt1 here
164 """
164 """
165
165
166 result.setdefault('number','')
166 result.setdefault('number','')
167
167
168 return self.input_prompt_template.safe_substitute(result)
168 return self.input_prompt_template.safe_substitute(result)
169
169
170
170
171 def continuationPrompt(self):
171 def continuationPrompt(self):
172 """Returns the current continuation prompt"""
172 """Returns the current continuation prompt"""
173
173
174 return self.continuation_prompt_template.safe_substitute()
174 return self.continuation_prompt_template.safe_substitute()
175
175
176 def outputPrompt(self, result):
176 def outputPrompt(self, result):
177 """Returns the output prompt for result"""
177 """Returns the output prompt for result"""
178
178
179 return self.output_prompt_template.safe_substitute(result)
179 return self.output_prompt_template.safe_substitute(result)
180
180
181
181
182 def is_complete(self, block):
182 def is_complete(self, block):
183 """Determine if block is complete.
183 """Determine if block is complete.
184
184
185 Parameters
185 Parameters
186 block : string
186 block : string
187
187
188 Result
188 Result
189 True if block can be sent to the engine without compile errors.
189 True if block can be sent to the engine without compile errors.
190 False otherwise.
190 False otherwise.
191 """
191 """
192
192
193 try:
193 try:
194 self.compile_ast(block)
194 ast = self.compile_ast(block)
195 return True
196 except:
195 except:
197 return False
196 return False
198
197
198 lines = block.split('\n')
199 return (len(lines)==1 or str(lines[-1])=='')
200
199
201
200 def compile_ast(self, block):
202 def compile_ast(self, block):
201 """Compile block to an AST
203 """Compile block to an AST
202
204
203 Parameters:
205 Parameters:
204 block : str
206 block : str
205
207
206 Result:
208 Result:
207 AST
209 AST
208
210
209 Throws:
211 Throws:
210 Exception if block cannot be compiled
212 Exception if block cannot be compiled
211 """
213 """
212
214
213 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
215 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
214
216
215
217
216 def execute(self, block, blockID=None):
218 def execute(self, block, blockID=None):
217 """Execute the block and return result.
219 """Execute the block and return result.
218
220
219 Parameters:
221 Parameters:
220 block : {str, AST}
222 block : {str, AST}
221 blockID : any
223 blockID : any
222 Caller may provide an ID to identify this block. result['blockID'] := blockID
224 Caller may provide an ID to identify this block. result['blockID'] := blockID
223
225
224 Result:
226 Result:
225 Deferred result of self.interpreter.execute
227 Deferred result of self.interpreter.execute
226 """
228 """
227 # if(not isinstance(block, _ast.AST)):
229 # if(not isinstance(block, _ast.AST)):
228 # block = self.compile_ast(block)
230 # block = self.compile_ast(block)
229
231
230 if(blockID == None):
232 if(blockID == None):
231 blockID = uuid.uuid4() #random UUID
233 blockID = uuid.uuid4() #random UUID
232
234
233 d = self.engine.execute(block)
235 d = self.engine.execute(block)
234 d.addCallback(self._add_block_id, blockID)
236 d.addCallback(self._add_block_id, blockID)
235 d.addCallback(self.update_cell_prompt)
237 d.addCallback(self.update_cell_prompt)
236 d.addCallbacks(self.render_result, errback=self.render_error)
238 d.addCallbacks(self.render_result, errback=self.render_error)
237
239
238 return d
240 return d
239
241
240 def _add_block_id(self, result, blockID):
242 def _add_block_id(self, result, blockID):
241 """add_block_id"""
243 """add_block_id"""
242
244
243 result['blockID'] = blockID
245 result['blockID'] = blockID
244
246
245 return result
247 return result
246
248
247
249
248 def get_history_item_previous(self, currentBlock):
250 def get_history_item_previous(self, currentBlock):
249 """ Returns previous history string and decrement history cursor.
251 """ Returns previous history string and decrement history cursor.
250 """
252 """
251 command = self.history.get_history_item(self.history_cursor - 1)
253 command = self.history.get_history_item(self.history_cursor - 1)
252 if command is not None:
254 if command is not None:
253 self.history.input_cache[self.history_cursor] = currentBlock
255 self.history.input_cache[self.history_cursor] = currentBlock
254 self.history_cursor -= 1
256 self.history_cursor -= 1
255 return command
257 return command
256
258
257
259
258 def get_history_item_next(self, currentBlock):
260 def get_history_item_next(self, currentBlock):
259 """ Returns next history string and increment history cursor.
261 """ Returns next history string and increment history cursor.
260 """
262 """
261 command = self.history.get_history_item(self.history_cursor + 1)
263 command = self.history.get_history_item(self.history_cursor + 1)
262 if command is not None:
264 if command is not None:
263 self.history.input_cache[self.history_cursor] = currentBlock
265 self.history.input_cache[self.history_cursor] = currentBlock
264 self.history_cursor += 1
266 self.history_cursor += 1
265 return command
267 return command
266
268
267 ###
269 ###
268 # Subclasses probably want to override these methods...
270 # Subclasses probably want to override these methods...
269 ###
271 ###
270
272
271 def update_cell_prompt(self, result):
273 def update_cell_prompt(self, result):
272 """Subclass may override to update the input prompt for a block.
274 """Subclass may override to update the input prompt for a block.
273 Since this method will be called as a twisted.internet.defer.Deferred's callback,
275 Since this method will be called as a twisted.internet.defer.Deferred's callback,
274 implementations should return result when finished."""
276 implementations should return result when finished."""
275
277
276 return result
278 return result
277
279
278
280
279 def render_result(self, result):
281 def render_result(self, result):
280 """Subclasses must override to render result. Since this method will be called as a
282 """Subclasses must override to render result. Since this method will be called as a
281 twisted.internet.defer.Deferred's callback, implementations should return result
283 twisted.internet.defer.Deferred's callback, implementations should return result
282 when finished."""
284 when finished."""
283
285
284 return result
286 return result
285
287
286
288
287 def render_error(self, failure):
289 def render_error(self, failure):
288 """Subclasses must override to render the failure. Since this method will be called as a
290 """Subclasses must override to render the failure. Since this method will be called as a
289 twisted.internet.defer.Deferred's callback, implementations should return result
291 twisted.internet.defer.Deferred's callback, implementations should return result
290 when finished."""
292 when finished."""
291
293
292 return failure
294 return failure
293
295
294
296
295
297
General Comments 0
You need to be logged in to leave comments. Login now