##// END OF EJS Templates
Upstream merges.
gvaroquaux -
r1298:92012389 merge
parent child Browse files
Show More
@@ -298,6 +298,8 b' def runlistpy(self, event):'
298 return dirs + pys
298 return dirs + pys
299
299
300
300
301 greedy_cd_completer = False
302
301 def cd_completer(self, event):
303 def cd_completer(self, event):
302 relpath = event.symbol
304 relpath = event.symbol
303 #print event # dbg
305 #print event # dbg
@@ -353,7 +355,10 b' def cd_completer(self, event):'
353 else:
355 else:
354 return matches
356 return matches
355
357
356 return single_dir_expand(found)
358 if greedy_cd_completer:
359 return single_dir_expand(found)
360 else:
361 return found
357
362
358 def apt_get_packages(prefix):
363 def apt_get_packages(prefix):
359 out = os.popen('apt-cache pkgnames')
364 out = os.popen('apt-cache pkgnames')
@@ -117,6 +117,7 b' def main():'
117 # and the next best thing to real 'ls -F'
117 # and the next best thing to real 'ls -F'
118 ip.defalias('d','dir /w /og /on')
118 ip.defalias('d','dir /w /og /on')
119
119
120 ip.set_hook('input_prefilter', dotslash_prefilter_f)
120 extend_shell_behavior(ip)
121 extend_shell_behavior(ip)
121
122
122 class LastArgFinder:
123 class LastArgFinder:
@@ -138,9 +139,15 b' class LastArgFinder:'
138 return parts[-1]
139 return parts[-1]
139 return ""
140 return ""
140
141
141
142 def dotslash_prefilter_f(self,line):
142
143 """ ./foo now runs foo as system command
143
144
145 Removes the need for doing !./foo
146 """
147 import IPython.genutils
148 if line.startswith("./"):
149 return "_ip.system(" + IPython.genutils.make_quoted_expr(line)+")"
150 raise ipapi.TryNext
144
151
145 # XXX You do not need to understand the next function!
152 # XXX You do not need to understand the next function!
146 # This should probably be moved out of profile
153 # This should probably be moved out of profile
@@ -92,6 +92,15 b' def main():'
92 # at your own risk!
92 # at your own risk!
93 #import ipy_greedycompleter
93 #import ipy_greedycompleter
94
94
95 # If you are on Linux, you may be annoyed by
96 # "Display all N possibilities? (y or n)" on tab completion,
97 # as well as the paging through "more". Uncomment the following
98 # lines to disable that behaviour
99 #import readline
100 #readline.parse_and_bind('set completion-query-items 1000')
101 #readline.parse_and_bind('set page-completions no')
102
103
95
104
96
105
97 # some config helper functions you can use
106 # some config helper functions you can use
@@ -1,27 +1,28 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: ipython1.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 ipython1.kernel.engineservice.EngineService.
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5
6
6 The Cocoa frontend is divided into two classes:
7 To add an IPython interpreter to a cocoa app, instantiate an
7 - IPythonCocoaController
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 - IPythonCLITextViewDelegate
9 NSTextView instance in your UI. That's it.
9
10
10 To add an IPython interpreter to a cocoa app, instantiate both of these classes in an XIB...[FINISH]
11 Author: Barry Wark
11 """
12 """
12
13
13 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
14
15
15 #-------------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008 Barry Wark <barrywark@gmail.com>
17 # Copyright (C) 2008 The IPython Development Team
17 #
18 #
18 # 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
19 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
21
22
22 #-------------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
23 # Imports
24 # Imports
24 #-------------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25
26
26 import objc
27 import objc
27 import uuid
28 import uuid
@@ -36,18 +37,19 b' from AppKit import NSApplicationWillTerminateNotification, NSBeep,\\'
36 from pprint import saferepr
37 from pprint import saferepr
37
38
38 import IPython
39 import IPython
39 from IPython.kernel.engineservice import EngineService, ThreadedEngineService
40 from IPython.kernel.engineservice import ThreadedEngineService
40 from IPython.frontend.frontendbase import FrontEndBase
41 from IPython.frontend.frontendbase import FrontEndBase
41
42
42 from twisted.internet.threads import blockingCallFromThread
43 from twisted.internet.threads import blockingCallFromThread
43 from twisted.python.failure import Failure
44 from twisted.python.failure import Failure
44
45
45 #-------------------------------------------------------------------------------
46 #------------------------------------------------------------------------------
46 # Classes to implement the Cocoa frontend
47 # Classes to implement the Cocoa frontend
47 #-------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
48
49
49 # TODO:
50 # TODO:
50 # 1. use MultiEngineClient and out-of-process engine rather than ThreadedEngineService?
51 # 1. use MultiEngineClient and out-of-process engine rather than
52 # ThreadedEngineService?
51 # 2. integrate Xgrid launching of engines
53 # 2. integrate Xgrid launching of engines
52
54
53
55
@@ -75,7 +77,7 b' class IPythonCocoaController(NSObject, FrontEndBase):'
75 self.lines = {}
77 self.lines = {}
76 self.tabSpaces = 4
78 self.tabSpaces = 4
77 self.tabUsesSpaces = True
79 self.tabUsesSpaces = True
78 self.currentBlockID = self.nextBlockID()
80 self.currentBlockID = self.next_block_ID()
79 self.blockRanges = {} # blockID=>NSRange
81 self.blockRanges = {} # blockID=>NSRange
80
82
81
83
@@ -89,18 +91,21 b' class IPythonCocoaController(NSObject, FrontEndBase):'
89 NSLog('IPython engine started')
91 NSLog('IPython engine started')
90
92
91 # Register for app termination
93 # Register for app termination
92 NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self,
94 nc = NSNotificationCenter.defaultCenter()
93 'appWillTerminate:',
95 nc.addObserver_selector_name_object_(
94 NSApplicationWillTerminateNotification,
96 self,
95 None)
97 'appWillTerminate:',
98 NSApplicationWillTerminateNotification,
99 None)
96
100
97 self.textView.setDelegate_(self)
101 self.textView.setDelegate_(self)
98 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
102 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
99 self.verticalRulerView = NSRulerView.alloc().initWithScrollView_orientation_(
103 r = NSRulerView.alloc().initWithScrollView_orientation_(
100 self.textView.enclosingScrollView(),
104 self.textView.enclosingScrollView(),
101 NSVerticalRuler)
105 NSVerticalRuler)
106 self.verticalRulerView = r
102 self.verticalRulerView.setClientView_(self.textView)
107 self.verticalRulerView.setClientView_(self.textView)
103 self.startCLIForTextView()
108 self._start_cli_banner()
104
109
105
110
106 def appWillTerminate_(self, notification):
111 def appWillTerminate_(self, notification):
@@ -118,7 +123,8 b' class IPythonCocoaController(NSObject, FrontEndBase):'
118
123
119 Result
124 Result
120 ------
125 ------
121 Deferred result of ipython1.kernel.engineservice.IEngineInteractive.complete
126 Deferred result of
127 IPython.kernel.engineservice.IEngineBase.complete
122 """
128 """
123
129
124 return self.engine.complete(token)
130 return self.engine.complete(token)
@@ -128,31 +134,31 b' class IPythonCocoaController(NSObject, FrontEndBase):'
128 self.waitingForEngine = True
134 self.waitingForEngine = True
129 self.willChangeValueForKey_('commandHistory')
135 self.willChangeValueForKey_('commandHistory')
130 d = super(IPythonCocoaController, self).execute(block, blockID)
136 d = super(IPythonCocoaController, self).execute(block, blockID)
131 d.addBoth(self._engineDone)
137 d.addBoth(self._engine_done)
132 d.addCallback(self._updateUserNS)
138 d.addCallback(self._update_user_ns)
133
139
134 return d
140 return d
135
141
136
142
137 def _engineDone(self, x):
143 def _engine_done(self, x):
138 self.waitingForEngine = False
144 self.waitingForEngine = False
139 self.didChangeValueForKey_('commandHistory')
145 self.didChangeValueForKey_('commandHistory')
140 return x
146 return x
141
147
142 def _updateUserNS(self, result):
148 def _update_user_ns(self, result):
143 """Update self.userNS from self.engine's namespace"""
149 """Update self.userNS from self.engine's namespace"""
144 d = self.engine.keys()
150 d = self.engine.keys()
145 d.addCallback(self._getEngineNamepsaceValuesForKeys)
151 d.addCallback(self._get_engine_namespace_values_for_keys)
146
152
147 return result
153 return result
148
154
149
155
150 def _getEngineNamepsaceValuesForKeys(self, keys):
156 def _get_engine_namespace_values_for_keys(self, keys):
151 d = self.engine.pull(keys)
157 d = self.engine.pull(keys)
152 d.addCallback(self._storeEngineNamespaceValues, keys=keys)
158 d.addCallback(self._store_engine_namespace_values, keys=keys)
153
159
154
160
155 def _storeEngineNamespaceValues(self, values, keys=[]):
161 def _store_engine_namespace_values(self, values, keys=[]):
156 assert(len(values) == len(keys))
162 assert(len(values) == len(keys))
157 self.willChangeValueForKey_('userNS')
163 self.willChangeValueForKey_('userNS')
158 for (k,v) in zip(keys,values):
164 for (k,v) in zip(keys,values):
@@ -160,39 +166,170 b' class IPythonCocoaController(NSObject, FrontEndBase):'
160 self.didChangeValueForKey_('userNS')
166 self.didChangeValueForKey_('userNS')
161
167
162
168
163 def startCLIForTextView(self):
169 def update_cell_prompt(self, result):
170 if(isinstance(result, Failure)):
171 blockID = result.blockID
172 else:
173 blockID = result['blockID']
174
175
176 self.insert_text(self.input_prompt(result=result),
177 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
178 scrollToVisible=False
179 )
180
181 return result
182
183
184 def render_result(self, result):
185 blockID = result['blockID']
186 inputRange = self.blockRanges[blockID]
187 del self.blockRanges[blockID]
188
189 #print inputRange,self.current_block_range()
190 self.insert_text('\n' +
191 self.output_prompt(result) +
192 result.get('display',{}).get('pprint','') +
193 '\n\n',
194 textRange=NSMakeRange(inputRange.location+inputRange.length,
195 0))
196 return result
197
198
199 def render_error(self, failure):
200 self.insert_text('\n\n'+str(failure)+'\n\n')
201 self.start_new_block()
202 return failure
203
204
205 def _start_cli_banner(self):
164 """Print banner"""
206 """Print banner"""
165
207
166 banner = """IPython1 %s -- An enhanced Interactive Python.""" % IPython.__version__
208 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
209 IPython.__version__
167
210
168 self.insert_text(banner + '\n\n')
211 self.insert_text(banner + '\n\n')
169
212
170 # NSTextView/IPythonTextView delegate methods
213
214 def start_new_block(self):
215 """"""
216
217 self.currentBlockID = self.next_block_ID()
218
219
220
221 def next_block_ID(self):
222
223 return uuid.uuid4()
224
225 def current_block_range(self):
226 return self.blockRanges.get(self.currentBlockID,
227 NSMakeRange(self.textView.textStorage().length(),
228 0))
229
230 def current_block(self):
231 """The current block's text"""
232
233 return self.text_for_range(self.current_block_range())
234
235 def text_for_range(self, textRange):
236 """text_for_range"""
237
238 ts = self.textView.textStorage()
239 return ts.string().substringWithRange_(textRange)
240
241 def current_line(self):
242 block = self.text_for_range(self.current_block_range())
243 block = block.split('\n')
244 return block[-1]
245
246
247 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
248 """Insert text into textView at textRange, updating blockRanges
249 as necessary
250 """
251
252 if(textRange == None):
253 #range for end of text
254 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
255
256 for r in self.blockRanges.itervalues():
257 intersection = NSIntersectionRange(r,textRange)
258 if(intersection.length == 0): #ranges don't intersect
259 if r.location >= textRange.location:
260 r.location += len(string)
261 else: #ranges intersect
262 if(r.location <= textRange.location):
263 assert(intersection.length == textRange.length)
264 r.length += textRange.length
265 else:
266 r.location += intersection.length
267
268 self.textView.replaceCharactersInRange_withString_(
269 textRange, string)
270 self.textView.setSelectedRange_(
271 NSMakeRange(textRange.location+len(string), 0))
272 if(scrollToVisible):
273 self.textView.scrollRangeToVisible_(textRange)
274
275
276
277
278 def replace_current_block_with_string(self, textView, string):
279 textView.replaceCharactersInRange_withString_(
280 self.current_block_range(),
281 string)
282 self.current_block_range().length = len(string)
283 r = NSMakeRange(textView.textStorage().length(), 0)
284 textView.scrollRangeToVisible_(r)
285 textView.setSelectedRange_(r)
286
287
288 def current_indent_string(self):
289 """returns string for indent or None if no indent"""
290
291 if(len(self.current_block()) > 0):
292 lines = self.current_block().split('\n')
293 currentIndent = len(lines[-1]) - len(lines[-1])
294 if(currentIndent == 0):
295 currentIndent = self.tabSpaces
296
297 if(self.tabUsesSpaces):
298 result = ' ' * currentIndent
299 else:
300 result = '\t' * (currentIndent/self.tabSpaces)
301 else:
302 result = None
303
304 return result
305
306
307 # NSTextView delegate methods...
171 def textView_doCommandBySelector_(self, textView, selector):
308 def textView_doCommandBySelector_(self, textView, selector):
172 assert(textView == self.textView)
309 assert(textView == self.textView)
173 NSLog("textView_doCommandBySelector_: "+selector)
310 NSLog("textView_doCommandBySelector_: "+selector)
174
311
175
312
176 if(selector == 'insertNewline:'):
313 if(selector == 'insertNewline:'):
177 indent = self.currentIndentString()
314 indent = self.current_indent_string()
178 if(indent):
315 if(indent):
179 line = indent + self.currentLine()
316 line = indent + self.current_line()
180 else:
317 else:
181 line = self.currentLine()
318 line = self.current_line()
182
319
183 if(self.is_complete(self.currentBlock())):
320 if(self.is_complete(self.current_block())):
184 self.execute(self.currentBlock(),
321 self.execute(self.current_block(),
185 blockID=self.currentBlockID)
322 blockID=self.currentBlockID)
186 self.startNewBlock()
323 self.start_new_block()
187
324
188 return True
325 return True
189
326
190 return False
327 return False
191
328
192 elif(selector == 'moveUp:'):
329 elif(selector == 'moveUp:'):
193 prevBlock = self.get_history_previous(self.currentBlock())
330 prevBlock = self.get_history_previous(self.current_block())
194 if(prevBlock != None):
331 if(prevBlock != None):
195 self.replaceCurrentBlockWithString(textView, prevBlock)
332 self.replace_current_block_with_string(textView, prevBlock)
196 else:
333 else:
197 NSBeep()
334 NSBeep()
198 return True
335 return True
@@ -200,26 +337,30 b' class IPythonCocoaController(NSObject, FrontEndBase):'
200 elif(selector == 'moveDown:'):
337 elif(selector == 'moveDown:'):
201 nextBlock = self.get_history_next()
338 nextBlock = self.get_history_next()
202 if(nextBlock != None):
339 if(nextBlock != None):
203 self.replaceCurrentBlockWithString(textView, nextBlock)
340 self.replace_current_block_with_string(textView, nextBlock)
204 else:
341 else:
205 NSBeep()
342 NSBeep()
206 return True
343 return True
207
344
208 elif(selector == 'moveToBeginningOfParagraph:'):
345 elif(selector == 'moveToBeginningOfParagraph:'):
209 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location, 0))
346 textView.setSelectedRange_(NSMakeRange(
347 self.current_block_range().location,
348 0))
210 return True
349 return True
211 elif(selector == 'moveToEndOfParagraph:'):
350 elif(selector == 'moveToEndOfParagraph:'):
212 textView.setSelectedRange_(NSMakeRange(self.currentBlockRange().location + \
351 textView.setSelectedRange_(NSMakeRange(
213 self.currentBlockRange().length, 0))
352 self.current_block_range().location + \
353 self.current_block_range().length, 0))
214 return True
354 return True
215 elif(selector == 'deleteToEndOfParagraph:'):
355 elif(selector == 'deleteToEndOfParagraph:'):
216 if(textView.selectedRange().location <= self.currentBlockRange().location):
356 if(textView.selectedRange().location <= \
357 self.current_block_range().location):
217 # Intersect the selected range with the current line range
358 # Intersect the selected range with the current line range
218 if(self.currentBlockRange().length < 0):
359 if(self.current_block_range().length < 0):
219 self.blockRanges[self.currentBlockID].length = 0
360 self.blockRanges[self.currentBlockID].length = 0
220
361
221 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
362 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
222 self.currentBlockRange())
363 self.current_block_range())
223
364
224 if(r.length > 0): #no intersection
365 if(r.length > 0): #no intersection
225 textView.setSelectedRange_(r)
366 textView.setSelectedRange_(r)
@@ -227,7 +368,7 b' class IPythonCocoaController(NSObject, FrontEndBase):'
227 return False # don't actually handle the delete
368 return False # don't actually handle the delete
228
369
229 elif(selector == 'insertTab:'):
370 elif(selector == 'insertTab:'):
230 if(len(self.currentLine().strip()) == 0): #only white space
371 if(len(self.current_line().strip()) == 0): #only white space
231 return False
372 return False
232 else:
373 else:
233 self.textView.complete_(self)
374 self.textView.complete_(self)
@@ -235,39 +376,45 b' class IPythonCocoaController(NSObject, FrontEndBase):'
235
376
236 elif(selector == 'deleteBackward:'):
377 elif(selector == 'deleteBackward:'):
237 #if we're at the beginning of the current block, ignore
378 #if we're at the beginning of the current block, ignore
238 if(textView.selectedRange().location == self.currentBlockRange().location):
379 if(textView.selectedRange().location == \
380 self.current_block_range().location):
239 return True
381 return True
240 else:
382 else:
241 self.currentBlockRange().length-=1
383 self.current_block_range().length-=1
242 return False
384 return False
243 return False
385 return False
244
386
245
387
246 def textView_shouldChangeTextInRanges_replacementStrings_(self, textView, ranges, replacementStrings):
388 def textView_shouldChangeTextInRanges_replacementStrings_(self,
389 textView, ranges, replacementStrings):
247 """
390 """
248 Delegate method for NSTextView.
391 Delegate method for NSTextView.
249
392
250 Refuse change text in ranges not at end, but make those changes at end.
393 Refuse change text in ranges not at end, but make those changes at
394 end.
251 """
395 """
252
396
253 #print 'textView_shouldChangeTextInRanges_replacementStrings_:',ranges,replacementStrings
254 assert(len(ranges) == len(replacementStrings))
397 assert(len(ranges) == len(replacementStrings))
255 allow = True
398 allow = True
256 for r,s in zip(ranges, replacementStrings):
399 for r,s in zip(ranges, replacementStrings):
257 r = r.rangeValue()
400 r = r.rangeValue()
258 if(textView.textStorage().length() > 0 and
401 if(textView.textStorage().length() > 0 and
259 r.location < self.currentBlockRange().location):
402 r.location < self.current_block_range().location):
260 self.insert_text(s)
403 self.insert_text(s)
261 allow = False
404 allow = False
262
405
263
406
264 self.blockRanges.setdefault(self.currentBlockID, self.currentBlockRange()).length += len(s)
407 self.blockRanges.setdefault(self.currentBlockID,
408 self.current_block_range()).length +=\
409 len(s)
265
410
266 return allow
411 return allow
267
412
268 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, textView, words, charRange, index):
413 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
414 textView, words, charRange, index):
269 try:
415 try:
270 token = textView.textStorage().string().substringWithRange_(charRange)
416 ts = textView.textStorage()
417 token = ts.string().substringWithRange_(charRange)
271 completions = blockingCallFromThread(self.complete, token)
418 completions = blockingCallFromThread(self.complete, token)
272 except:
419 except:
273 completions = objc.nil
420 completions = objc.nil
@@ -275,121 +422,4 b' class IPythonCocoaController(NSObject, FrontEndBase):'
275
422
276 return (completions,0)
423 return (completions,0)
277
424
278
279 def startNewBlock(self):
280 """"""
281
282 self.currentBlockID = self.nextBlockID()
283
284
285
286 def nextBlockID(self):
287
288 return uuid.uuid4()
289
290 def currentBlockRange(self):
291 return self.blockRanges.get(self.currentBlockID, NSMakeRange(self.textView.textStorage().length(), 0))
292
293 def currentBlock(self):
294 """The current block's text"""
295
296 return self.textForRange(self.currentBlockRange())
297
298 def textForRange(self, textRange):
299 """textForRange"""
300
301 return self.textView.textStorage().string().substringWithRange_(textRange)
302
303 def currentLine(self):
304 block = self.textForRange(self.currentBlockRange())
305 block = block.split('\n')
306 return block[-1]
307
308 def update_cell_prompt(self, result):
309 if(isinstance(result, Failure)):
310 blockID = result.blockID
311 else:
312 blockID = result['blockID']
313
314
315 self.insert_text(self.input_prompt(result=result),
316 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
317 scrollToVisible=False
318 )
319
320 return result
321
322
323 def render_result(self, result):
324 blockID = result['blockID']
325 inputRange = self.blockRanges[blockID]
326 del self.blockRanges[blockID]
327
328 #print inputRange,self.currentBlockRange()
329 self.insert_text('\n' +
330 self.output_prompt(result) +
331 result.get('display',{}).get('pprint','') +
332 '\n\n',
333 textRange=NSMakeRange(inputRange.location+inputRange.length, 0))
334 return result
335
336
337 def render_error(self, failure):
338 self.insert_text('\n\n'+str(failure)+'\n\n')
339 self.startNewBlock()
340 return failure
341
342
343 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
344 """Insert text into textView at textRange, updating blockRanges as necessary"""
345
346 if(textRange == None):
347 textRange = NSMakeRange(self.textView.textStorage().length(), 0) #range for end of text
348
349 for r in self.blockRanges.itervalues():
350 intersection = NSIntersectionRange(r,textRange)
351 if(intersection.length == 0): #ranges don't intersect
352 if r.location >= textRange.location:
353 r.location += len(string)
354 else: #ranges intersect
355 if(r.location <= textRange.location):
356 assert(intersection.length == textRange.length)
357 r.length += textRange.length
358 else:
359 r.location += intersection.length
360
361 self.textView.replaceCharactersInRange_withString_(textRange, string) #textStorage().string()
362 self.textView.setSelectedRange_(NSMakeRange(textRange.location+len(string), 0))
363 if(scrollToVisible):
364 self.textView.scrollRangeToVisible_(textRange)
365
366
367
368 def replaceCurrentBlockWithString(self, textView, string):
369 textView.replaceCharactersInRange_withString_(self.currentBlockRange(),
370 string)
371 self.currentBlockRange().length = len(string)
372 r = NSMakeRange(textView.textStorage().length(), 0)
373 textView.scrollRangeToVisible_(r)
374 textView.setSelectedRange_(r)
375
376
377 def currentIndentString(self):
378 """returns string for indent or None if no indent"""
379
380 if(len(self.currentBlock()) > 0):
381 lines = self.currentBlock().split('\n')
382 currentIndent = len(lines[-1]) - len(lines[-1])
383 if(currentIndent == 0):
384 currentIndent = self.tabSpaces
385
386 if(self.tabUsesSpaces):
387 result = ' ' * currentIndent
388 else:
389 result = '\t' * (currentIndent/self.tabSpaces)
390 else:
391 result = None
392
393 return result
394
395
425
@@ -1,27 +1,20 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """This file contains unittests for the ipython1.frontend.cocoa.cocoa_frontend module.
2 """This file contains unittests for the
3
3 IPython.frontend.cocoa.cocoa_frontend module.
4 Things that should be tested:
5
6 - IPythonCocoaController instantiates an IEngineInteractive
7 - IPythonCocoaController executes code on the engine
8 - IPythonCocoaController mirrors engine's user_ns
9 """
4 """
10 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
11
6
12 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
13 # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
8 # Copyright (C) 2005 The IPython Development Team
14 # Brian E Granger <ellisonbg@gmail.com>
9 #
15 # Benjamin Ragan-Kelley <benjaminrk@gmail.com>
10 # Distributed under the terms of the BSD License. The full license is in
16 #
11 # the file COPYING, distributed as part of this software.
17 # Distributed under the terms of the BSD License. The full license is in
12 #---------------------------------------------------------------------------
18 # the file COPYING, distributed as part of this software.
13
19 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
20
15 # Imports
21 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
22 # Imports
17 from IPython.kernel.core.interpreter import Interpreter
23 #-------------------------------------------------------------------------------
24 from IPython.kernel.core.interpreter import Interpreter
25 import IPython.kernel.engineservice as es
18 import IPython.kernel.engineservice as es
26 from IPython.testing.util import DeferredTestCase
19 from IPython.testing.util import DeferredTestCase
27 from twisted.internet.defer import succeed
20 from twisted.internet.defer import succeed
@@ -51,7 +44,9 b' class TestIPythonCocoaControler(DeferredTestCase):'
51 del result['number']
44 del result['number']
52 del result['id']
45 del result['id']
53 return result
46 return result
54 self.assertDeferredEquals(self.controller.execute(code).addCallback(removeNumberAndID), expected)
47 self.assertDeferredEquals(
48 self.controller.execute(code).addCallback(removeNumberAndID),
49 expected)
55
50
56 def testControllerMirrorsUserNSWithValuesAsStrings(self):
51 def testControllerMirrorsUserNSWithValuesAsStrings(self):
57 code = """userns1=1;userns2=2"""
52 code = """userns1=1;userns2=2"""
@@ -1,6 +1,8 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
2 """
3 """
3 frontendbase provides an interface and base class for GUI frontends for IPython.kernel/IPython.kernel.core.
4 frontendbase provides an interface and base class for GUI frontends for
5 IPython.kernel/IPython.kernel.core.
4
6
5 Frontend implementations will likely want to subclass FrontEndBase.
7 Frontend implementations will likely want to subclass FrontEndBase.
6
8
@@ -57,20 +59,34 b' class IFrontEndFactory(zi.Interface):'
57 class IFrontEnd(zi.Interface):
59 class IFrontEnd(zi.Interface):
58 """Interface for frontends. All methods return t.i.d.Deferred"""
60 """Interface for frontends. All methods return t.i.d.Deferred"""
59
61
60 zi.Attribute("input_prompt_template", "string.Template instance substituteable with execute result.")
62 zi.Attribute("input_prompt_template", "string.Template instance\
61 zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.")
63 substituteable with execute result.")
62 zi.Attribute("continuation_prompt_template", "string.Template instance substituteable with execute result.")
64 zi.Attribute("output_prompt_template", "string.Template instance\
65 substituteable with execute result.")
66 zi.Attribute("continuation_prompt_template", "string.Template instance\
67 substituteable with execute result.")
63
68
64 def update_cell_prompt(self, result):
69 def update_cell_prompt(self, result):
65 """Subclass may override to update the input prompt for a block.
70 """Subclass may override to update the input prompt for a block.
66 Since this method will be called as a twisted.internet.defer.Deferred's callback,
71 Since this method will be called as a
67 implementations should return result when finished."""
72 twisted.internet.defer.Deferred's callback,
73 implementations should return result when finished.
74
75 NB: result is a failure if the execute returned a failre.
76 To get the blockID, you should do something like::
77 if(isinstance(result, twisted.python.failure.Failure)):
78 blockID = result.blockID
79 else:
80 blockID = result['blockID']
81 """
68
82
69 pass
83 pass
70
84
71 def render_result(self, result):
85 def render_result(self, result):
72 """Render the result of an execute call. Implementors may choose the method of rendering.
86 """Render the result of an execute call. Implementors may choose the
73 For example, a notebook-style frontend might render a Chaco plot inline.
87 method of rendering.
88 For example, a notebook-style frontend might render a Chaco plot
89 inline.
74
90
75 Parameters:
91 Parameters:
76 result : dict (result of IEngineBase.execute )
92 result : dict (result of IEngineBase.execute )
@@ -82,24 +98,31 b' class IFrontEnd(zi.Interface):'
82 pass
98 pass
83
99
84 def render_error(self, failure):
100 def render_error(self, failure):
85 """Subclasses must override to render the failure. Since this method will be called as a
101 """Subclasses must override to render the failure. Since this method
86 twisted.internet.defer.Deferred's callback, implementations should return result
102 ill be called as a twisted.internet.defer.Deferred's callback,
87 when finished."""
103 implementations should return result when finished.
104 """
88
105
89 pass
106 pass
90
107
91
108
92 def input_prompt(result={}):
109 def input_prompt(result={}):
93 """Returns the input prompt by subsituting into self.input_prompt_template"""
110 """Returns the input prompt by subsituting into
111 self.input_prompt_template
112 """
94 pass
113 pass
95
114
96 def output_prompt(result):
115 def output_prompt(result):
97 """Returns the output prompt by subsituting into self.output_prompt_template"""
116 """Returns the output prompt by subsituting into
117 self.output_prompt_template
118 """
98
119
99 pass
120 pass
100
121
101 def continuation_prompt():
122 def continuation_prompt():
102 """Returns the continuation prompt by subsituting into self.continuation_prompt_template"""
123 """Returns the continuation prompt by subsituting into
124 self.continuation_prompt_template
125 """
103
126
104 pass
127 pass
105
128
@@ -221,7 +244,8 b' class FrontEndBase(object):'
221 Parameters:
244 Parameters:
222 block : {str, AST}
245 block : {str, AST}
223 blockID : any
246 blockID : any
224 Caller may provide an ID to identify this block. result['blockID'] := blockID
247 Caller may provide an ID to identify this block.
248 result['blockID'] := blockID
225
249
226 Result:
250 Result:
227 Deferred result of self.interpreter.execute
251 Deferred result of self.interpreter.execute
@@ -243,8 +267,8 b' class FrontEndBase(object):'
243
267
244
268
245 def _add_block_id(self, result, blockID):
269 def _add_block_id(self, result, blockID):
246 """Add the blockID to result or failure. Unfortunatley, we have to treat failures
270 """Add the blockID to result or failure. Unfortunatley, we have to
247 differently than result dicts
271 treat failures differently than result dicts.
248 """
272 """
249
273
250 if(isinstance(result, Failure)):
274 if(isinstance(result, Failure)):
@@ -291,11 +315,12 b' class FrontEndBase(object):'
291
315
292 def update_cell_prompt(self, result):
316 def update_cell_prompt(self, result):
293 """Subclass may override to update the input prompt for a block.
317 """Subclass may override to update the input prompt for a block.
294 Since this method will be called as a twisted.internet.defer.Deferred's callback,
318 Since this method will be called as a
295 implementations should return result when finished.
319 twisted.internet.defer.Deferred's callback, implementations should
320 return result when finished.
296
321
297 NP: result is a failure if the execute returned a failre. To get the blockID, you should
322 NB: result is a failure if the execute returned a failre.
298 do something like::
323 To get the blockID, you should do something like::
299 if(isinstance(result, twisted.python.failure.Failure)):
324 if(isinstance(result, twisted.python.failure.Failure)):
300 blockID = result.blockID
325 blockID = result.blockID
301 else:
326 else:
@@ -308,17 +333,18 b' class FrontEndBase(object):'
308
333
309
334
310 def render_result(self, result):
335 def render_result(self, result):
311 """Subclasses must override to render result. Since this method will be called as a
336 """Subclasses must override to render result. Since this method will
312 twisted.internet.defer.Deferred's callback, implementations should return result
337 be called as a twisted.internet.defer.Deferred's callback,
313 when finished."""
338 implementations should return result when finished.
339 """
314
340
315 return result
341 return result
316
342
317
343
318 def render_error(self, failure):
344 def render_error(self, failure):
319 """Subclasses must override to render the failure. Since this method will be called as a
345 """Subclasses must override to render the failure. Since this method
320 twisted.internet.defer.Deferred's callback, implementations should return result
346 will be called as a twisted.internet.defer.Deferred's callback,
321 when finished."""
347 implementations should return result when finished."""
322
348
323 return failure
349 return failure
324
350
@@ -4,16 +4,16 b''
4
4
5 __docformat__ = "restructuredtext en"
5 __docformat__ = "restructuredtext en"
6
6
7 #-------------------------------------------------------------------------------
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
8 # Copyright (C) 2008 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13
13
14 #-------------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-------------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17
17
18 import unittest
18 import unittest
19 from IPython.frontend import frontendbase
19 from IPython.frontend import frontendbase
@@ -22,7 +22,8 b' from IPython.kernel.engineservice import EngineService'
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
22 class FrontEndCallbackChecker(frontendbase.FrontEndBase):
23 """FrontEndBase subclass for checking callbacks"""
23 """FrontEndBase subclass for checking callbacks"""
24 def __init__(self, engine=None, history=None):
24 def __init__(self, engine=None, history=None):
25 super(FrontEndCallbackChecker, self).__init__(engine=engine, history=history)
25 super(FrontEndCallbackChecker, self).__init__(engine=engine,
26 history=history)
26 self.updateCalled = False
27 self.updateCalled = False
27 self.renderResultCalled = False
28 self.renderResultCalled = False
28 self.renderErrorCalled = False
29 self.renderErrorCalled = False
@@ -51,7 +52,8 b' class TestFrontendBase(unittest.TestCase):'
51
52
52
53
53 def test_implements_IFrontEnd(self):
54 def test_implements_IFrontEnd(self):
54 assert(frontendbase.IFrontEnd.implementedBy(frontendbase.FrontEndBase))
55 assert(frontendbase.IFrontEnd.implementedBy(
56 frontendbase.FrontEndBase))
55
57
56
58
57 def test_is_complete_returns_False_for_incomplete_block(self):
59 def test_is_complete_returns_False_for_incomplete_block(self):
@@ -27,9 +27,11 b' try:'
27 except ImportError:
27 except ImportError:
28 pass
28 pass
29 import os
29 import os
30 import platform
30 import re
31 import re
31 import shlex
32 import shlex
32 import shutil
33 import shutil
34 import subprocess
33 import sys
35 import sys
34 import tempfile
36 import tempfile
35 import time
37 import time
@@ -2041,6 +2043,55 b" def wrap_deprecated(func, suggest = '<nothing>'):"
2041 stacklevel = 2)
2043 stacklevel = 2)
2042 return func(*args, **kwargs)
2044 return func(*args, **kwargs)
2043 return newFunc
2045 return newFunc
2044
2045 #*************************** end of file <genutils.py> **********************
2046
2046
2047
2048 def _num_cpus_unix():
2049 """Return the number of active CPUs on a Unix system."""
2050 return os.sysconf("SC_NPROCESSORS_ONLN")
2051
2052
2053 def _num_cpus_darwin():
2054 """Return the number of active CPUs on a Darwin system."""
2055 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
2056 return p.stdout.read()
2057
2058
2059 def _num_cpus_windows():
2060 """Return the number of active CPUs on a Windows system."""
2061 return os.environ.get("NUMBER_OF_PROCESSORS")
2062
2063
2064 def num_cpus():
2065 """Return the effective number of CPUs in the system as an integer.
2066
2067 This cross-platform function makes an attempt at finding the total number of
2068 available CPUs in the system, as returned by various underlying system and
2069 python calls.
2070
2071 If it can't find a sensible answer, it returns 1 (though an error *may* make
2072 it return a large positive number that's actually incorrect).
2073 """
2074
2075 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
2076 # for the names of the keys we needed to look up for this function. This
2077 # code was inspired by their equivalent function.
2078
2079 ncpufuncs = {'Linux':_num_cpus_unix,
2080 'Darwin':_num_cpus_darwin,
2081 'Windows':_num_cpus_windows,
2082 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
2083 # See http://bugs.python.org/issue1082 for details.
2084 'Microsoft':_num_cpus_windows,
2085 }
2086
2087 ncpufunc = ncpufuncs.get(platform.system(),
2088 # default to unix version (Solaris, AIX, etc)
2089 _num_cpus_unix)
2090
2091 try:
2092 ncpus = max(1,int(ncpufunc()))
2093 except:
2094 ncpus = 1
2095 return ncpus
2096
2097 #*************************** end of file <genutils.py> **********************
@@ -55,24 +55,32 b' class Struct:'
55 Define a dictionary and initialize both with dict and k=v pairs:
55 Define a dictionary and initialize both with dict and k=v pairs:
56 >>> d={'a':1,'b':2}
56 >>> d={'a':1,'b':2}
57 >>> s=Struct(d,hi=10,ho=20)
57 >>> s=Struct(d,hi=10,ho=20)
58
58 The return of __repr__ can be used to create a new instance:
59 The return of __repr__ can be used to create a new instance:
59 >>> s
60 >>> s
60 Struct({'ho': 20, 'b': 2, 'hi': 10, 'a': 1})
61 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
62
63 Note: the special '__allownew' key is used for internal purposes.
64
61 __str__ (called by print) shows it's not quite a regular dictionary:
65 __str__ (called by print) shows it's not quite a regular dictionary:
62 >>> print s
66 >>> print s
63 Struct {a: 1, b: 2, hi: 10, ho: 20}
67 Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20})
68
64 Access by explicitly named key with dot notation:
69 Access by explicitly named key with dot notation:
65 >>> s.a
70 >>> s.a
66 1
71 1
72
67 Or like a dictionary:
73 Or like a dictionary:
68 >>> s['a']
74 >>> s['a']
69 1
75 1
76
70 If you want a variable to hold the key value, only dictionary access works:
77 If you want a variable to hold the key value, only dictionary access works:
71 >>> key='hi'
78 >>> key='hi'
72 >>> s.key
79 >>> s.key
73 Traceback (most recent call last):
80 Traceback (most recent call last):
74 File "<stdin>", line 1, in ?
81 File "<stdin>", line 1, in ?
75 AttributeError: Struct instance has no attribute 'key'
82 AttributeError: Struct instance has no attribute 'key'
83
76 >>> s[key]
84 >>> s[key]
77 10
85 10
78
86
@@ -81,13 +89,16 b' class Struct:'
81 accessed using the dictionary syntax. Again, an example:
89 accessed using the dictionary syntax. Again, an example:
82
90
83 This doesn't work:
91 This doesn't work:
84 >>> s=Struct(4='hi')
92 >>> s=Struct(4='hi') #doctest: +IGNORE_EXCEPTION_DETAIL
93 Traceback (most recent call last):
94 ...
85 SyntaxError: keyword can't be an expression
95 SyntaxError: keyword can't be an expression
96
86 But this does:
97 But this does:
87 >>> s=Struct()
98 >>> s=Struct()
88 >>> s[4]='hi'
99 >>> s[4]='hi'
89 >>> s
100 >>> s
90 Struct({4: 'hi'})
101 Struct({4: 'hi', '__allownew': True})
91 >>> s[4]
102 >>> s[4]
92 'hi'
103 'hi'
93 """
104 """
@@ -318,7 +329,8 b' class Struct:'
318 if __conflict_solve:
329 if __conflict_solve:
319 inv_conflict_solve_user = __conflict_solve.copy()
330 inv_conflict_solve_user = __conflict_solve.copy()
320 for name, func in [('preserve',preserve), ('update',update),
331 for name, func in [('preserve',preserve), ('update',update),
321 ('add',add), ('add_flip',add_flip), ('add_s',add_s)]:
332 ('add',add), ('add_flip',add_flip),
333 ('add_s',add_s)]:
322 if name in inv_conflict_solve_user.keys():
334 if name in inv_conflict_solve_user.keys():
323 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
335 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
324 del inv_conflict_solve_user[name]
336 del inv_conflict_solve_user[name]
@@ -369,14 +381,14 b' class Struct:'
369 return ret
381 return ret
370
382
371 def get(self,attr,val=None):
383 def get(self,attr,val=None):
372 """S.get(k[,d]) -> S[k] if S.has_key(k), else d. d defaults to None."""
384 """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None."""
373 try:
385 try:
374 return self[attr]
386 return self[attr]
375 except KeyError:
387 except KeyError:
376 return val
388 return val
377
389
378 def setdefault(self,attr,val=None):
390 def setdefault(self,attr,val=None):
379 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S.has_key(k)"""
391 """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S"""
380 if not self.has_key(attr):
392 if not self.has_key(attr):
381 self[attr] = val
393 self[attr] = val
382 return self.get(attr,val)
394 return self.get(attr,val)
@@ -384,8 +396,8 b' class Struct:'
384 def allow_new_attr(self, allow = True):
396 def allow_new_attr(self, allow = True):
385 """ Set whether new attributes can be created inside struct
397 """ Set whether new attributes can be created inside struct
386
398
387 This can be used to catch typos by verifying that the attribute user tries to
399 This can be used to catch typos by verifying that the attribute user
388 change already exists in this Struct.
400 tries to change already exists in this Struct.
389 """
401 """
390 self['__allownew'] = allow
402 self['__allownew'] = allow
391
403
@@ -847,11 +847,13 b' class Command(object):'
847 self.deferred.errback(reason)
847 self.deferred.errback(reason)
848
848
849 class ThreadedEngineService(EngineService):
849 class ThreadedEngineService(EngineService):
850 """An EngineService subclass that defers execute commands to a separate thread.
850 """An EngineService subclass that defers execute commands to a separate
851 thread.
851
852
852 ThreadedEngineService uses twisted.internet.threads.deferToThread to defer execute
853 ThreadedEngineService uses twisted.internet.threads.deferToThread to
853 requests to a separate thread. GUI frontends may want to use ThreadedEngineService as
854 defer execute requests to a separate thread. GUI frontends may want to
854 the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 use ThreadedEngineService as the engine in an
856 IPython.frontend.frontendbase.FrontEndBase subclass to prevent
855 block execution from blocking the GUI thread.
857 block execution from blocking the GUI thread.
856 """
858 """
857
859
1 NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog
NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog
@@ -29,6 +29,7 b' New features'
29 Development Team" as the copyright holder. We give more details about exactly
29 Development Team" as the copyright holder. We give more details about exactly
30 what this means in this file. All developer should read this and use the new
30 what this means in this file. All developer should read this and use the new
31 banner in all IPython source code files.
31 banner in all IPython source code files.
32 * sh profile: ./foo runs foo as system command, no need to do !./foo anymore
32
33
33 Bug fixes
34 Bug fixes
34 ---------
35 ---------
@@ -5,22 +5,6 b' IPython development guidelines'
5 ==================================
5 ==================================
6
6
7 .. contents::
7 .. contents::
8 ..
9 1 Overview
10 2 Project organization
11 2.1 Subpackages
12 2.2 Installation and dependencies
13 2.3 Specific subpackages
14 3 Version control
15 4 Documentation
16 4.1 Standalone documentation
17 4.2 Docstring format
18 5 Coding conventions
19 5.1 General
20 5.2 Naming conventions
21 6 Testing
22 7 Configuration
23 ..
24
8
25
9
26 Overview
10 Overview
@@ -136,11 +120,72 b' Specific subpackages'
136 Version control
120 Version control
137 ===============
121 ===============
138
122
139 In the past, IPython development has been done using `Subversion`__. We are currently trying out `Bazaar`__ and `Launchpad`__.
123 In the past, IPython development has been done using `Subversion`__. Recently, we made the transition to using `Bazaar`__ and `Launchpad`__. This makes it much easier for people
124 to contribute code to IPython. Here is a sketch of how to use Bazaar for IPython
125 development. First, you should install Bazaar. After you have done that, make
126 sure that it is working by getting the latest main branch of IPython::
127
128 $ bzr branch lp:ipython
129
130 Now you can create a new branch for you to do your work in::
131
132 $ bzr branch ipython ipython-mybranch
133
134 The typical work cycle in this branch will be to make changes in `ipython-mybranch`
135 and then commit those changes using the commit command::
136
137 $ ...do work in ipython-mybranch...
138 $ bzr ci -m "the commit message goes here"
139
140 Please note that since we now don't use an old-style linear ChangeLog
141 (that tends to cause problems with distributed version control
142 systems), you should ensure that your log messages are reasonably
143 detailed. Use a docstring-like approach in the commit messages
144 (including the second line being left *blank*)::
145
146 Single line summary of changes being committed.
147
148 - more details when warranted ...
149 - including crediting outside contributors if they sent the
150 code/bug/idea!
151
152 If we couple this with a policy of making single commits for each
153 reasonably atomic change, the bzr log should give an excellent view of
154 the project, and the `--short` log option becomes a nice summary.
155
156 While working with this branch, it is a good idea to merge in changes that have been
157 made upstream in the parent branch. This can be done by doing::
158
159 $ bzr pull
160
161 If this command shows that the branches have diverged, then you should do a merge
162 instead::
163
164 $ bzr merge lp:ipython
165
166 If you want others to be able to see your branch, you can create an account with
167 launchpad and push the branch to your own workspace::
168
169 $ bzr push bzr+ssh://<me>@bazaar.launchpad.net/~<me>/+junk/ipython-mybranch
170
171 Finally, once the work in your branch is done, you can merge your changes back into
172 the `ipython` branch by using merge::
173
174 $ cd ipython
175 $ merge ../ipython-mybranch
176 [resolve any conflicts]
177 $ bzr ci -m "Fixing that bug"
178 $ bzr push
179
180 But this will require you to have write permissions to the `ipython` branch. It you don't
181 you can tell one of the IPython devs about your branch and they can do the merge for you.
182
183 More information about Bazaar workflows can be found `here`__.
140
184
141 .. __: http://subversion.tigris.org/
185 .. __: http://subversion.tigris.org/
142 .. __: http://bazaar-vcs.org/
186 .. __: http://bazaar-vcs.org/
143 .. __: http://www.launchpad.net/ipython
187 .. __: http://www.launchpad.net/ipython
188 .. __: http://doc.bazaar-vcs.org/bzr.dev/en/user-guide/index.html
144
189
145 Documentation
190 Documentation
146 =============
191 =============
@@ -160,7 +160,9 b' def find_data_files():'
160 ('data', manpagebase, manpages),
160 ('data', manpagebase, manpages),
161 ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles),
161 ('data',pjoin(docdirbase, 'extensions'),igridhelpfiles),
162 ]
162 ]
163 return data_files
163 # import pprint
164 # pprint.pprint(data_files)
165 return []
164
166
165 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
166 # Find scripts
168 # Find scripts
General Comments 0
You need to be logged in to leave comments. Login now