##// END OF EJS Templates
added encoding and objc.signature to executeFileAtPath_encoding_
Barry Wark <barrywarkatgmaildotcom> -
Show More
@@ -1,504 +1,505 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3
3
4 """PyObjC classes to provide a Cocoa frontend to the
4 """PyObjC classes to provide a Cocoa frontend to the
5 IPython.kernel.engineservice.IEngineBase.
5 IPython.kernel.engineservice.IEngineBase.
6
6
7 To add an IPython interpreter to a cocoa app, instantiate an
7 To add an IPython interpreter to a cocoa app, instantiate an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 NSTextView instance in your UI. That's it.
9 NSTextView instance in your UI. That's it.
10
10
11 Author: Barry Wark
11 Author: Barry Wark
12 """
12 """
13
13
14 __docformat__ = "restructuredtext en"
14 __docformat__ = "restructuredtext en"
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008 The IPython Development Team
17 # Copyright (C) 2008 The IPython Development Team
18 #
18 #
19 # 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
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import sys
27 import sys
28 import objc
28 import objc
29 import uuid
29 import uuid
30
30
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
32 NSLog, NSNotificationCenter, NSMakeRange,\
33 NSLocalizedString, NSIntersectionRange,\
33 NSLocalizedString, NSIntersectionRange,\
34 NSString, NSAutoreleasePool
34 NSString, NSAutoreleasePool
35
35
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 NSTextView, NSRulerView, NSVerticalRuler
37 NSTextView, NSRulerView, NSVerticalRuler
38
38
39 from pprint import saferepr
39 from pprint import saferepr
40
40
41 import IPython
41 import IPython
42 from IPython.kernel.engineservice import ThreadedEngineService
42 from IPython.kernel.engineservice import ThreadedEngineService
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
43 from IPython.frontend.frontendbase import AsyncFrontEndBase
44
44
45 from twisted.internet.threads import blockingCallFromThread
45 from twisted.internet.threads import blockingCallFromThread
46 from twisted.python.failure import Failure
46 from twisted.python.failure import Failure
47
47
48 #------------------------------------------------------------------------------
48 #------------------------------------------------------------------------------
49 # Classes to implement the Cocoa frontend
49 # Classes to implement the Cocoa frontend
50 #------------------------------------------------------------------------------
50 #------------------------------------------------------------------------------
51
51
52 # TODO:
52 # TODO:
53 # 1. use MultiEngineClient and out-of-process engine rather than
53 # 1. use MultiEngineClient and out-of-process engine rather than
54 # ThreadedEngineService?
54 # ThreadedEngineService?
55 # 2. integrate Xgrid launching of engines
55 # 2. integrate Xgrid launching of engines
56
56
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 """Wrap all blocks in an NSAutoreleasePool"""
58 """Wrap all blocks in an NSAutoreleasePool"""
59
59
60 def wrapped_execute(self, msg, lines):
60 def wrapped_execute(self, msg, lines):
61 """wrapped_execute"""
61 """wrapped_execute"""
62 try:
62 try:
63 p = NSAutoreleasePool.alloc().init()
63 p = NSAutoreleasePool.alloc().init()
64 result = self.shell.execute(lines)
64 result = self.shell.execute(lines)
65 except Exception,e:
65 except Exception,e:
66 # This gives the following:
66 # This gives the following:
67 # et=exception class
67 # et=exception class
68 # ev=exception class instance
68 # ev=exception class instance
69 # tb=traceback object
69 # tb=traceback object
70 et,ev,tb = sys.exc_info()
70 et,ev,tb = sys.exc_info()
71 # This call adds attributes to the exception value
71 # This call adds attributes to the exception value
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
72 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
73 # Add another attribute
73 # Add another attribute
74
74
75 # Create a new exception with the new attributes
75 # Create a new exception with the new attributes
76 e = et(ev._ipython_traceback_text)
76 e = et(ev._ipython_traceback_text)
77 e._ipython_engine_info = msg
77 e._ipython_engine_info = msg
78
78
79 # Re-raise
79 # Re-raise
80 raise e
80 raise e
81 finally:
81 finally:
82 p.drain()
82 p.drain()
83
83
84 return result
84 return result
85
85
86 def execute(self, lines):
86 def execute(self, lines):
87 # Only import this if we are going to use this class
87 # Only import this if we are going to use this class
88 from twisted.internet import threads
88 from twisted.internet import threads
89
89
90 msg = {'engineid':self.id,
90 msg = {'engineid':self.id,
91 'method':'execute',
91 'method':'execute',
92 'args':[lines]}
92 'args':[lines]}
93
93
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
94 d = threads.deferToThread(self.wrapped_execute, msg, lines)
95 d.addCallback(self.addIDToResult)
95 d.addCallback(self.addIDToResult)
96 return d
96 return d
97
97
98
98
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
99 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
100 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
101 waitingForEngine = objc.ivar().bool()
101 waitingForEngine = objc.ivar().bool()
102 textView = objc.IBOutlet()
102 textView = objc.IBOutlet()
103
103
104 def init(self):
104 def init(self):
105 self = super(IPythonCocoaController, self).init()
105 self = super(IPythonCocoaController, self).init()
106 AsyncFrontEndBase.__init__(self,
106 AsyncFrontEndBase.__init__(self,
107 engine=AutoreleasePoolWrappedThreadedEngineService())
107 engine=AutoreleasePoolWrappedThreadedEngineService())
108 if(self != None):
108 if(self != None):
109 self._common_init()
109 self._common_init()
110
110
111 return self
111 return self
112
112
113 def _common_init(self):
113 def _common_init(self):
114 """_common_init"""
114 """_common_init"""
115
115
116 self.userNS = NSMutableDictionary.dictionary()
116 self.userNS = NSMutableDictionary.dictionary()
117 self.waitingForEngine = False
117 self.waitingForEngine = False
118
118
119 self.lines = {}
119 self.lines = {}
120 self.tabSpaces = 4
120 self.tabSpaces = 4
121 self.tabUsesSpaces = True
121 self.tabUsesSpaces = True
122 self.currentBlockID = self.next_block_ID()
122 self.currentBlockID = self.next_block_ID()
123 self.blockRanges = {} # blockID=>NSRange
123 self.blockRanges = {} # blockID=>NSRange
124
124
125
125
126 def awakeFromNib(self):
126 def awakeFromNib(self):
127 """awakeFromNib"""
127 """awakeFromNib"""
128
128
129 self._common_init()
129 self._common_init()
130
130
131 # Start the IPython engine
131 # Start the IPython engine
132 self.engine.startService()
132 self.engine.startService()
133 NSLog('IPython engine started')
133 NSLog('IPython engine started')
134
134
135 # Register for app termination
135 # Register for app termination
136 nc = NSNotificationCenter.defaultCenter()
136 nc = NSNotificationCenter.defaultCenter()
137 nc.addObserver_selector_name_object_(
137 nc.addObserver_selector_name_object_(
138 self,
138 self,
139 'appWillTerminate:',
139 'appWillTerminate:',
140 NSApplicationWillTerminateNotification,
140 NSApplicationWillTerminateNotification,
141 None)
141 None)
142
142
143 self.textView.setDelegate_(self)
143 self.textView.setDelegate_(self)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
144 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
145 r = NSRulerView.alloc().initWithScrollView_orientation_(
146 self.textView.enclosingScrollView(),
146 self.textView.enclosingScrollView(),
147 NSVerticalRuler)
147 NSVerticalRuler)
148 self.verticalRulerView = r
148 self.verticalRulerView = r
149 self.verticalRulerView.setClientView_(self.textView)
149 self.verticalRulerView.setClientView_(self.textView)
150 self._start_cli_banner()
150 self._start_cli_banner()
151
151
152
152
153 def appWillTerminate_(self, notification):
153 def appWillTerminate_(self, notification):
154 """appWillTerminate"""
154 """appWillTerminate"""
155
155
156 self.engine.stopService()
156 self.engine.stopService()
157
157
158
158
159 def complete(self, token):
159 def complete(self, token):
160 """Complete token in engine's user_ns
160 """Complete token in engine's user_ns
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 token : string
164 token : string
165
165
166 Result
166 Result
167 ------
167 ------
168 Deferred result of
168 Deferred result of
169 IPython.kernel.engineservice.IEngineBase.complete
169 IPython.kernel.engineservice.IEngineBase.complete
170 """
170 """
171
171
172 return self.engine.complete(token)
172 return self.engine.complete(token)
173
173
174
174
175 def execute(self, block, blockID=None):
175 def execute(self, block, blockID=None):
176 self.waitingForEngine = True
176 self.waitingForEngine = True
177 self.willChangeValueForKey_('commandHistory')
177 self.willChangeValueForKey_('commandHistory')
178 d = super(IPythonCocoaController, self).execute(block,
178 d = super(IPythonCocoaController, self).execute(block,
179 blockID)
179 blockID)
180 d.addBoth(self._engine_done)
180 d.addBoth(self._engine_done)
181 d.addCallback(self._update_user_ns)
181 d.addCallback(self._update_user_ns)
182
182
183 return d
183 return d
184
184
185
185
186 def push_(self, namespace):
186 def push_(self, namespace):
187 """Push dictionary of key=>values to python namespace"""
187 """Push dictionary of key=>values to python namespace"""
188
188
189 self.waitingForEngine = True
189 self.waitingForEngine = True
190 self.willChangeValueForKey_('commandHistory')
190 self.willChangeValueForKey_('commandHistory')
191 d = self.engine.push(namespace)
191 d = self.engine.push(namespace)
192 d.addBoth(self._engine_done)
192 d.addBoth(self._engine_done)
193 d.addCallback(self._update_user_ns)
193 d.addCallback(self._update_user_ns)
194
194
195
195
196 def pull_(self, keys):
196 def pull_(self, keys):
197 """Pull keys from python namespace"""
197 """Pull keys from python namespace"""
198
198
199 self.waitingForEngine = True
199 self.waitingForEngine = True
200 result = blockingCallFromThread(self.engine.pull, keys)
200 result = blockingCallFromThread(self.engine.pull, keys)
201 self.waitingForEngine = False
201 self.waitingForEngine = False
202
202
203 def executeFileAtPath_(self, path):
203 @objc.signature('v@:@I')
204 def executeFileAtPath_encoding_(self, path, encoding):
204 """Execute file at path in an empty namespace. Update the engine
205 """Execute file at path in an empty namespace. Update the engine
205 user_ns with the resulting locals."""
206 user_ns with the resulting locals."""
206
207
207 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
208 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
208 path,
209 path,
209 NSString.defaultCStringEncoding(),
210 encoding,
210 None)
211 None)
211 self.engine.execute(lines)
212 self.engine.execute(lines)
212
213
213
214
214 def _engine_done(self, x):
215 def _engine_done(self, x):
215 self.waitingForEngine = False
216 self.waitingForEngine = False
216 self.didChangeValueForKey_('commandHistory')
217 self.didChangeValueForKey_('commandHistory')
217 return x
218 return x
218
219
219 def _update_user_ns(self, result):
220 def _update_user_ns(self, result):
220 """Update self.userNS from self.engine's namespace"""
221 """Update self.userNS from self.engine's namespace"""
221 d = self.engine.keys()
222 d = self.engine.keys()
222 d.addCallback(self._get_engine_namespace_values_for_keys)
223 d.addCallback(self._get_engine_namespace_values_for_keys)
223
224
224 return result
225 return result
225
226
226
227
227 def _get_engine_namespace_values_for_keys(self, keys):
228 def _get_engine_namespace_values_for_keys(self, keys):
228 d = self.engine.pull(keys)
229 d = self.engine.pull(keys)
229 d.addCallback(self._store_engine_namespace_values, keys=keys)
230 d.addCallback(self._store_engine_namespace_values, keys=keys)
230
231
231
232
232 def _store_engine_namespace_values(self, values, keys=[]):
233 def _store_engine_namespace_values(self, values, keys=[]):
233 assert(len(values) == len(keys))
234 assert(len(values) == len(keys))
234 self.willChangeValueForKey_('userNS')
235 self.willChangeValueForKey_('userNS')
235 for (k,v) in zip(keys,values):
236 for (k,v) in zip(keys,values):
236 self.userNS[k] = saferepr(v)
237 self.userNS[k] = saferepr(v)
237 self.didChangeValueForKey_('userNS')
238 self.didChangeValueForKey_('userNS')
238
239
239
240
240 def update_cell_prompt(self, result, blockID=None):
241 def update_cell_prompt(self, result, blockID=None):
241 if(isinstance(result, Failure)):
242 if(isinstance(result, Failure)):
242 self.insert_text(self.input_prompt(),
243 self.insert_text(self.input_prompt(),
243 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
244 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
244 scrollToVisible=False
245 scrollToVisible=False
245 )
246 )
246 else:
247 else:
247 self.insert_text(self.input_prompt(number=result['number']),
248 self.insert_text(self.input_prompt(number=result['number']),
248 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 textRange=NSMakeRange(self.blockRanges[blockID].location,0),
249 scrollToVisible=False
250 scrollToVisible=False
250 )
251 )
251
252
252 return result
253 return result
253
254
254
255
255 def render_result(self, result):
256 def render_result(self, result):
256 blockID = result['blockID']
257 blockID = result['blockID']
257 inputRange = self.blockRanges[blockID]
258 inputRange = self.blockRanges[blockID]
258 del self.blockRanges[blockID]
259 del self.blockRanges[blockID]
259
260
260 #print inputRange,self.current_block_range()
261 #print inputRange,self.current_block_range()
261 self.insert_text('\n' +
262 self.insert_text('\n' +
262 self.output_prompt(number=result['number']) +
263 self.output_prompt(number=result['number']) +
263 result.get('display',{}).get('pprint','') +
264 result.get('display',{}).get('pprint','') +
264 '\n\n',
265 '\n\n',
265 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 textRange=NSMakeRange(inputRange.location+inputRange.length,
266 0))
267 0))
267 return result
268 return result
268
269
269
270
270 def render_error(self, failure):
271 def render_error(self, failure):
271 self.insert_text('\n' +
272 self.insert_text('\n' +
272 self.output_prompt() +
273 self.output_prompt() +
273 '\n' +
274 '\n' +
274 failure.getErrorMessage() +
275 failure.getErrorMessage() +
275 '\n\n')
276 '\n\n')
276 self.start_new_block()
277 self.start_new_block()
277 return failure
278 return failure
278
279
279
280
280 def _start_cli_banner(self):
281 def _start_cli_banner(self):
281 """Print banner"""
282 """Print banner"""
282
283
283 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
284 IPython.__version__
285 IPython.__version__
285
286
286 self.insert_text(banner + '\n\n')
287 self.insert_text(banner + '\n\n')
287
288
288
289
289 def start_new_block(self):
290 def start_new_block(self):
290 """"""
291 """"""
291
292
292 self.currentBlockID = self.next_block_ID()
293 self.currentBlockID = self.next_block_ID()
293
294
294
295
295
296
296 def next_block_ID(self):
297 def next_block_ID(self):
297
298
298 return uuid.uuid4()
299 return uuid.uuid4()
299
300
300 def current_block_range(self):
301 def current_block_range(self):
301 return self.blockRanges.get(self.currentBlockID,
302 return self.blockRanges.get(self.currentBlockID,
302 NSMakeRange(self.textView.textStorage().length(),
303 NSMakeRange(self.textView.textStorage().length(),
303 0))
304 0))
304
305
305 def current_block(self):
306 def current_block(self):
306 """The current block's text"""
307 """The current block's text"""
307
308
308 return self.text_for_range(self.current_block_range())
309 return self.text_for_range(self.current_block_range())
309
310
310 def text_for_range(self, textRange):
311 def text_for_range(self, textRange):
311 """text_for_range"""
312 """text_for_range"""
312
313
313 ts = self.textView.textStorage()
314 ts = self.textView.textStorage()
314 return ts.string().substringWithRange_(textRange)
315 return ts.string().substringWithRange_(textRange)
315
316
316 def current_line(self):
317 def current_line(self):
317 block = self.text_for_range(self.current_block_range())
318 block = self.text_for_range(self.current_block_range())
318 block = block.split('\n')
319 block = block.split('\n')
319 return block[-1]
320 return block[-1]
320
321
321
322
322 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
323 """Insert text into textView at textRange, updating blockRanges
324 """Insert text into textView at textRange, updating blockRanges
324 as necessary
325 as necessary
325 """
326 """
326
327
327 if(textRange == None):
328 if(textRange == None):
328 #range for end of text
329 #range for end of text
329 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
330
331
331 for r in self.blockRanges.itervalues():
332 for r in self.blockRanges.itervalues():
332 intersection = NSIntersectionRange(r,textRange)
333 intersection = NSIntersectionRange(r,textRange)
333 if(intersection.length == 0): #ranges don't intersect
334 if(intersection.length == 0): #ranges don't intersect
334 if r.location >= textRange.location:
335 if r.location >= textRange.location:
335 r.location += len(string)
336 r.location += len(string)
336 else: #ranges intersect
337 else: #ranges intersect
337 if(r.location <= textRange.location):
338 if(r.location <= textRange.location):
338 assert(intersection.length == textRange.length)
339 assert(intersection.length == textRange.length)
339 r.length += textRange.length
340 r.length += textRange.length
340 else:
341 else:
341 r.location += intersection.length
342 r.location += intersection.length
342
343
343 self.textView.replaceCharactersInRange_withString_(
344 self.textView.replaceCharactersInRange_withString_(
344 textRange, string)
345 textRange, string)
345 self.textView.setSelectedRange_(
346 self.textView.setSelectedRange_(
346 NSMakeRange(textRange.location+len(string), 0))
347 NSMakeRange(textRange.location+len(string), 0))
347 if(scrollToVisible):
348 if(scrollToVisible):
348 self.textView.scrollRangeToVisible_(textRange)
349 self.textView.scrollRangeToVisible_(textRange)
349
350
350
351
351
352
352
353
353 def replace_current_block_with_string(self, textView, string):
354 def replace_current_block_with_string(self, textView, string):
354 textView.replaceCharactersInRange_withString_(
355 textView.replaceCharactersInRange_withString_(
355 self.current_block_range(),
356 self.current_block_range(),
356 string)
357 string)
357 self.current_block_range().length = len(string)
358 self.current_block_range().length = len(string)
358 r = NSMakeRange(textView.textStorage().length(), 0)
359 r = NSMakeRange(textView.textStorage().length(), 0)
359 textView.scrollRangeToVisible_(r)
360 textView.scrollRangeToVisible_(r)
360 textView.setSelectedRange_(r)
361 textView.setSelectedRange_(r)
361
362
362
363
363 def current_indent_string(self):
364 def current_indent_string(self):
364 """returns string for indent or None if no indent"""
365 """returns string for indent or None if no indent"""
365
366
366 return self._indent_for_block(self.current_block())
367 return self._indent_for_block(self.current_block())
367
368
368
369
369 def _indent_for_block(self, block):
370 def _indent_for_block(self, block):
370 lines = block.split('\n')
371 lines = block.split('\n')
371 if(len(lines) > 1):
372 if(len(lines) > 1):
372 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
373 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
373 if(currentIndent == 0):
374 if(currentIndent == 0):
374 currentIndent = self.tabSpaces
375 currentIndent = self.tabSpaces
375
376
376 if(self.tabUsesSpaces):
377 if(self.tabUsesSpaces):
377 result = ' ' * currentIndent
378 result = ' ' * currentIndent
378 else:
379 else:
379 result = '\t' * (currentIndent/self.tabSpaces)
380 result = '\t' * (currentIndent/self.tabSpaces)
380 else:
381 else:
381 result = None
382 result = None
382
383
383 return result
384 return result
384
385
385
386
386 # NSTextView delegate methods...
387 # NSTextView delegate methods...
387 def textView_doCommandBySelector_(self, textView, selector):
388 def textView_doCommandBySelector_(self, textView, selector):
388 assert(textView == self.textView)
389 assert(textView == self.textView)
389 NSLog("textView_doCommandBySelector_: "+selector)
390 NSLog("textView_doCommandBySelector_: "+selector)
390
391
391
392
392 if(selector == 'insertNewline:'):
393 if(selector == 'insertNewline:'):
393 indent = self.current_indent_string()
394 indent = self.current_indent_string()
394 if(indent):
395 if(indent):
395 line = indent + self.current_line()
396 line = indent + self.current_line()
396 else:
397 else:
397 line = self.current_line()
398 line = self.current_line()
398
399
399 if(self.is_complete(self.current_block())):
400 if(self.is_complete(self.current_block())):
400 self.execute(self.current_block(),
401 self.execute(self.current_block(),
401 blockID=self.currentBlockID)
402 blockID=self.currentBlockID)
402 self.start_new_block()
403 self.start_new_block()
403
404
404 return True
405 return True
405
406
406 return False
407 return False
407
408
408 elif(selector == 'moveUp:'):
409 elif(selector == 'moveUp:'):
409 prevBlock = self.get_history_previous(self.current_block())
410 prevBlock = self.get_history_previous(self.current_block())
410 if(prevBlock != None):
411 if(prevBlock != None):
411 self.replace_current_block_with_string(textView, prevBlock)
412 self.replace_current_block_with_string(textView, prevBlock)
412 else:
413 else:
413 NSBeep()
414 NSBeep()
414 return True
415 return True
415
416
416 elif(selector == 'moveDown:'):
417 elif(selector == 'moveDown:'):
417 nextBlock = self.get_history_next()
418 nextBlock = self.get_history_next()
418 if(nextBlock != None):
419 if(nextBlock != None):
419 self.replace_current_block_with_string(textView, nextBlock)
420 self.replace_current_block_with_string(textView, nextBlock)
420 else:
421 else:
421 NSBeep()
422 NSBeep()
422 return True
423 return True
423
424
424 elif(selector == 'moveToBeginningOfParagraph:'):
425 elif(selector == 'moveToBeginningOfParagraph:'):
425 textView.setSelectedRange_(NSMakeRange(
426 textView.setSelectedRange_(NSMakeRange(
426 self.current_block_range().location,
427 self.current_block_range().location,
427 0))
428 0))
428 return True
429 return True
429 elif(selector == 'moveToEndOfParagraph:'):
430 elif(selector == 'moveToEndOfParagraph:'):
430 textView.setSelectedRange_(NSMakeRange(
431 textView.setSelectedRange_(NSMakeRange(
431 self.current_block_range().location + \
432 self.current_block_range().location + \
432 self.current_block_range().length, 0))
433 self.current_block_range().length, 0))
433 return True
434 return True
434 elif(selector == 'deleteToEndOfParagraph:'):
435 elif(selector == 'deleteToEndOfParagraph:'):
435 if(textView.selectedRange().location <= \
436 if(textView.selectedRange().location <= \
436 self.current_block_range().location):
437 self.current_block_range().location):
437 # Intersect the selected range with the current line range
438 # Intersect the selected range with the current line range
438 if(self.current_block_range().length < 0):
439 if(self.current_block_range().length < 0):
439 self.blockRanges[self.currentBlockID].length = 0
440 self.blockRanges[self.currentBlockID].length = 0
440
441
441 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 r = NSIntersectionRange(textView.rangesForUserTextChange()[0],
442 self.current_block_range())
443 self.current_block_range())
443
444
444 if(r.length > 0): #no intersection
445 if(r.length > 0): #no intersection
445 textView.setSelectedRange_(r)
446 textView.setSelectedRange_(r)
446
447
447 return False # don't actually handle the delete
448 return False # don't actually handle the delete
448
449
449 elif(selector == 'insertTab:'):
450 elif(selector == 'insertTab:'):
450 if(len(self.current_line().strip()) == 0): #only white space
451 if(len(self.current_line().strip()) == 0): #only white space
451 return False
452 return False
452 else:
453 else:
453 self.textView.complete_(self)
454 self.textView.complete_(self)
454 return True
455 return True
455
456
456 elif(selector == 'deleteBackward:'):
457 elif(selector == 'deleteBackward:'):
457 #if we're at the beginning of the current block, ignore
458 #if we're at the beginning of the current block, ignore
458 if(textView.selectedRange().location == \
459 if(textView.selectedRange().location == \
459 self.current_block_range().location):
460 self.current_block_range().location):
460 return True
461 return True
461 else:
462 else:
462 self.current_block_range().length-=1
463 self.current_block_range().length-=1
463 return False
464 return False
464 return False
465 return False
465
466
466
467
467 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 def textView_shouldChangeTextInRanges_replacementStrings_(self,
468 textView, ranges, replacementStrings):
469 textView, ranges, replacementStrings):
469 """
470 """
470 Delegate method for NSTextView.
471 Delegate method for NSTextView.
471
472
472 Refuse change text in ranges not at end, but make those changes at
473 Refuse change text in ranges not at end, but make those changes at
473 end.
474 end.
474 """
475 """
475
476
476 assert(len(ranges) == len(replacementStrings))
477 assert(len(ranges) == len(replacementStrings))
477 allow = True
478 allow = True
478 for r,s in zip(ranges, replacementStrings):
479 for r,s in zip(ranges, replacementStrings):
479 r = r.rangeValue()
480 r = r.rangeValue()
480 if(textView.textStorage().length() > 0 and
481 if(textView.textStorage().length() > 0 and
481 r.location < self.current_block_range().location):
482 r.location < self.current_block_range().location):
482 self.insert_text(s)
483 self.insert_text(s)
483 allow = False
484 allow = False
484
485
485
486
486 self.blockRanges.setdefault(self.currentBlockID,
487 self.blockRanges.setdefault(self.currentBlockID,
487 self.current_block_range()).length +=\
488 self.current_block_range()).length +=\
488 len(s)
489 len(s)
489
490
490 return allow
491 return allow
491
492
492 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
493 textView, words, charRange, index):
494 textView, words, charRange, index):
494 try:
495 try:
495 ts = textView.textStorage()
496 ts = textView.textStorage()
496 token = ts.string().substringWithRange_(charRange)
497 token = ts.string().substringWithRange_(charRange)
497 completions = blockingCallFromThread(self.complete, token)
498 completions = blockingCallFromThread(self.complete, token)
498 except:
499 except:
499 completions = objc.nil
500 completions = objc.nil
500 NSBeep()
501 NSBeep()
501
502
502 return (completions,0)
503 return (completions,0)
503
504
504
505
General Comments 0
You need to be logged in to leave comments. Login now