##// END OF EJS Templates
Delete all references to old IPython.external.guid...
Thomas Spura -
Show More
@@ -1,82 +1,82
1 1 """
2 2 Base front end class for all async frontends.
3 3 """
4 4 __docformat__ = "restructuredtext en"
5 5
6 6 # Tell nose to skip this module
7 7 __test__ = {}
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19
20 20 # Third-party
21 21 from twisted.python.failure import Failure
22 22 from zope.interface import implements, classProvides
23 23
24 24 # From IPython
25 from IPython.external import guid
26
27 25 from IPython.frontend.frontendbase import (FrontEndBase, IFrontEnd,
28 26 IFrontEndFactory)
29 27 from IPython.kernel.core.history import FrontEndHistory
30 28 from IPython.kernel.engineservice import IEngineCore
31 29
30 import uuid
31
32 32 #-----------------------------------------------------------------------------
33 33 # Classes and functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class AsyncFrontEndBase(FrontEndBase):
37 37 """
38 38 Overrides FrontEndBase to wrap execute in a deferred result.
39 39 All callbacks are made as callbacks on the deferred result.
40 40 """
41 41
42 42 implements(IFrontEnd)
43 43 classProvides(IFrontEndFactory)
44 44
45 45 def __init__(self, engine=None, history=None):
46 46 assert(engine==None or IEngineCore.providedBy(engine))
47 47 self.engine = IEngineCore(engine)
48 48 if history is None:
49 49 self.history = FrontEndHistory(input_cache=[''])
50 50 else:
51 51 self.history = history
52 52
53 53 def execute(self, block, blockID=None):
54 54 """Execute the block and return the deferred result.
55 55
56 56 Parameters:
57 57 block : {str, AST}
58 58 blockID : any
59 59 Caller may provide an ID to identify this block.
60 60 result['blockID'] := blockID
61 61
62 62 Result:
63 63 Deferred result of self.interpreter.execute
64 64 """
65 65
66 66 if(not self.is_complete(block)):
67 67 return Failure(Exception("Block is not compilable"))
68 68
69 69 if(blockID == None):
70 blockID = guid.generate()
70 blockID = uuid.uuid4()
71 71
72 72 d = self.engine.execute(block)
73 73 d.addCallback(self._add_history, block=block)
74 74 d.addCallbacks(self._add_block_id_for_result,
75 75 errback=self._add_block_id_for_failure,
76 76 callbackArgs=(blockID,),
77 77 errbackArgs=(blockID,))
78 78 d.addBoth(self.update_cell_prompt, blockID=blockID)
79 79 d.addCallbacks(self.render_result,
80 80 errback=self.render_error)
81 81
82 82 return d
@@ -1,560 +1,560
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3 3
4 4 """PyObjC classes to provide a Cocoa frontend to the
5 5 IPython.kernel.engineservice.IEngineBase.
6 6
7 7 To add an IPython interpreter to a cocoa app, instantiate an
8 8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 9 NSTextView instance in your UI. That's it.
10 10
11 11 Author: Barry Wark
12 12 """
13 13
14 14 __docformat__ = "restructuredtext en"
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008-2011 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 import sys
28 28 import objc
29 from IPython.external import guid
29 import uuid
30 30
31 31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 32 NSLog, NSNotificationCenter, NSMakeRange,\
33 33 NSLocalizedString, NSIntersectionRange,\
34 34 NSString, NSAutoreleasePool
35 35
36 36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 37 NSTextView, NSRulerView, NSVerticalRuler
38 38
39 39 from pprint import saferepr
40 40
41 41 import IPython
42 42 from IPython.kernel.engineservice import ThreadedEngineService
43 43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44 44
45 45 from twisted.internet.threads import blockingCallFromThread
46 46 from twisted.python.failure import Failure
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Classes to implement the Cocoa frontend
50 50 #-----------------------------------------------------------------------------
51 51
52 52 # TODO:
53 53 # 1. use MultiEngineClient and out-of-process engine rather than
54 54 # ThreadedEngineService?
55 55 # 2. integrate Xgrid launching of engines
56 56
57 57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 58 """Wrap all blocks in an NSAutoreleasePool"""
59 59
60 60 def wrapped_execute(self, msg, lines):
61 61 """wrapped_execute"""
62 62 try:
63 63 p = NSAutoreleasePool.alloc().init()
64 64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 65 self).wrapped_execute(msg, lines)
66 66 finally:
67 67 p.drain()
68 68
69 69 return result
70 70
71 71
72 72
73 73 class Cell(NSObject):
74 74 """
75 75 Representation of the prompts, input and output of a cell in the
76 76 frontend
77 77 """
78 78
79 79 blockNumber = objc.ivar().unsigned_long()
80 80 blockID = objc.ivar()
81 81 inputBlock = objc.ivar()
82 82 output = objc.ivar()
83 83
84 84
85 85
86 86 class CellBlock(object):
87 87 """
88 88 Storage for information about text ranges relating to a single cell
89 89 """
90 90
91 91
92 92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
93 93 outputRange=None):
94 94 super(CellBlock, self).__init__()
95 95 self.inputPromptRange = inputPromptRange
96 96 self.inputRange = inputRange
97 97 self.outputPromptRange = outputPromptRange
98 98 self.outputRange = outputRange
99 99
100 100 def update_ranges_for_insertion(self, text, textRange):
101 101 """Update ranges for text insertion at textRange"""
102 102
103 103 for r in [self.inputPromptRange,self.inputRange,
104 104 self.outputPromptRange, self.outputRange]:
105 105 if(r == None):
106 106 continue
107 107 intersection = NSIntersectionRange(r,textRange)
108 108 if(intersection.length == 0): #ranges don't intersect
109 109 if r.location >= textRange.location:
110 110 r.location += len(text)
111 111 else: #ranges intersect
112 112 if(r.location > textRange.location):
113 113 offset = len(text) - intersection.length
114 114 r.length -= offset
115 115 r.location += offset
116 116 elif(r.location == textRange.location):
117 117 r.length += len(text) - intersection.length
118 118 else:
119 119 r.length -= intersection.length
120 120
121 121
122 122 def update_ranges_for_deletion(self, textRange):
123 123 """Update ranges for text deletion at textRange"""
124 124
125 125 for r in [self.inputPromptRange,self.inputRange,
126 126 self.outputPromptRange, self.outputRange]:
127 127 if(r==None):
128 128 continue
129 129 intersection = NSIntersectionRange(r, textRange)
130 130 if(intersection.length == 0): #ranges don't intersect
131 131 if r.location >= textRange.location:
132 132 r.location -= textRange.length
133 133 else: #ranges intersect
134 134 if(r.location > textRange.location):
135 135 offset = intersection.length
136 136 r.length -= offset
137 137 r.location += offset
138 138 elif(r.location == textRange.location):
139 139 r.length += intersection.length
140 140 else:
141 141 r.length -= intersection.length
142 142
143 143 def __repr__(self):
144 144 return 'CellBlock('+ str((self.inputPromptRange,
145 145 self.inputRange,
146 146 self.outputPromptRange,
147 147 self.outputRange)) + ')'
148 148
149 149
150 150
151 151
152 152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
153 153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
154 154 waitingForEngine = objc.ivar().bool()
155 155 textView = objc.IBOutlet()
156 156
157 157 def init(self):
158 158 self = super(IPythonCocoaController, self).init()
159 159 AsyncFrontEndBase.__init__(self,
160 160 engine=AutoreleasePoolWrappedThreadedEngineService())
161 161 if(self != None):
162 162 self._common_init()
163 163
164 164 return self
165 165
166 166 def _common_init(self):
167 167 """_common_init"""
168 168
169 169 self.userNS = NSMutableDictionary.dictionary()
170 170 self.waitingForEngine = False
171 171
172 172 self.lines = {}
173 173 self.tabSpaces = 4
174 174 self.tabUsesSpaces = True
175 175 self.currentBlockID = self.next_block_ID()
176 176 self.blockRanges = {} # blockID=>CellBlock
177 177
178 178
179 179 def awakeFromNib(self):
180 180 """awakeFromNib"""
181 181
182 182 self._common_init()
183 183
184 184 # Start the IPython engine
185 185 self.engine.startService()
186 186 NSLog('IPython engine started')
187 187
188 188 # Register for app termination
189 189 nc = NSNotificationCenter.defaultCenter()
190 190 nc.addObserver_selector_name_object_(
191 191 self,
192 192 'appWillTerminate:',
193 193 NSApplicationWillTerminateNotification,
194 194 None)
195 195
196 196 self.textView.setDelegate_(self)
197 197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
198 198 r = NSRulerView.alloc().initWithScrollView_orientation_(
199 199 self.textView.enclosingScrollView(),
200 200 NSVerticalRuler)
201 201 self.verticalRulerView = r
202 202 self.verticalRulerView.setClientView_(self.textView)
203 203 self._start_cli_banner()
204 204 self.start_new_block()
205 205
206 206
207 207 def appWillTerminate_(self, notification):
208 208 """appWillTerminate"""
209 209
210 210 self.engine.stopService()
211 211
212 212
213 213 def complete(self, token):
214 214 """Complete token in engine's user_ns
215 215
216 216 Parameters
217 217 ----------
218 218 token : string
219 219
220 220 Result
221 221 ------
222 222 Deferred result of
223 223 IPython.kernel.engineservice.IEngineBase.complete
224 224 """
225 225
226 226 return self.engine.complete(token)
227 227
228 228
229 229 def execute(self, block, blockID=None):
230 230 self.waitingForEngine = True
231 231 self.willChangeValueForKey_('commandHistory')
232 232 d = super(IPythonCocoaController, self).execute(block,
233 233 blockID)
234 234 d.addBoth(self._engine_done)
235 235 d.addCallback(self._update_user_ns)
236 236
237 237 return d
238 238
239 239
240 240 def push_(self, namespace):
241 241 """Push dictionary of key=>values to python namespace"""
242 242
243 243 self.waitingForEngine = True
244 244 self.willChangeValueForKey_('commandHistory')
245 245 d = self.engine.push(namespace)
246 246 d.addBoth(self._engine_done)
247 247 d.addCallback(self._update_user_ns)
248 248
249 249
250 250 def pull_(self, keys):
251 251 """Pull keys from python namespace"""
252 252
253 253 self.waitingForEngine = True
254 254 result = blockingCallFromThread(self.engine.pull, keys)
255 255 self.waitingForEngine = False
256 256
257 257 @objc.signature('v@:@I')
258 258 def executeFileAtPath_encoding_(self, path, encoding):
259 259 """Execute file at path in an empty namespace. Update the engine
260 260 user_ns with the resulting locals."""
261 261
262 262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
263 263 path,
264 264 encoding,
265 265 None)
266 266 self.engine.execute(lines)
267 267
268 268
269 269 def _engine_done(self, x):
270 270 self.waitingForEngine = False
271 271 self.didChangeValueForKey_('commandHistory')
272 272 return x
273 273
274 274 def _update_user_ns(self, result):
275 275 """Update self.userNS from self.engine's namespace"""
276 276 d = self.engine.keys()
277 277 d.addCallback(self._get_engine_namespace_values_for_keys)
278 278
279 279 return result
280 280
281 281
282 282 def _get_engine_namespace_values_for_keys(self, keys):
283 283 d = self.engine.pull(keys)
284 284 d.addCallback(self._store_engine_namespace_values, keys=keys)
285 285
286 286
287 287 def _store_engine_namespace_values(self, values, keys=[]):
288 288 assert(len(values) == len(keys))
289 289 self.willChangeValueForKey_('userNS')
290 290 for (k,v) in zip(keys,values):
291 291 self.userNS[k] = saferepr(v)
292 292 self.didChangeValueForKey_('userNS')
293 293
294 294
295 295 def update_cell_prompt(self, result, blockID=None):
296 296 print self.blockRanges
297 297 if(isinstance(result, Failure)):
298 298 prompt = self.input_prompt()
299 299
300 300 else:
301 301 prompt = self.input_prompt(number=result['number'])
302 302
303 303 r = self.blockRanges[blockID].inputPromptRange
304 304 self.insert_text(prompt,
305 305 textRange=r,
306 306 scrollToVisible=False
307 307 )
308 308
309 309 return result
310 310
311 311
312 312 def render_result(self, result):
313 313 blockID = result['blockID']
314 314 inputRange = self.blockRanges[blockID].inputRange
315 315 del self.blockRanges[blockID]
316 316
317 317 #print inputRange,self.current_block_range()
318 318 self.insert_text('\n' +
319 319 self.output_prompt(number=result['number']) +
320 320 result.get('display',{}).get('pprint','') +
321 321 '\n\n',
322 322 textRange=NSMakeRange(inputRange.location+inputRange.length,
323 323 0))
324 324 return result
325 325
326 326
327 327 def render_error(self, failure):
328 328 print failure
329 329 blockID = failure.blockID
330 330 inputRange = self.blockRanges[blockID].inputRange
331 331 self.insert_text('\n' +
332 332 self.output_prompt() +
333 333 '\n' +
334 334 failure.getErrorMessage() +
335 335 '\n\n',
336 336 textRange=NSMakeRange(inputRange.location +
337 337 inputRange.length,
338 338 0))
339 339 self.start_new_block()
340 340 return failure
341 341
342 342
343 343 def _start_cli_banner(self):
344 344 """Print banner"""
345 345
346 346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
347 347 IPython.__version__
348 348
349 349 self.insert_text(banner + '\n\n')
350 350
351 351
352 352 def start_new_block(self):
353 353 """"""
354 354
355 355 self.currentBlockID = self.next_block_ID()
356 356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
357 357 self.insert_text(self.input_prompt(),
358 358 textRange=self.current_block_range().inputPromptRange)
359 359
360 360
361 361
362 362 def next_block_ID(self):
363 363
364 return guid.generate()
364 return uuid.uuid4()
365 365
366 366 def new_cell_block(self):
367 367 """A new CellBlock at the end of self.textView.textStorage()"""
368 368
369 369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
370 370 0), #len(self.input_prompt())),
371 371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
372 372 0))
373 373
374 374
375 375 def current_block_range(self):
376 376 return self.blockRanges.get(self.currentBlockID,
377 377 self.new_cell_block())
378 378
379 379 def current_block(self):
380 380 """The current block's text"""
381 381
382 382 return self.text_for_range(self.current_block_range().inputRange)
383 383
384 384 def text_for_range(self, textRange):
385 385 """text_for_range"""
386 386
387 387 ts = self.textView.textStorage()
388 388 return ts.string().substringWithRange_(textRange)
389 389
390 390 def current_line(self):
391 391 block = self.text_for_range(self.current_block_range().inputRange)
392 392 block = block.split('\n')
393 393 return block[-1]
394 394
395 395
396 396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
397 397 """Insert text into textView at textRange, updating blockRanges
398 398 as necessary
399 399 """
400 400 if(textRange == None):
401 401 #range for end of text
402 402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
403 403
404 404
405 405 self.textView.replaceCharactersInRange_withString_(
406 406 textRange, string)
407 407
408 408 for r in self.blockRanges.itervalues():
409 409 r.update_ranges_for_insertion(string, textRange)
410 410
411 411 self.textView.setSelectedRange_(textRange)
412 412 if(scrollToVisible):
413 413 self.textView.scrollRangeToVisible_(textRange)
414 414
415 415
416 416
417 417 def replace_current_block_with_string(self, textView, string):
418 418 textView.replaceCharactersInRange_withString_(
419 419 self.current_block_range().inputRange,
420 420 string)
421 421 self.current_block_range().inputRange.length = len(string)
422 422 r = NSMakeRange(textView.textStorage().length(), 0)
423 423 textView.scrollRangeToVisible_(r)
424 424 textView.setSelectedRange_(r)
425 425
426 426
427 427 def current_indent_string(self):
428 428 """returns string for indent or None if no indent"""
429 429
430 430 return self._indent_for_block(self.current_block())
431 431
432 432
433 433 def _indent_for_block(self, block):
434 434 lines = block.split('\n')
435 435 if(len(lines) > 1):
436 436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
437 437 if(currentIndent == 0):
438 438 currentIndent = self.tabSpaces
439 439
440 440 if(self.tabUsesSpaces):
441 441 result = ' ' * currentIndent
442 442 else:
443 443 result = '\t' * (currentIndent/self.tabSpaces)
444 444 else:
445 445 result = None
446 446
447 447 return result
448 448
449 449
450 450 # NSTextView delegate methods...
451 451 def textView_doCommandBySelector_(self, textView, selector):
452 452 assert(textView == self.textView)
453 453 NSLog("textView_doCommandBySelector_: "+selector)
454 454
455 455
456 456 if(selector == 'insertNewline:'):
457 457 indent = self.current_indent_string()
458 458 if(indent):
459 459 line = indent + self.current_line()
460 460 else:
461 461 line = self.current_line()
462 462
463 463 if(self.is_complete(self.current_block())):
464 464 self.execute(self.current_block(),
465 465 blockID=self.currentBlockID)
466 466 self.start_new_block()
467 467
468 468 return True
469 469
470 470 return False
471 471
472 472 elif(selector == 'moveUp:'):
473 473 prevBlock = self.get_history_previous(self.current_block())
474 474 if(prevBlock != None):
475 475 self.replace_current_block_with_string(textView, prevBlock)
476 476 else:
477 477 NSBeep()
478 478 return True
479 479
480 480 elif(selector == 'moveDown:'):
481 481 nextBlock = self.get_history_next()
482 482 if(nextBlock != None):
483 483 self.replace_current_block_with_string(textView, nextBlock)
484 484 else:
485 485 NSBeep()
486 486 return True
487 487
488 488 elif(selector == 'moveToBeginningOfParagraph:'):
489 489 textView.setSelectedRange_(NSMakeRange(
490 490 self.current_block_range().inputRange.location,
491 491 0))
492 492 return True
493 493 elif(selector == 'moveToEndOfParagraph:'):
494 494 textView.setSelectedRange_(NSMakeRange(
495 495 self.current_block_range().inputRange.location + \
496 496 self.current_block_range().inputRange.length, 0))
497 497 return True
498 498 elif(selector == 'deleteToEndOfParagraph:'):
499 499 if(textView.selectedRange().location <= \
500 500 self.current_block_range().location):
501 501 raise NotImplemented()
502 502
503 503 return False # don't actually handle the delete
504 504
505 505 elif(selector == 'insertTab:'):
506 506 if(len(self.current_line().strip()) == 0): #only white space
507 507 return False
508 508 else:
509 509 self.textView.complete_(self)
510 510 return True
511 511
512 512 elif(selector == 'deleteBackward:'):
513 513 #if we're at the beginning of the current block, ignore
514 514 if(textView.selectedRange().location == \
515 515 self.current_block_range().inputRange.location):
516 516 return True
517 517 else:
518 518 for r in self.blockRanges.itervalues():
519 519 deleteRange = textView.selectedRange
520 520 if(deleteRange.length == 0):
521 521 deleteRange.location -= 1
522 522 deleteRange.length = 1
523 523 r.update_ranges_for_deletion(deleteRange)
524 524 return False
525 525 return False
526 526
527 527
528 528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
529 529 textView, ranges, replacementStrings):
530 530 """
531 531 Delegate method for NSTextView.
532 532
533 533 Refuse change text in ranges not at end, but make those changes at
534 534 end.
535 535 """
536 536
537 537 assert(len(ranges) == len(replacementStrings))
538 538 allow = True
539 539 for r,s in zip(ranges, replacementStrings):
540 540 r = r.rangeValue()
541 541 if(textView.textStorage().length() > 0 and
542 542 r.location < self.current_block_range().inputRange.location):
543 543 self.insert_text(s)
544 544 allow = False
545 545
546 546 return allow
547 547
548 548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
549 549 textView, words, charRange, index):
550 550 try:
551 551 ts = textView.textStorage()
552 552 token = ts.string().substringWithRange_(charRange)
553 553 completions = blockingCallFromThread(self.complete, token)
554 554 except:
555 555 completions = objc.nil
556 556 NSBeep()
557 557
558 558 return (completions,0)
559 559
560 560
@@ -1,343 +1,343
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 frontendbase provides an interface and base class for GUI frontends for
5 5 IPython.kernel/IPython.kernel.core.
6 6
7 7 Frontend implementations will likely want to subclass FrontEndBase.
8 8
9 9 Author: Barry Wark
10 10 """
11 11 __docformat__ = "restructuredtext en"
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import string
24 24 import codeop
25 from IPython.external import guid
25 import uuid
26 26
27 27
28 28 from IPython.frontend.zopeinterface import (
29 29 Interface,
30 30 Attribute,
31 31 )
32 32 from IPython.kernel.core.history import FrontEndHistory
33 33 from IPython.kernel.core.util import Bunch
34 34
35 35 ##############################################################################
36 36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
37 37 # not
38 38
39 39 rc = Bunch()
40 40 rc.prompt_in1 = r'In [$number]: '
41 41 rc.prompt_in2 = r'...'
42 42 rc.prompt_out = r'Out [$number]: '
43 43
44 44 ##############################################################################
45 45 # Interface definitions
46 46 ##############################################################################
47 47
48 48 class IFrontEndFactory(Interface):
49 49 """Factory interface for frontends."""
50 50
51 51 def __call__(engine=None, history=None):
52 52 """
53 53 Parameters:
54 54 interpreter : IPython.kernel.engineservice.IEngineCore
55 55 """
56 56
57 57 pass
58 58
59 59
60 60 class IFrontEnd(Interface):
61 61 """Interface for frontends. All methods return t.i.d.Deferred"""
62 62
63 63 Attribute("input_prompt_template", "string.Template instance\
64 64 substituteable with execute result.")
65 65 Attribute("output_prompt_template", "string.Template instance\
66 66 substituteable with execute result.")
67 67 Attribute("continuation_prompt_template", "string.Template instance\
68 68 substituteable with execute result.")
69 69
70 70 def update_cell_prompt(result, blockID=None):
71 71 """Subclass may override to update the input prompt for a block.
72 72
73 73 In asynchronous frontends, this method will be called as a
74 74 twisted.internet.defer.Deferred's callback/errback.
75 75 Implementations should thus return result when finished.
76 76
77 77 Result is a result dict in case of success, and a
78 78 twisted.python.util.failure.Failure in case of an error
79 79 """
80 80
81 81 pass
82 82
83 83 def render_result(result):
84 84 """Render the result of an execute call. Implementors may choose the
85 85 method of rendering.
86 86 For example, a notebook-style frontend might render a Chaco plot
87 87 inline.
88 88
89 89 Parameters:
90 90 result : dict (result of IEngineBase.execute )
91 91 blockID = result['blockID']
92 92
93 93 Result:
94 94 Output of frontend rendering
95 95 """
96 96
97 97 pass
98 98
99 99 def render_error(failure):
100 100 """Subclasses must override to render the failure.
101 101
102 102 In asynchronous frontend, since this method will be called as a
103 103 twisted.internet.defer.Deferred's callback. Implementations
104 104 should thus return result when finished.
105 105
106 106 blockID = failure.blockID
107 107 """
108 108
109 109 pass
110 110
111 111 def input_prompt(number=''):
112 112 """Returns the input prompt by subsituting into
113 113 self.input_prompt_template
114 114 """
115 115 pass
116 116
117 117 def output_prompt(number=''):
118 118 """Returns the output prompt by subsituting into
119 119 self.output_prompt_template
120 120 """
121 121
122 122 pass
123 123
124 124 def continuation_prompt():
125 125 """Returns the continuation prompt by subsituting into
126 126 self.continuation_prompt_template
127 127 """
128 128
129 129 pass
130 130
131 131 def is_complete(block):
132 132 """Returns True if block is complete, False otherwise."""
133 133
134 134 pass
135 135
136 136
137 137 def get_history_previous(current_block):
138 138 """Returns the block previous in the history. Saves currentBlock if
139 139 the history_cursor is currently at the end of the input history"""
140 140 pass
141 141
142 142 def get_history_next():
143 143 """Returns the next block in the history."""
144 144
145 145 pass
146 146
147 147 def complete(self, line):
148 148 """Returns the list of possible completions, and the completed
149 149 line.
150 150
151 151 The input argument is the full line to be completed. This method
152 152 returns both the line completed as much as possible, and the list
153 153 of further possible completions (full words).
154 154 """
155 155 pass
156 156
157 157
158 158 ##############################################################################
159 159 # Base class for all the frontends.
160 160 ##############################################################################
161 161
162 162 class FrontEndBase(object):
163 163 """
164 164 FrontEndBase manages the state tasks for a CLI frontend:
165 165 - Input and output history management
166 166 - Input/continuation and output prompt generation
167 167
168 168 Some issues (due to possibly unavailable engine):
169 169 - How do we get the current cell number for the engine?
170 170 - How do we handle completions?
171 171 """
172 172
173 173 history_cursor = 0
174 174
175 175 input_prompt_template = string.Template(rc.prompt_in1)
176 176 output_prompt_template = string.Template(rc.prompt_out)
177 177 continuation_prompt_template = string.Template(rc.prompt_in2)
178 178
179 179 def __init__(self, shell=None, history=None):
180 180 self.shell = shell
181 181 if history is None:
182 182 self.history = FrontEndHistory(input_cache=[''])
183 183 else:
184 184 self.history = history
185 185
186 186
187 187 def input_prompt(self, number=''):
188 188 """Returns the current input prompt
189 189
190 190 It would be great to use ipython1.core.prompts.Prompt1 here
191 191 """
192 192 return self.input_prompt_template.safe_substitute({'number':number})
193 193
194 194
195 195 def continuation_prompt(self):
196 196 """Returns the current continuation prompt"""
197 197
198 198 return self.continuation_prompt_template.safe_substitute()
199 199
200 200 def output_prompt(self, number=''):
201 201 """Returns the output prompt for result"""
202 202
203 203 return self.output_prompt_template.safe_substitute({'number':number})
204 204
205 205
206 206 def is_complete(self, block):
207 207 """Determine if block is complete.
208 208
209 209 Parameters
210 210 block : string
211 211
212 212 Result
213 213 True if block can be sent to the engine without compile errors.
214 214 False otherwise.
215 215 """
216 216
217 217 try:
218 218 is_complete = codeop.compile_command(block.rstrip() + '\n\n',
219 219 "<string>", "exec")
220 220 except:
221 221 return False
222 222
223 223 lines = block.split('\n')
224 224 return ((is_complete is not None)
225 225 and (len(lines)==1 or str(lines[-1])==''))
226 226
227 227
228 228 def execute(self, block, blockID=None):
229 229 """Execute the block and return the result.
230 230
231 231 Parameters:
232 232 block : {str, AST}
233 233 blockID : any
234 234 Caller may provide an ID to identify this block.
235 235 result['blockID'] := blockID
236 236
237 237 Result:
238 238 Deferred result of self.interpreter.execute
239 239 """
240 240
241 241 if(not self.is_complete(block)):
242 242 raise Exception("Block is not compilable")
243 243
244 244 if(blockID == None):
245 blockID = guid.generate()
245 blockID = uuid.uuid4()
246 246
247 247 try:
248 248 result = self.shell.execute(block)
249 249 except Exception,e:
250 250 e = self._add_block_id_for_failure(e, blockID=blockID)
251 251 e = self.update_cell_prompt(e, blockID=blockID)
252 252 e = self.render_error(e)
253 253 else:
254 254 result = self._add_block_id_for_result(result, blockID=blockID)
255 255 result = self.update_cell_prompt(result, blockID=blockID)
256 256 result = self.render_result(result)
257 257
258 258 return result
259 259
260 260
261 261 def _add_block_id_for_result(self, result, blockID):
262 262 """Add the blockID to result or failure. Unfortunatley, we have to
263 263 treat failures differently than result dicts.
264 264 """
265 265
266 266 result['blockID'] = blockID
267 267
268 268 return result
269 269
270 270 def _add_block_id_for_failure(self, failure, blockID):
271 271 """_add_block_id_for_failure"""
272 272 failure.blockID = blockID
273 273 return failure
274 274
275 275
276 276 def _add_history(self, result, block=None):
277 277 """Add block to the history"""
278 278
279 279 assert(block != None)
280 280 self.history.add_items([block])
281 281 self.history_cursor += 1
282 282
283 283 return result
284 284
285 285
286 286 def get_history_previous(self, current_block):
287 287 """ Returns previous history string and decrement history cursor.
288 288 """
289 289 command = self.history.get_history_item(self.history_cursor - 1)
290 290
291 291 if command is not None:
292 292 if(self.history_cursor+1 == len(self.history.input_cache)):
293 293 self.history.input_cache[self.history_cursor] = current_block
294 294 self.history_cursor -= 1
295 295 return command
296 296
297 297
298 298 def get_history_next(self):
299 299 """ Returns next history string and increment history cursor.
300 300 """
301 301 command = self.history.get_history_item(self.history_cursor+1)
302 302
303 303 if command is not None:
304 304 self.history_cursor += 1
305 305 return command
306 306
307 307 ###
308 308 # Subclasses probably want to override these methods...
309 309 ###
310 310
311 311 def update_cell_prompt(self, result, blockID=None):
312 312 """Subclass may override to update the input prompt for a block.
313 313
314 314 This method only really makes sens in asyncrhonous frontend.
315 315 Since this method will be called as a
316 316 twisted.internet.defer.Deferred's callback, implementations should
317 317 return result when finished.
318 318 """
319 319
320 320 raise NotImplementedError
321 321
322 322
323 323 def render_result(self, result):
324 324 """Subclasses must override to render result.
325 325
326 326 In asynchronous frontends, this method will be called as a
327 327 twisted.internet.defer.Deferred's callback. Implementations
328 328 should thus return result when finished.
329 329 """
330 330
331 331 raise NotImplementedError
332 332
333 333
334 334 def render_error(self, failure):
335 335 """Subclasses must override to render the failure.
336 336
337 337 In asynchronous frontends, this method will be called as a
338 338 twisted.internet.defer.Deferred's callback. Implementations
339 339 should thus return result when finished.
340 340 """
341 341
342 342 raise NotImplementedError
343 343
General Comments 0
You need to be logged in to leave comments. Login now