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: |
|
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 | 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 |
|
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 |
|
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.next |
|
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() |
|
94 | nc = NSNotificationCenter.defaultCenter() | |
93 | 'appWillTerminate:', |
|
95 | nc.addObserver_selector_name_object_( | |
94 |
|
|
96 | self, | |
95 |
|
|
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 |
|
|
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. |
|
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._engine |
|
137 | d.addBoth(self._engine_done) | |
132 |
d.addCallback(self._update |
|
138 | d.addCallback(self._update_user_ns) | |
133 |
|
139 | |||
134 | return d |
|
140 | return d | |
135 |
|
141 | |||
136 |
|
142 | |||
137 |
def _engine |
|
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 _update |
|
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._get |
|
151 | d.addCallback(self._get_engine_namespace_values_for_keys) | |
146 |
|
152 | |||
147 | return result |
|
153 | return result | |
148 |
|
154 | |||
149 |
|
155 | |||
150 |
def _get |
|
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._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 | 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.""" % |
|
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.current |
|
314 | indent = self.current_indent_string() | |
178 | if(indent): |
|
315 | if(indent): | |
179 |
line = indent + self.current |
|
316 | line = indent + self.current_line() | |
180 | else: |
|
317 | else: | |
181 |
line = self.current |
|
318 | line = self.current_line() | |
182 |
|
319 | |||
183 |
if(self.is_complete(self.current |
|
320 | if(self.is_complete(self.current_block())): | |
184 |
self.execute(self.current |
|
321 | self.execute(self.current_block(), | |
185 | blockID=self.currentBlockID) |
|
322 | blockID=self.currentBlockID) | |
186 |
self.start |
|
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.current |
|
330 | prevBlock = self.get_history_previous(self.current_block()) | |
194 | if(prevBlock != None): |
|
331 | if(prevBlock != None): | |
195 |
self.replace |
|
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.replace |
|
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( |
|
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( |
|
351 | textView.setSelectedRange_(NSMakeRange( | |
213 |
|
|
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 <= |
|
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.current |
|
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.current |
|
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.current |
|
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 == |
|
379 | if(textView.selectedRange().location == \ | |
|
380 | self.current_block_range().location): | |||
239 | return True |
|
381 | return True | |
240 | else: |
|
382 | else: | |
241 |
self.current |
|
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, |
|
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 |
|
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.current |
|
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, |
|
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, |
|
413 | def textView_completions_forPartialWordRange_indexOfSelectedItem_(self, | |
|
414 | textView, words, charRange, index): | |||
269 | try: |
|
415 | try: | |
270 |
t |
|
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 |
|
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 |
|
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 |
|
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(" |
|
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 |
|
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 |
|
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 |
|
101 | """Subclasses must override to render the failure. Since this method | |
86 |
twisted.internet.defer.Deferred's callback, |
|
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 |
|
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 |
|
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 |
|
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. |
|
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 |
|
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 |
|
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 |
N |
|
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 |
|
336 | """Subclasses must override to render result. Since this method will | |
312 |
twisted.internet.defer.Deferred's callback, |
|
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 |
|
345 | """Subclasses must override to render the failure. Since this method | |
320 |
twisted.internet.defer.Deferred's callback, |
|
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, |
|
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( |
|
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({' |
|
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 |
|
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), |
|
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 |
|
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 |
|
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 |
|
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 |
|
850 | """An EngineService subclass that defers execute commands to a separate | |
|
851 | thread. | |||
851 |
|
852 | |||
852 |
ThreadedEngineService uses twisted.internet.threads.deferToThread to |
|
853 | ThreadedEngineService uses twisted.internet.threads.deferToThread to | |
853 |
requests to a separate thread. GUI frontends may want to |
|
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`__. |
|
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