Show More
@@ -298,6 +298,8 b' def runlistpy(self, event):' | |||
|
298 | 298 | return dirs + pys |
|
299 | 299 | |
|
300 | 300 | |
|
301 | greedy_cd_completer = False | |
|
302 | ||
|
301 | 303 | def cd_completer(self, event): |
|
302 | 304 | relpath = event.symbol |
|
303 | 305 | #print event # dbg |
@@ -353,7 +355,10 b' def cd_completer(self, event):' | |||
|
353 | 355 | else: |
|
354 | 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 | 363 | def apt_get_packages(prefix): |
|
359 | 364 | out = os.popen('apt-cache pkgnames') |
@@ -117,6 +117,7 b' def main():' | |||
|
117 | 117 | # and the next best thing to real 'ls -F' |
|
118 | 118 | ip.defalias('d','dir /w /og /on') |
|
119 | 119 | |
|
120 | ip.set_hook('input_prefilter', dotslash_prefilter_f) | |
|
120 | 121 | extend_shell_behavior(ip) |
|
121 | 122 | |
|
122 | 123 | class LastArgFinder: |
@@ -138,9 +139,15 b' class LastArgFinder:' | |||
|
138 | 139 | return parts[-1] |
|
139 | 140 | return "" |
|
140 | 141 | |
|
141 | ||
|
142 | ||
|
143 | ||
|
142 | def dotslash_prefilter_f(self,line): | |
|
143 | """ ./foo now runs foo as system command | |
|
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 | 152 | # XXX You do not need to understand the next function! |
|
146 | 153 | # This should probably be moved out of profile |
@@ -92,6 +92,15 b' def main():' | |||
|
92 | 92 | # at your own risk! |
|
93 | 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 | 106 | # some config helper functions you can use |
@@ -1,27 +1,28 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 |
# -*- test-case-name: |
|
|
2 | # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*- | |
|
3 | 3 | |
|
4 |
"""PyObjC classes to provide a Cocoa frontend to the |
|
|
4 | """PyObjC classes to provide a Cocoa frontend to the | |
|
5 | IPython.kernel.engineservice.IEngineBase. | |
|
5 | 6 | |
|
6 | The Cocoa frontend is divided into two classes: | |
|
7 | - IPythonCocoaController | |
|
8 | - IPythonCLITextViewDelegate | |
|
7 | To add an IPython interpreter to a cocoa app, instantiate an | |
|
8 | IPythonCocoaController in a XIB and connect its textView outlet to an | |
|
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 | 14 | __docformat__ = "restructuredtext en" |
|
14 | 15 | |
|
15 |
#----------------------------------------------------------------------------- |
|
|
16 | # Copyright (C) 2008 Barry Wark <barrywark@gmail.com> | |
|
16 | #----------------------------------------------------------------------------- | |
|
17 | # Copyright (C) 2008 The IPython Development Team | |
|
17 | 18 | # |
|
18 | 19 | # Distributed under the terms of the BSD License. The full license is in |
|
19 | 20 | # the file COPYING, distributed as part of this software. |
|
20 |
#----------------------------------------------------------------------------- |
|
|
21 | #----------------------------------------------------------------------------- | |
|
21 | 22 | |
|
22 |
#----------------------------------------------------------------------------- |
|
|
23 | #----------------------------------------------------------------------------- | |
|
23 | 24 | # Imports |
|
24 |
#----------------------------------------------------------------------------- |
|
|
25 | #----------------------------------------------------------------------------- | |
|
25 | 26 | |
|
26 | 27 | import objc |
|
27 | 28 | import uuid |
@@ -36,18 +37,19 b' from AppKit import NSApplicationWillTerminateNotification, NSBeep,\\' | |||
|
36 | 37 | from pprint import saferepr |
|
37 | 38 | |
|
38 | 39 | import IPython |
|
39 |
from IPython.kernel.engineservice import |
|
|
40 | from IPython.kernel.engineservice import ThreadedEngineService | |
|
40 | 41 | from IPython.frontend.frontendbase import FrontEndBase |
|
41 | 42 | |
|
42 | 43 | from twisted.internet.threads import blockingCallFromThread |
|
43 | 44 | from twisted.python.failure import Failure |
|
44 | 45 | |
|
45 |
#------------------------------------------------------------------------------ |
|
|
46 | #------------------------------------------------------------------------------ | |
|
46 | 47 | # Classes to implement the Cocoa frontend |
|
47 |
#------------------------------------------------------------------------------ |
|
|
48 | #------------------------------------------------------------------------------ | |
|
48 | 49 | |
|
49 | 50 | # TODO: |
|
50 |
# 1. use MultiEngineClient and out-of-process engine rather than |
|
|
51 | # 1. use MultiEngineClient and out-of-process engine rather than | |
|
52 | # ThreadedEngineService? | |
|
51 | 53 | # 2. integrate Xgrid launching of engines |
|
52 | 54 | |
|
53 | 55 | |
@@ -75,7 +77,7 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
75 | 77 | self.lines = {} |
|
76 | 78 | self.tabSpaces = 4 |
|
77 | 79 | self.tabUsesSpaces = True |
|
78 |
self.currentBlockID = self.next |
|
|
80 | self.currentBlockID = self.next_block_ID() | |
|
79 | 81 | self.blockRanges = {} # blockID=>NSRange |
|
80 | 82 | |
|
81 | 83 | |
@@ -89,18 +91,21 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
89 | 91 | NSLog('IPython engine started') |
|
90 | 92 | |
|
91 | 93 | # Register for app termination |
|
92 |
NSNotificationCenter.defaultCenter() |
|
|
93 | 'appWillTerminate:', | |
|
94 |
|
|
|
95 |
|
|
|
94 | nc = NSNotificationCenter.defaultCenter() | |
|
95 | nc.addObserver_selector_name_object_( | |
|
96 | self, | |
|
97 | 'appWillTerminate:', | |
|
98 | NSApplicationWillTerminateNotification, | |
|
99 | None) | |
|
96 | 100 | |
|
97 | 101 | self.textView.setDelegate_(self) |
|
98 | 102 | self.textView.enclosingScrollView().setHasVerticalRuler_(True) |
|
99 |
|
|
|
100 | self.textView.enclosingScrollView(), | |
|
101 | NSVerticalRuler) | |
|
103 | r = NSRulerView.alloc().initWithScrollView_orientation_( | |
|
104 | self.textView.enclosingScrollView(), | |
|
105 | NSVerticalRuler) | |
|
106 | self.verticalRulerView = r | |
|
102 | 107 | self.verticalRulerView.setClientView_(self.textView) |
|
103 |
self. |
|
|
108 | self._start_cli_banner() | |
|
104 | 109 | |
|
105 | 110 | |
|
106 | 111 | def appWillTerminate_(self, notification): |
@@ -118,7 +123,8 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
118 | 123 | |
|
119 | 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 | 130 | return self.engine.complete(token) |
@@ -128,31 +134,31 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
128 | 134 | self.waitingForEngine = True |
|
129 | 135 | self.willChangeValueForKey_('commandHistory') |
|
130 | 136 | d = super(IPythonCocoaController, self).execute(block, blockID) |
|
131 |
d.addBoth(self._engine |
|
|
132 |
d.addCallback(self._update |
|
|
137 | d.addBoth(self._engine_done) | |
|
138 | d.addCallback(self._update_user_ns) | |
|
133 | 139 | |
|
134 | 140 | return d |
|
135 | 141 | |
|
136 | 142 | |
|
137 |
def _engine |
|
|
143 | def _engine_done(self, x): | |
|
138 | 144 | self.waitingForEngine = False |
|
139 | 145 | self.didChangeValueForKey_('commandHistory') |
|
140 | 146 | return x |
|
141 | 147 | |
|
142 |
def _update |
|
|
148 | def _update_user_ns(self, result): | |
|
143 | 149 | """Update self.userNS from self.engine's namespace""" |
|
144 | 150 | d = self.engine.keys() |
|
145 |
d.addCallback(self._get |
|
|
151 | d.addCallback(self._get_engine_namespace_values_for_keys) | |
|
146 | 152 | |
|
147 | 153 | return result |
|
148 | 154 | |
|
149 | 155 | |
|
150 |
def _get |
|
|
156 | def _get_engine_namespace_values_for_keys(self, keys): | |
|
151 | 157 | d = self.engine.pull(keys) |
|
152 |
d.addCallback(self._store |
|
|
158 | d.addCallback(self._store_engine_namespace_values, keys=keys) | |
|
153 | 159 | |
|
154 | 160 | |
|
155 |
def _store |
|
|
161 | def _store_engine_namespace_values(self, values, keys=[]): | |
|
156 | 162 | assert(len(values) == len(keys)) |
|
157 | 163 | self.willChangeValueForKey_('userNS') |
|
158 | 164 | for (k,v) in zip(keys,values): |
@@ -160,39 +166,170 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
160 | 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 | 206 | """Print banner""" |
|
165 | 207 | |
|
166 |
banner = """IPython1 %s -- An enhanced Interactive Python.""" % |
|
|
208 | banner = """IPython1 %s -- An enhanced Interactive Python.""" % \ | |
|
209 | IPython.__version__ | |
|
167 | 210 | |
|
168 | 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 | 308 | def textView_doCommandBySelector_(self, textView, selector): |
|
172 | 309 | assert(textView == self.textView) |
|
173 | 310 | NSLog("textView_doCommandBySelector_: "+selector) |
|
174 | 311 | |
|
175 | 312 | |
|
176 | 313 | if(selector == 'insertNewline:'): |
|
177 |
indent = self.current |
|
|
314 | indent = self.current_indent_string() | |
|
178 | 315 | if(indent): |
|
179 |
line = indent + self.current |
|
|
316 | line = indent + self.current_line() | |
|
180 | 317 | else: |
|
181 |
line = self.current |
|
|
318 | line = self.current_line() | |
|
182 | 319 | |
|
183 |
if(self.is_complete(self.current |
|
|
184 |
self.execute(self.current |
|
|
320 | if(self.is_complete(self.current_block())): | |
|
321 | self.execute(self.current_block(), | |
|
185 | 322 | blockID=self.currentBlockID) |
|
186 |
self.start |
|
|
323 | self.start_new_block() | |
|
187 | 324 | |
|
188 | 325 | return True |
|
189 | 326 | |
|
190 | 327 | return False |
|
191 | 328 | |
|
192 | 329 | elif(selector == 'moveUp:'): |
|
193 |
prevBlock = self.get_history_previous(self.current |
|
|
330 | prevBlock = self.get_history_previous(self.current_block()) | |
|
194 | 331 | if(prevBlock != None): |
|
195 |
self.replace |
|
|
332 | self.replace_current_block_with_string(textView, prevBlock) | |
|
196 | 333 | else: |
|
197 | 334 | NSBeep() |
|
198 | 335 | return True |
@@ -200,26 +337,30 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
200 | 337 | elif(selector == 'moveDown:'): |
|
201 | 338 | nextBlock = self.get_history_next() |
|
202 | 339 | if(nextBlock != None): |
|
203 |
self.replace |
|
|
340 | self.replace_current_block_with_string(textView, nextBlock) | |
|
204 | 341 | else: |
|
205 | 342 | NSBeep() |
|
206 | 343 | return True |
|
207 | 344 | |
|
208 | 345 | elif(selector == 'moveToBeginningOfParagraph:'): |
|
209 |
textView.setSelectedRange_(NSMakeRange( |
|
|
346 | textView.setSelectedRange_(NSMakeRange( | |
|
347 | self.current_block_range().location, | |
|
348 | 0)) | |
|
210 | 349 | return True |
|
211 | 350 | elif(selector == 'moveToEndOfParagraph:'): |
|
212 |
textView.setSelectedRange_(NSMakeRange( |
|
|
213 |
|
|
|
351 | textView.setSelectedRange_(NSMakeRange( | |
|
352 | self.current_block_range().location + \ | |
|
353 | self.current_block_range().length, 0)) | |
|
214 | 354 | return True |
|
215 | 355 | elif(selector == 'deleteToEndOfParagraph:'): |
|
216 |
if(textView.selectedRange().location <= |
|
|
356 | if(textView.selectedRange().location <= \ | |
|
357 | self.current_block_range().location): | |
|
217 | 358 | # Intersect the selected range with the current line range |
|
218 |
if(self.current |
|
|
359 | if(self.current_block_range().length < 0): | |
|
219 | 360 | self.blockRanges[self.currentBlockID].length = 0 |
|
220 | 361 | |
|
221 | 362 | r = NSIntersectionRange(textView.rangesForUserTextChange()[0], |
|
222 |
self.current |
|
|
363 | self.current_block_range()) | |
|
223 | 364 | |
|
224 | 365 | if(r.length > 0): #no intersection |
|
225 | 366 | textView.setSelectedRange_(r) |
@@ -227,7 +368,7 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
227 | 368 | return False # don't actually handle the delete |
|
228 | 369 | |
|
229 | 370 | elif(selector == 'insertTab:'): |
|
230 |
if(len(self.current |
|
|
371 | if(len(self.current_line().strip()) == 0): #only white space | |
|
231 | 372 | return False |
|
232 | 373 | else: |
|
233 | 374 | self.textView.complete_(self) |
@@ -235,39 +376,45 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
235 | 376 | |
|
236 | 377 | elif(selector == 'deleteBackward:'): |
|
237 | 378 | #if we're at the beginning of the current block, ignore |
|
238 |
if(textView.selectedRange().location == |
|
|
379 | if(textView.selectedRange().location == \ | |
|
380 | self.current_block_range().location): | |
|
239 | 381 | return True |
|
240 | 382 | else: |
|
241 |
self.current |
|
|
383 | self.current_block_range().length-=1 | |
|
242 | 384 | return False |
|
243 | 385 | return False |
|
244 | 386 | |
|
245 | 387 | |
|
246 |
def textView_shouldChangeTextInRanges_replacementStrings_(self, |
|
|
388 | def textView_shouldChangeTextInRanges_replacementStrings_(self, | |
|
389 | textView, ranges, replacementStrings): | |
|
247 | 390 | """ |
|
248 | 391 | Delegate method for NSTextView. |
|
249 | 392 | |
|
250 |
Refuse change text in ranges not at end, but make those changes at |
|
|
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 | 397 | assert(len(ranges) == len(replacementStrings)) |
|
255 | 398 | allow = True |
|
256 | 399 | for r,s in zip(ranges, replacementStrings): |
|
257 | 400 | r = r.rangeValue() |
|
258 | 401 | if(textView.textStorage().length() > 0 and |
|
259 |
r.location < self.current |
|
|
402 | r.location < self.current_block_range().location): | |
|
260 | 403 | self.insert_text(s) |
|
261 | 404 | allow = False |
|
262 | 405 | |
|
263 | 406 | |
|
264 |
self.blockRanges.setdefault(self.currentBlockID, |
|
|
407 | self.blockRanges.setdefault(self.currentBlockID, | |
|
408 | self.current_block_range()).length +=\ | |
|
409 | len(s) | |
|
265 | 410 | |
|
266 | 411 | return allow |
|
267 | 412 | |
|
268 |
def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, |
|
|
413 | def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, | |
|
414 | textView, words, charRange, index): | |
|
269 | 415 | try: |
|
270 |
t |
|
|
416 | ts = textView.textStorage() | |
|
417 | token = ts.string().substringWithRange_(charRange) | |
|
271 | 418 | completions = blockingCallFromThread(self.complete, token) |
|
272 | 419 | except: |
|
273 | 420 | completions = objc.nil |
@@ -275,121 +422,4 b' class IPythonCocoaController(NSObject, FrontEndBase):' | |||
|
275 | 422 | |
|
276 | 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 | 1 | # encoding: utf-8 |
|
2 |
"""This file contains unittests for the |
|
|
3 | ||
|
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 | |
|
2 | """This file contains unittests for the | |
|
3 | IPython.frontend.cocoa.cocoa_frontend module. | |
|
9 | 4 | """ |
|
10 | 5 | __docformat__ = "restructuredtext en" |
|
11 | ||
|
12 |
#--------------------------------------------------------------------------- |
|
|
13 | # Copyright (C) 2005 Fernando Perez <fperez@colorado.edu> | |
|
14 | # Brian E Granger <ellisonbg@gmail.com> | |
|
15 | # Benjamin Ragan-Kelley <benjaminrk@gmail.com> | |
|
16 | # | |
|
17 | # Distributed under the terms of the BSD License. The full license is in | |
|
18 | # the file COPYING, distributed as part of this software. | |
|
19 |
#--------------------------------------------------------------------------- |
|
|
20 | ||
|
21 |
#--------------------------------------------------------------------------- |
|
|
22 | # Imports | |
|
23 | #------------------------------------------------------------------------------- | |
|
24 | from IPython.kernel.core.interpreter import Interpreter | |
|
6 | ||
|
7 | #--------------------------------------------------------------------------- | |
|
8 | # Copyright (C) 2005 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 | #--------------------------------------------------------------------------- | |
|
13 | ||
|
14 | #--------------------------------------------------------------------------- | |
|
15 | # Imports | |
|
16 | #--------------------------------------------------------------------------- | |
|
17 | from IPython.kernel.core.interpreter import Interpreter | |
|
25 | 18 | import IPython.kernel.engineservice as es |
|
26 | 19 | from IPython.testing.util import DeferredTestCase |
|
27 | 20 | from twisted.internet.defer import succeed |
@@ -51,7 +44,9 b' class TestIPythonCocoaControler(DeferredTestCase):' | |||
|
51 | 44 | del result['number'] |
|
52 | 45 | del result['id'] |
|
53 | 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 | 51 | def testControllerMirrorsUserNSWithValuesAsStrings(self): |
|
57 | 52 | code = """userns1=1;userns2=2""" |
@@ -1,6 +1,8 b'' | |||
|
1 | 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 |
|
|
4 | frontendbase provides an interface and base class for GUI frontends for | |
|
5 | IPython.kernel/IPython.kernel.core. | |
|
4 | 6 | |
|
5 | 7 | Frontend implementations will likely want to subclass FrontEndBase. |
|
6 | 8 | |
@@ -57,20 +59,34 b' class IFrontEndFactory(zi.Interface):' | |||
|
57 | 59 | class IFrontEnd(zi.Interface): |
|
58 | 60 | """Interface for frontends. All methods return t.i.d.Deferred""" |
|
59 | 61 | |
|
60 |
zi.Attribute("input_prompt_template", "string.Template instance |
|
|
61 | zi.Attribute("output_prompt_template", "string.Template instance substituteable with execute result.") | |
|
62 |
zi.Attribute(" |
|
|
62 | zi.Attribute("input_prompt_template", "string.Template instance\ | |
|
63 | 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 | 69 | def update_cell_prompt(self, result): |
|
65 | 70 | """Subclass may override to update the input prompt for a block. |
|
66 |
Since this method will be called as a |
|
|
67 | implementations should return result when finished.""" | |
|
71 | Since this method will be called as a | |
|
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 | 83 | pass |
|
70 | 84 | |
|
71 | 85 | def render_result(self, result): |
|
72 |
"""Render the result of an execute call. Implementors may choose the |
|
|
73 | For example, a notebook-style frontend might render a Chaco plot inline. | |
|
86 | """Render the result of an execute call. Implementors may choose the | |
|
87 | method of rendering. | |
|
88 | For example, a notebook-style frontend might render a Chaco plot | |
|
89 | inline. | |
|
74 | 90 | |
|
75 | 91 | Parameters: |
|
76 | 92 | result : dict (result of IEngineBase.execute ) |
@@ -82,24 +98,31 b' class IFrontEnd(zi.Interface):' | |||
|
82 | 98 | pass |
|
83 | 99 | |
|
84 | 100 | def render_error(self, failure): |
|
85 |
"""Subclasses must override to render the failure. Since this method |
|
|
86 |
twisted.internet.defer.Deferred's callback, |
|
|
87 | when finished.""" | |
|
101 | """Subclasses must override to render the failure. Since this method | |
|
102 | ill be called as a twisted.internet.defer.Deferred's callback, | |
|
103 | implementations should return result when finished. | |
|
104 | """ | |
|
88 | 105 | |
|
89 | 106 | pass |
|
90 | 107 | |
|
91 | 108 | |
|
92 | 109 | def input_prompt(result={}): |
|
93 |
"""Returns the input prompt by subsituting into |
|
|
110 | """Returns the input prompt by subsituting into | |
|
111 | self.input_prompt_template | |
|
112 | """ | |
|
94 | 113 | pass |
|
95 | 114 | |
|
96 | 115 | def output_prompt(result): |
|
97 |
"""Returns the output prompt by subsituting into |
|
|
116 | """Returns the output prompt by subsituting into | |
|
117 | self.output_prompt_template | |
|
118 | """ | |
|
98 | 119 | |
|
99 | 120 | pass |
|
100 | 121 | |
|
101 | 122 | def continuation_prompt(): |
|
102 |
"""Returns the continuation prompt by subsituting into |
|
|
123 | """Returns the continuation prompt by subsituting into | |
|
124 | self.continuation_prompt_template | |
|
125 | """ | |
|
103 | 126 | |
|
104 | 127 | pass |
|
105 | 128 | |
@@ -221,7 +244,8 b' class FrontEndBase(object):' | |||
|
221 | 244 | Parameters: |
|
222 | 245 | block : {str, AST} |
|
223 | 246 | blockID : any |
|
224 |
Caller may provide an ID to identify this block. |
|
|
247 | Caller may provide an ID to identify this block. | |
|
248 | result['blockID'] := blockID | |
|
225 | 249 | |
|
226 | 250 | Result: |
|
227 | 251 | Deferred result of self.interpreter.execute |
@@ -243,8 +267,8 b' class FrontEndBase(object):' | |||
|
243 | 267 | |
|
244 | 268 | |
|
245 | 269 | def _add_block_id(self, result, blockID): |
|
246 |
"""Add the blockID to result or failure. Unfortunatley, we have to |
|
|
247 | differently than result dicts | |
|
270 | """Add the blockID to result or failure. Unfortunatley, we have to | |
|
271 | treat failures differently than result dicts. | |
|
248 | 272 | """ |
|
249 | 273 | |
|
250 | 274 | if(isinstance(result, Failure)): |
@@ -291,11 +315,12 b' class FrontEndBase(object):' | |||
|
291 | 315 | |
|
292 | 316 | def update_cell_prompt(self, result): |
|
293 | 317 | """Subclass may override to update the input prompt for a block. |
|
294 |
Since this method will be called as a |
|
|
295 | implementations should return result when finished. | |
|
318 | Since this method will be called as a | |
|
319 | twisted.internet.defer.Deferred's callback, implementations should | |
|
320 | return result when finished. | |
|
296 | 321 | |
|
297 |
N |
|
|
298 | do something like:: | |
|
322 | NB: result is a failure if the execute returned a failre. | |
|
323 | To get the blockID, you should do something like:: | |
|
299 | 324 | if(isinstance(result, twisted.python.failure.Failure)): |
|
300 | 325 | blockID = result.blockID |
|
301 | 326 | else: |
@@ -308,17 +333,18 b' class FrontEndBase(object):' | |||
|
308 | 333 | |
|
309 | 334 | |
|
310 | 335 | def render_result(self, result): |
|
311 |
"""Subclasses must override to render result. Since this method will |
|
|
312 |
twisted.internet.defer.Deferred's callback, |
|
|
313 | when finished.""" | |
|
336 | """Subclasses must override to render result. Since this method will | |
|
337 | be called as a twisted.internet.defer.Deferred's callback, | |
|
338 | implementations should return result when finished. | |
|
339 | """ | |
|
314 | 340 | |
|
315 | 341 | return result |
|
316 | 342 | |
|
317 | 343 | |
|
318 | 344 | def render_error(self, failure): |
|
319 |
"""Subclasses must override to render the failure. Since this method |
|
|
320 |
twisted.internet.defer.Deferred's callback, |
|
|
321 | when finished.""" | |
|
345 | """Subclasses must override to render the failure. Since this method | |
|
346 | will be called as a twisted.internet.defer.Deferred's callback, | |
|
347 | implementations should return result when finished.""" | |
|
322 | 348 | |
|
323 | 349 | return failure |
|
324 | 350 |
@@ -4,16 +4,16 b'' | |||
|
4 | 4 | |
|
5 | 5 | __docformat__ = "restructuredtext en" |
|
6 | 6 | |
|
7 |
#--------------------------------------------------------------------------- |
|
|
8 | # Copyright (C) 2008 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 |
#--------------------------------------------------------------------------- |
|
|
13 | ||
|
14 |
#--------------------------------------------------------------------------- |
|
|
15 | # Imports | |
|
16 |
#--------------------------------------------------------------------------- |
|
|
7 | #--------------------------------------------------------------------------- | |
|
8 | # Copyright (C) 2008 The IPython Development Team | |
|
9 | # | |
|
10 | # Distributed under the terms of the BSD License. The full license is in | |
|
11 | # the file COPYING, distributed as part of this software. | |
|
12 | #--------------------------------------------------------------------------- | |
|
13 | ||
|
14 | #--------------------------------------------------------------------------- | |
|
15 | # Imports | |
|
16 | #--------------------------------------------------------------------------- | |
|
17 | 17 | |
|
18 | 18 | import unittest |
|
19 | 19 | from IPython.frontend import frontendbase |
@@ -22,7 +22,8 b' from IPython.kernel.engineservice import EngineService' | |||
|
22 | 22 | class FrontEndCallbackChecker(frontendbase.FrontEndBase): |
|
23 | 23 | """FrontEndBase subclass for checking callbacks""" |
|
24 | 24 | def __init__(self, engine=None, history=None): |
|
25 |
super(FrontEndCallbackChecker, self).__init__(engine=engine, |
|
|
25 | super(FrontEndCallbackChecker, self).__init__(engine=engine, | |
|
26 | history=history) | |
|
26 | 27 | self.updateCalled = False |
|
27 | 28 | self.renderResultCalled = False |
|
28 | 29 | self.renderErrorCalled = False |
@@ -51,7 +52,8 b' class TestFrontendBase(unittest.TestCase):' | |||
|
51 | 52 | |
|
52 | 53 | |
|
53 | 54 | def test_implements_IFrontEnd(self): |
|
54 |
assert(frontendbase.IFrontEnd.implementedBy( |
|
|
55 | assert(frontendbase.IFrontEnd.implementedBy( | |
|
56 | frontendbase.FrontEndBase)) | |
|
55 | 57 | |
|
56 | 58 | |
|
57 | 59 | def test_is_complete_returns_False_for_incomplete_block(self): |
@@ -27,9 +27,11 b' try:' | |||
|
27 | 27 | except ImportError: |
|
28 | 28 | pass |
|
29 | 29 | import os |
|
30 | import platform | |
|
30 | 31 | import re |
|
31 | 32 | import shlex |
|
32 | 33 | import shutil |
|
34 | import subprocess | |
|
33 | 35 | import sys |
|
34 | 36 | import tempfile |
|
35 | 37 | import time |
@@ -2041,6 +2043,55 b" def wrap_deprecated(func, suggest = '<nothing>'):" | |||
|
2041 | 2043 | stacklevel = 2) |
|
2042 | 2044 | return func(*args, **kwargs) |
|
2043 | 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 | 55 | Define a dictionary and initialize both with dict and k=v pairs: |
|
56 | 56 | >>> d={'a':1,'b':2} |
|
57 | 57 | >>> s=Struct(d,hi=10,ho=20) |
|
58 | ||
|
58 | 59 | The return of __repr__ can be used to create a new instance: |
|
59 | 60 | >>> s |
|
60 |
Struct({' |
|
|
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 | 65 | __str__ (called by print) shows it's not quite a regular dictionary: |
|
62 | 66 | >>> print s |
|
63 |
Struct |
|
|
67 | Struct({'__allownew': True, 'a': 1, 'b': 2, 'hi': 10, 'ho': 20}) | |
|
68 | ||
|
64 | 69 | Access by explicitly named key with dot notation: |
|
65 | 70 | >>> s.a |
|
66 | 71 | 1 |
|
72 | ||
|
67 | 73 | Or like a dictionary: |
|
68 | 74 | >>> s['a'] |
|
69 | 75 | 1 |
|
76 | ||
|
70 | 77 | If you want a variable to hold the key value, only dictionary access works: |
|
71 | 78 | >>> key='hi' |
|
72 | 79 | >>> s.key |
|
73 | 80 | Traceback (most recent call last): |
|
74 | 81 | File "<stdin>", line 1, in ? |
|
75 | 82 | AttributeError: Struct instance has no attribute 'key' |
|
83 | ||
|
76 | 84 | >>> s[key] |
|
77 | 85 | 10 |
|
78 | 86 | |
@@ -81,13 +89,16 b' class Struct:' | |||
|
81 | 89 | accessed using the dictionary syntax. Again, an example: |
|
82 | 90 | |
|
83 | 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 | 95 | SyntaxError: keyword can't be an expression |
|
96 | ||
|
86 | 97 | But this does: |
|
87 | 98 | >>> s=Struct() |
|
88 | 99 | >>> s[4]='hi' |
|
89 | 100 | >>> s |
|
90 | Struct({4: 'hi'}) | |
|
101 | Struct({4: 'hi', '__allownew': True}) | |
|
91 | 102 | >>> s[4] |
|
92 | 103 | 'hi' |
|
93 | 104 | """ |
@@ -318,7 +329,8 b' class Struct:' | |||
|
318 | 329 | if __conflict_solve: |
|
319 | 330 | inv_conflict_solve_user = __conflict_solve.copy() |
|
320 | 331 | for name, func in [('preserve',preserve), ('update',update), |
|
321 |
('add',add), ('add_flip',add_flip), |
|
|
332 | ('add',add), ('add_flip',add_flip), | |
|
333 | ('add_s',add_s)]: | |
|
322 | 334 | if name in inv_conflict_solve_user.keys(): |
|
323 | 335 | inv_conflict_solve_user[func] = inv_conflict_solve_user[name] |
|
324 | 336 | del inv_conflict_solve_user[name] |
@@ -369,14 +381,14 b' class Struct:' | |||
|
369 | 381 | return ret |
|
370 | 382 | |
|
371 | 383 | def get(self,attr,val=None): |
|
372 |
"""S.get(k[,d]) -> S[k] if |
|
|
384 | """S.get(k[,d]) -> S[k] if k in S, else d. d defaults to None.""" | |
|
373 | 385 | try: |
|
374 | 386 | return self[attr] |
|
375 | 387 | except KeyError: |
|
376 | 388 | return val |
|
377 | 389 | |
|
378 | 390 | def setdefault(self,attr,val=None): |
|
379 |
"""S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if not S |
|
|
391 | """S.setdefault(k[,d]) -> S.get(k,d), also set S[k]=d if k not in S""" | |
|
380 | 392 | if not self.has_key(attr): |
|
381 | 393 | self[attr] = val |
|
382 | 394 | return self.get(attr,val) |
@@ -384,8 +396,8 b' class Struct:' | |||
|
384 | 396 | def allow_new_attr(self, allow = True): |
|
385 | 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 |
|
|
388 | change already exists in this Struct. | |
|
399 | This can be used to catch typos by verifying that the attribute user | |
|
400 | tries to change already exists in this Struct. | |
|
389 | 401 | """ |
|
390 | 402 | self['__allownew'] = allow |
|
391 | 403 |
@@ -847,11 +847,13 b' class Command(object):' | |||
|
847 | 847 | self.deferred.errback(reason) |
|
848 | 848 | |
|
849 | 849 | class ThreadedEngineService(EngineService): |
|
850 |
"""An EngineService subclass that defers execute commands to a separate |
|
|
850 | """An EngineService subclass that defers execute commands to a separate | |
|
851 | thread. | |
|
851 | 852 | |
|
852 |
ThreadedEngineService uses twisted.internet.threads.deferToThread to |
|
|
853 |
requests to a separate thread. GUI frontends may want to |
|
|
854 | the engine in an IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |
|
853 | ThreadedEngineService uses twisted.internet.threads.deferToThread to | |
|
854 | defer execute requests to a separate thread. GUI frontends may want to | |
|
855 | use ThreadedEngineService as the engine in an | |
|
856 | IPython.frontend.frontendbase.FrontEndBase subclass to prevent | |
|
855 | 857 | block execution from blocking the GUI thread. |
|
856 | 858 | """ |
|
857 | 859 |
|
1 | NO CONTENT: file renamed from docs/ChangeLog to docs/attic/ChangeLog |
@@ -29,6 +29,7 b' New features' | |||
|
29 | 29 | Development Team" as the copyright holder. We give more details about exactly |
|
30 | 30 | what this means in this file. All developer should read this and use the new |
|
31 | 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 | 34 | Bug fixes |
|
34 | 35 | --------- |
@@ -5,22 +5,6 b' IPython development guidelines' | |||
|
5 | 5 | ================================== |
|
6 | 6 | |
|
7 | 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 | 10 | Overview |
@@ -136,11 +120,72 b' Specific subpackages' | |||
|
136 | 120 | Version control |
|
137 | 121 | =============== |
|
138 | 122 | |
|
139 |
In the past, IPython development has been done using `Subversion`__. |
|
|
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 | 185 | .. __: http://subversion.tigris.org/ |
|
142 | 186 | .. __: http://bazaar-vcs.org/ |
|
143 | 187 | .. __: http://www.launchpad.net/ipython |
|
188 | .. __: http://doc.bazaar-vcs.org/bzr.dev/en/user-guide/index.html | |
|
144 | 189 | |
|
145 | 190 | Documentation |
|
146 | 191 | ============= |
@@ -160,7 +160,9 b' def find_data_files():' | |||
|
160 | 160 | ('data', manpagebase, manpages), |
|
161 | 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 | 168 | # Find scripts |
General Comments 0
You need to be logged in to leave comments.
Login now