##// END OF EJS Templates
branch to try non-textview
Barry Wark -
Show More
@@ -1,548 +1,560 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 = super(AutoreleasePoolWrappedThreadedEngineService,
64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 self).wrapped_execute(msg, lines)
65 self).wrapped_execute(msg, lines)
66 finally:
66 finally:
67 p.drain()
67 p.drain()
68
68
69 return result
69 return result
70
70
71
71
72
72
73 class Cell(NSObject):
74 """
75 Representation of the prompts, input and output of a cell in the
76 frontend
77 """
78
79 blockNumber = objc.ivar().unsigned_long()
80 blockID = objc.ivar()
81 inputBlock = objc.ivar()
82 output = objc.ivar()
83
84
85
73 class CellBlock(object):
86 class CellBlock(object):
74 """
87 """
75 Storage for information about text ranges relating to a single cell
88 Storage for information about text ranges relating to a single cell
76 """
89 """
77
90
78
91
79 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
80 outputRange=None):
93 outputRange=None):
81 super(CellBlock, self).__init__()
94 super(CellBlock, self).__init__()
82 self.inputPromptRange = inputPromptRange
95 self.inputPromptRange = inputPromptRange
83 self.inputRange = inputRange
96 self.inputRange = inputRange
84 self.outputPromptRange = outputPromptRange
97 self.outputPromptRange = outputPromptRange
85 self.outputRange = outputRange
98 self.outputRange = outputRange
86
99
87 def update_ranges_for_insertion(self, text, textRange):
100 def update_ranges_for_insertion(self, text, textRange):
88 """Update ranges for text insertion at textRange"""
101 """Update ranges for text insertion at textRange"""
89
102
90 for r in [self.inputPromptRange,self.inputRange,
103 for r in [self.inputPromptRange,self.inputRange,
91 self.outputPromptRange, self.outputRange]:
104 self.outputPromptRange, self.outputRange]:
92 if(r == None):
105 if(r == None):
93 continue
106 continue
94 intersection = NSIntersectionRange(r,textRange)
107 intersection = NSIntersectionRange(r,textRange)
95 if(intersection.length == 0): #ranges don't intersect
108 if(intersection.length == 0): #ranges don't intersect
96 if r.location >= textRange.location:
109 if r.location >= textRange.location:
97 r.location += len(text)
110 r.location += len(text)
98 else: #ranges intersect
111 else: #ranges intersect
99 if(r.location > textRange.location):
112 if(r.location > textRange.location):
100 offset = len(text) - intersection.length
113 offset = len(text) - intersection.length
101 r.length -= offset
114 r.length -= offset
102 r.location += offset
115 r.location += offset
103 elif(r.location == textRange.location):
116 elif(r.location == textRange.location):
104 r.length += len(text) - intersection.length
117 r.length += len(text) - intersection.length
105 else:
118 else:
106 r.length -= intersection.length
119 r.length -= intersection.length
107
120
108
121
109 def update_ranges_for_deletion(self, textRange):
122 def update_ranges_for_deletion(self, textRange):
110 """Update ranges for text deletion at textRange"""
123 """Update ranges for text deletion at textRange"""
111
124
112 for r in [self.inputPromptRange,self.inputRange,
125 for r in [self.inputPromptRange,self.inputRange,
113 self.outputPromptRange, self.outputRange]:
126 self.outputPromptRange, self.outputRange]:
114 if(r==None):
127 if(r==None):
115 continue
128 continue
116 intersection = NSIntersectionRange(r, textRange)
129 intersection = NSIntersectionRange(r, textRange)
117 if(intersection.length == 0): #ranges don't intersect
130 if(intersection.length == 0): #ranges don't intersect
118 if r.location >= textRange.location:
131 if r.location >= textRange.location:
119 r.location -= textRange.length
132 r.location -= textRange.length
120 else: #ranges intersect
133 else: #ranges intersect
121 if(r.location > textRange.location):
134 if(r.location > textRange.location):
122 offset = intersection.length
135 offset = intersection.length
123 r.length -= offset
136 r.length -= offset
124 r.location += offset
137 r.location += offset
125 elif(r.location == textRange.location):
138 elif(r.location == textRange.location):
126 r.length += intersection.length
139 r.length += intersection.length
127 else:
140 else:
128 r.length -= intersection.length
141 r.length -= intersection.length
129
142
130 def __repr__(self):
143 def __repr__(self):
131 return 'CellBlock('+ str((self.inputPromptRange,
144 return 'CellBlock('+ str((self.inputPromptRange,
132 self.inputRange,
145 self.inputRange,
133 self.outputPromptRange,
146 self.outputPromptRange,
134 self.outputRange)) + ')'
147 self.outputRange)) + ')'
135
148
136
149
137
150
138
151
139 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
140 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
141 waitingForEngine = objc.ivar().bool()
154 waitingForEngine = objc.ivar().bool()
142 textView = objc.IBOutlet()
155 textView = objc.IBOutlet()
143
156
144 def init(self):
157 def init(self):
145 self = super(IPythonCocoaController, self).init()
158 self = super(IPythonCocoaController, self).init()
146 AsyncFrontEndBase.__init__(self,
159 AsyncFrontEndBase.__init__(self,
147 engine=AutoreleasePoolWrappedThreadedEngineService())
160 engine=AutoreleasePoolWrappedThreadedEngineService())
148 if(self != None):
161 if(self != None):
149 self._common_init()
162 self._common_init()
150
163
151 return self
164 return self
152
165
153 def _common_init(self):
166 def _common_init(self):
154 """_common_init"""
167 """_common_init"""
155
168
156 self.userNS = NSMutableDictionary.dictionary()
169 self.userNS = NSMutableDictionary.dictionary()
157 self.waitingForEngine = False
170 self.waitingForEngine = False
158
171
159 self.lines = {}
172 self.lines = {}
160 self.tabSpaces = 4
173 self.tabSpaces = 4
161 self.tabUsesSpaces = True
174 self.tabUsesSpaces = True
162 self.currentBlockID = self.next_block_ID()
175 self.currentBlockID = self.next_block_ID()
163 self.blockRanges = {} # blockID=>CellBlock
176 self.blockRanges = {} # blockID=>CellBlock
164
177
165
178
166 def awakeFromNib(self):
179 def awakeFromNib(self):
167 """awakeFromNib"""
180 """awakeFromNib"""
168
181
169 self._common_init()
182 self._common_init()
170
183
171 # Start the IPython engine
184 # Start the IPython engine
172 self.engine.startService()
185 self.engine.startService()
173 NSLog('IPython engine started')
186 NSLog('IPython engine started')
174
187
175 # Register for app termination
188 # Register for app termination
176 nc = NSNotificationCenter.defaultCenter()
189 nc = NSNotificationCenter.defaultCenter()
177 nc.addObserver_selector_name_object_(
190 nc.addObserver_selector_name_object_(
178 self,
191 self,
179 'appWillTerminate:',
192 'appWillTerminate:',
180 NSApplicationWillTerminateNotification,
193 NSApplicationWillTerminateNotification,
181 None)
194 None)
182
195
183 self.textView.setDelegate_(self)
196 self.textView.setDelegate_(self)
184 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
185 r = NSRulerView.alloc().initWithScrollView_orientation_(
198 r = NSRulerView.alloc().initWithScrollView_orientation_(
186 self.textView.enclosingScrollView(),
199 self.textView.enclosingScrollView(),
187 NSVerticalRuler)
200 NSVerticalRuler)
188 self.verticalRulerView = r
201 self.verticalRulerView = r
189 self.verticalRulerView.setClientView_(self.textView)
202 self.verticalRulerView.setClientView_(self.textView)
190 self._start_cli_banner()
203 self._start_cli_banner()
191 self.start_new_block()
204 self.start_new_block()
192
205
193
206
194 def appWillTerminate_(self, notification):
207 def appWillTerminate_(self, notification):
195 """appWillTerminate"""
208 """appWillTerminate"""
196
209
197 self.engine.stopService()
210 self.engine.stopService()
198
211
199
212
200 def complete(self, token):
213 def complete(self, token):
201 """Complete token in engine's user_ns
214 """Complete token in engine's user_ns
202
215
203 Parameters
216 Parameters
204 ----------
217 ----------
205 token : string
218 token : string
206
219
207 Result
220 Result
208 ------
221 ------
209 Deferred result of
222 Deferred result of
210 IPython.kernel.engineservice.IEngineBase.complete
223 IPython.kernel.engineservice.IEngineBase.complete
211 """
224 """
212
225
213 return self.engine.complete(token)
226 return self.engine.complete(token)
214
227
215
228
216 def execute(self, block, blockID=None):
229 def execute(self, block, blockID=None):
217 self.waitingForEngine = True
230 self.waitingForEngine = True
218 self.willChangeValueForKey_('commandHistory')
231 self.willChangeValueForKey_('commandHistory')
219 d = super(IPythonCocoaController, self).execute(block,
232 d = super(IPythonCocoaController, self).execute(block,
220 blockID)
233 blockID)
221 d.addBoth(self._engine_done)
234 d.addBoth(self._engine_done)
222 d.addCallback(self._update_user_ns)
235 d.addCallback(self._update_user_ns)
223
236
224 return d
237 return d
225
238
226
239
227 def push_(self, namespace):
240 def push_(self, namespace):
228 """Push dictionary of key=>values to python namespace"""
241 """Push dictionary of key=>values to python namespace"""
229
242
230 self.waitingForEngine = True
243 self.waitingForEngine = True
231 self.willChangeValueForKey_('commandHistory')
244 self.willChangeValueForKey_('commandHistory')
232 d = self.engine.push(namespace)
245 d = self.engine.push(namespace)
233 d.addBoth(self._engine_done)
246 d.addBoth(self._engine_done)
234 d.addCallback(self._update_user_ns)
247 d.addCallback(self._update_user_ns)
235
248
236
249
237 def pull_(self, keys):
250 def pull_(self, keys):
238 """Pull keys from python namespace"""
251 """Pull keys from python namespace"""
239
252
240 self.waitingForEngine = True
253 self.waitingForEngine = True
241 result = blockingCallFromThread(self.engine.pull, keys)
254 result = blockingCallFromThread(self.engine.pull, keys)
242 self.waitingForEngine = False
255 self.waitingForEngine = False
243
256
244 @objc.signature('v@:@I')
257 @objc.signature('v@:@I')
245 def executeFileAtPath_encoding_(self, path, encoding):
258 def executeFileAtPath_encoding_(self, path, encoding):
246 """Execute file at path in an empty namespace. Update the engine
259 """Execute file at path in an empty namespace. Update the engine
247 user_ns with the resulting locals."""
260 user_ns with the resulting locals."""
248
261
249 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
250 path,
263 path,
251 encoding,
264 encoding,
252 None)
265 None)
253 self.engine.execute(lines)
266 self.engine.execute(lines)
254
267
255
268
256 def _engine_done(self, x):
269 def _engine_done(self, x):
257 self.waitingForEngine = False
270 self.waitingForEngine = False
258 self.didChangeValueForKey_('commandHistory')
271 self.didChangeValueForKey_('commandHistory')
259 return x
272 return x
260
273
261 def _update_user_ns(self, result):
274 def _update_user_ns(self, result):
262 """Update self.userNS from self.engine's namespace"""
275 """Update self.userNS from self.engine's namespace"""
263 d = self.engine.keys()
276 d = self.engine.keys()
264 d.addCallback(self._get_engine_namespace_values_for_keys)
277 d.addCallback(self._get_engine_namespace_values_for_keys)
265
278
266 return result
279 return result
267
280
268
281
269 def _get_engine_namespace_values_for_keys(self, keys):
282 def _get_engine_namespace_values_for_keys(self, keys):
270 d = self.engine.pull(keys)
283 d = self.engine.pull(keys)
271 d.addCallback(self._store_engine_namespace_values, keys=keys)
284 d.addCallback(self._store_engine_namespace_values, keys=keys)
272
285
273
286
274 def _store_engine_namespace_values(self, values, keys=[]):
287 def _store_engine_namespace_values(self, values, keys=[]):
275 assert(len(values) == len(keys))
288 assert(len(values) == len(keys))
276 self.willChangeValueForKey_('userNS')
289 self.willChangeValueForKey_('userNS')
277 for (k,v) in zip(keys,values):
290 for (k,v) in zip(keys,values):
278 self.userNS[k] = saferepr(v)
291 self.userNS[k] = saferepr(v)
279 self.didChangeValueForKey_('userNS')
292 self.didChangeValueForKey_('userNS')
280
293
281
294
282 def update_cell_prompt(self, result, blockID=None):
295 def update_cell_prompt(self, result, blockID=None):
283 print self.blockRanges
296 print self.blockRanges
284 if(isinstance(result, Failure)):
297 if(isinstance(result, Failure)):
285 prompt = self.input_prompt()
298 prompt = self.input_prompt()
286
299
287 else:
300 else:
288 prompt = self.input_prompt(number=result['number'])
301 prompt = self.input_prompt(number=result['number'])
289
302
290 r = self.blockRanges[blockID].inputPromptRange
303 r = self.blockRanges[blockID].inputPromptRange
291 self.insert_text(prompt,
304 self.insert_text(prompt,
292 textRange=r,
305 textRange=r,
293 scrollToVisible=False
306 scrollToVisible=False
294 )
307 )
295
308
296 return result
309 return result
297
310
298
311
299 def render_result(self, result):
312 def render_result(self, result):
300 blockID = result['blockID']
313 blockID = result['blockID']
301 inputRange = self.blockRanges[blockID].inputRange
314 inputRange = self.blockRanges[blockID].inputRange
302 del self.blockRanges[blockID]
315 del self.blockRanges[blockID]
303
316
304 #print inputRange,self.current_block_range()
317 #print inputRange,self.current_block_range()
305 self.insert_text('\n' +
318 self.insert_text('\n' +
306 self.output_prompt(number=result['number']) +
319 self.output_prompt(number=result['number']) +
307 result.get('display',{}).get('pprint','') +
320 result.get('display',{}).get('pprint','') +
308 '\n\n',
321 '\n\n',
309 textRange=NSMakeRange(inputRange.location+inputRange.length,
322 textRange=NSMakeRange(inputRange.location+inputRange.length,
310 0))
323 0))
311 return result
324 return result
312
325
313
326
314 def render_error(self, failure):
327 def render_error(self, failure):
315 print failure
328 print failure
316 blockID = failure.blockID
329 blockID = failure.blockID
317 inputRange = self.blockRanges[blockID].inputRange
330 inputRange = self.blockRanges[blockID].inputRange
318 self.insert_text('\n' +
331 self.insert_text('\n' +
319 self.output_prompt() +
332 self.output_prompt() +
320 '\n' +
333 '\n' +
321 failure.getErrorMessage() +
334 failure.getErrorMessage() +
322 '\n\n',
335 '\n\n',
323 textRange=NSMakeRange(inputRange.location +
336 textRange=NSMakeRange(inputRange.location +
324 inputRange.length,
337 inputRange.length,
325 0))
338 0))
326 self.start_new_block()
339 self.start_new_block()
327 return failure
340 return failure
328
341
329
342
330 def _start_cli_banner(self):
343 def _start_cli_banner(self):
331 """Print banner"""
344 """Print banner"""
332
345
333 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
334 IPython.__version__
347 IPython.__version__
335
348
336 self.insert_text(banner + '\n\n')
349 self.insert_text(banner + '\n\n')
337
350
338
351
339 def start_new_block(self):
352 def start_new_block(self):
340 """"""
353 """"""
341
354
342 self.currentBlockID = self.next_block_ID()
355 self.currentBlockID = self.next_block_ID()
343 self.blockRanges[self.currentBlockID] = self.new_cell_block()
356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
344 self.insert_text(self.input_prompt(),
357 self.insert_text(self.input_prompt(),
345 textRange=self.current_block_range().inputPromptRange)
358 textRange=self.current_block_range().inputPromptRange)
346
359
347
360
348
361
349 def next_block_ID(self):
362 def next_block_ID(self):
350
363
351 return uuid.uuid4()
364 return uuid.uuid4()
352
365
353 def new_cell_block(self):
366 def new_cell_block(self):
354 """A new CellBlock at the end of self.textView.textStorage()"""
367 """A new CellBlock at the end of self.textView.textStorage()"""
355
368
356 return CellBlock(NSMakeRange(self.textView.textStorage().length()-1,
369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
357 0), #len(self.input_prompt())),
370 0), #len(self.input_prompt())),
358 NSMakeRange(self.textView.textStorage().length()-1,# + len(self.input_prompt()),
371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
359 0))
372 0))
360
373
361
374
362 def current_block_range(self):
375 def current_block_range(self):
363 return self.blockRanges.get(self.currentBlockID,
376 return self.blockRanges.get(self.currentBlockID,
364 self.new_cell_block())
377 self.new_cell_block())
365
378
366 def current_block(self):
379 def current_block(self):
367 """The current block's text"""
380 """The current block's text"""
368
381
369 return self.text_for_range(self.current_block_range().inputRange)
382 return self.text_for_range(self.current_block_range().inputRange)
370
383
371 def text_for_range(self, textRange):
384 def text_for_range(self, textRange):
372 """text_for_range"""
385 """text_for_range"""
373
386
374 ts = self.textView.textStorage()
387 ts = self.textView.textStorage()
375 return ts.string().substringWithRange_(textRange)
388 return ts.string().substringWithRange_(textRange)
376
389
377 def current_line(self):
390 def current_line(self):
378 block = self.text_for_range(self.current_block_range().inputRange)
391 block = self.text_for_range(self.current_block_range().inputRange)
379 block = block.split('\n')
392 block = block.split('\n')
380 return block[-1]
393 return block[-1]
381
394
382
395
383 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
384 """Insert text into textView at textRange, updating blockRanges
397 """Insert text into textView at textRange, updating blockRanges
385 as necessary
398 as necessary
386 """
399 """
387 if(textRange == None):
400 if(textRange == None):
388 #range for end of text
401 #range for end of text
389 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
390
403
391
404
392 print textRange,string,self.textView.textStorage(),self.textView.textStorage().length()
393 self.textView.replaceCharactersInRange_withString_(
405 self.textView.replaceCharactersInRange_withString_(
394 textRange, string)
406 textRange, string)
395
407
396 for r in self.blockRanges.itervalues():
408 for r in self.blockRanges.itervalues():
397 r.update_ranges_for_insertion(string, textRange)
409 r.update_ranges_for_insertion(string, textRange)
398
410
399 self.textView.setSelectedRange_(textRange)
411 self.textView.setSelectedRange_(textRange)
400 if(scrollToVisible):
412 if(scrollToVisible):
401 self.textView.scrollRangeToVisible_(textRange)
413 self.textView.scrollRangeToVisible_(textRange)
402
414
403
415
404
416
405 def replace_current_block_with_string(self, textView, string):
417 def replace_current_block_with_string(self, textView, string):
406 textView.replaceCharactersInRange_withString_(
418 textView.replaceCharactersInRange_withString_(
407 self.current_block_range().inputRange,
419 self.current_block_range().inputRange,
408 string)
420 string)
409 self.current_block_range().inputRange.length = len(string)
421 self.current_block_range().inputRange.length = len(string)
410 r = NSMakeRange(textView.textStorage().length(), 0)
422 r = NSMakeRange(textView.textStorage().length(), 0)
411 textView.scrollRangeToVisible_(r)
423 textView.scrollRangeToVisible_(r)
412 textView.setSelectedRange_(r)
424 textView.setSelectedRange_(r)
413
425
414
426
415 def current_indent_string(self):
427 def current_indent_string(self):
416 """returns string for indent or None if no indent"""
428 """returns string for indent or None if no indent"""
417
429
418 return self._indent_for_block(self.current_block())
430 return self._indent_for_block(self.current_block())
419
431
420
432
421 def _indent_for_block(self, block):
433 def _indent_for_block(self, block):
422 lines = block.split('\n')
434 lines = block.split('\n')
423 if(len(lines) > 1):
435 if(len(lines) > 1):
424 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
425 if(currentIndent == 0):
437 if(currentIndent == 0):
426 currentIndent = self.tabSpaces
438 currentIndent = self.tabSpaces
427
439
428 if(self.tabUsesSpaces):
440 if(self.tabUsesSpaces):
429 result = ' ' * currentIndent
441 result = ' ' * currentIndent
430 else:
442 else:
431 result = '\t' * (currentIndent/self.tabSpaces)
443 result = '\t' * (currentIndent/self.tabSpaces)
432 else:
444 else:
433 result = None
445 result = None
434
446
435 return result
447 return result
436
448
437
449
438 # NSTextView delegate methods...
450 # NSTextView delegate methods...
439 def textView_doCommandBySelector_(self, textView, selector):
451 def textView_doCommandBySelector_(self, textView, selector):
440 assert(textView == self.textView)
452 assert(textView == self.textView)
441 NSLog("textView_doCommandBySelector_: "+selector)
453 NSLog("textView_doCommandBySelector_: "+selector)
442
454
443
455
444 if(selector == 'insertNewline:'):
456 if(selector == 'insertNewline:'):
445 indent = self.current_indent_string()
457 indent = self.current_indent_string()
446 if(indent):
458 if(indent):
447 line = indent + self.current_line()
459 line = indent + self.current_line()
448 else:
460 else:
449 line = self.current_line()
461 line = self.current_line()
450
462
451 if(self.is_complete(self.current_block())):
463 if(self.is_complete(self.current_block())):
452 self.execute(self.current_block(),
464 self.execute(self.current_block(),
453 blockID=self.currentBlockID)
465 blockID=self.currentBlockID)
454 self.start_new_block()
466 self.start_new_block()
455
467
456 return True
468 return True
457
469
458 return False
470 return False
459
471
460 elif(selector == 'moveUp:'):
472 elif(selector == 'moveUp:'):
461 prevBlock = self.get_history_previous(self.current_block())
473 prevBlock = self.get_history_previous(self.current_block())
462 if(prevBlock != None):
474 if(prevBlock != None):
463 self.replace_current_block_with_string(textView, prevBlock)
475 self.replace_current_block_with_string(textView, prevBlock)
464 else:
476 else:
465 NSBeep()
477 NSBeep()
466 return True
478 return True
467
479
468 elif(selector == 'moveDown:'):
480 elif(selector == 'moveDown:'):
469 nextBlock = self.get_history_next()
481 nextBlock = self.get_history_next()
470 if(nextBlock != None):
482 if(nextBlock != None):
471 self.replace_current_block_with_string(textView, nextBlock)
483 self.replace_current_block_with_string(textView, nextBlock)
472 else:
484 else:
473 NSBeep()
485 NSBeep()
474 return True
486 return True
475
487
476 elif(selector == 'moveToBeginningOfParagraph:'):
488 elif(selector == 'moveToBeginningOfParagraph:'):
477 textView.setSelectedRange_(NSMakeRange(
489 textView.setSelectedRange_(NSMakeRange(
478 self.current_block_range().inputRange.location,
490 self.current_block_range().inputRange.location,
479 0))
491 0))
480 return True
492 return True
481 elif(selector == 'moveToEndOfParagraph:'):
493 elif(selector == 'moveToEndOfParagraph:'):
482 textView.setSelectedRange_(NSMakeRange(
494 textView.setSelectedRange_(NSMakeRange(
483 self.current_block_range().inputRange.location + \
495 self.current_block_range().inputRange.location + \
484 self.current_block_range().inputRange.length, 0))
496 self.current_block_range().inputRange.length, 0))
485 return True
497 return True
486 elif(selector == 'deleteToEndOfParagraph:'):
498 elif(selector == 'deleteToEndOfParagraph:'):
487 if(textView.selectedRange().location <= \
499 if(textView.selectedRange().location <= \
488 self.current_block_range().location):
500 self.current_block_range().location):
489 raise NotImplemented()
501 raise NotImplemented()
490
502
491 return False # don't actually handle the delete
503 return False # don't actually handle the delete
492
504
493 elif(selector == 'insertTab:'):
505 elif(selector == 'insertTab:'):
494 if(len(self.current_line().strip()) == 0): #only white space
506 if(len(self.current_line().strip()) == 0): #only white space
495 return False
507 return False
496 else:
508 else:
497 self.textView.complete_(self)
509 self.textView.complete_(self)
498 return True
510 return True
499
511
500 elif(selector == 'deleteBackward:'):
512 elif(selector == 'deleteBackward:'):
501 #if we're at the beginning of the current block, ignore
513 #if we're at the beginning of the current block, ignore
502 if(textView.selectedRange().location == \
514 if(textView.selectedRange().location == \
503 self.current_block_range().inputRange.location):
515 self.current_block_range().inputRange.location):
504 return True
516 return True
505 else:
517 else:
506 for r in self.blockRanges.itervalues():
518 for r in self.blockRanges.itervalues():
507 deleteRange = textView.selectedRange
519 deleteRange = textView.selectedRange
508 if(deleteRange.length == 0):
520 if(deleteRange.length == 0):
509 deleteRange.location -= 1
521 deleteRange.location -= 1
510 deleteRange.length = 1
522 deleteRange.length = 1
511 r.update_ranges_for_deletion(deleteRange)
523 r.update_ranges_for_deletion(deleteRange)
512 return False
524 return False
513 return False
525 return False
514
526
515
527
516 def textView_shouldChangeTextInRanges_replacementStrings_(self,
528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
517 textView, ranges, replacementStrings):
529 textView, ranges, replacementStrings):
518 """
530 """
519 Delegate method for NSTextView.
531 Delegate method for NSTextView.
520
532
521 Refuse change text in ranges not at end, but make those changes at
533 Refuse change text in ranges not at end, but make those changes at
522 end.
534 end.
523 """
535 """
524
536
525 assert(len(ranges) == len(replacementStrings))
537 assert(len(ranges) == len(replacementStrings))
526 allow = True
538 allow = True
527 for r,s in zip(ranges, replacementStrings):
539 for r,s in zip(ranges, replacementStrings):
528 r = r.rangeValue()
540 r = r.rangeValue()
529 if(textView.textStorage().length() > 0 and
541 if(textView.textStorage().length() > 0 and
530 r.location < self.current_block_range().inputRange.location):
542 r.location < self.current_block_range().inputRange.location):
531 self.insert_text(s, textRange=r)
543 self.insert_text(s)
532 allow = False
544 allow = False
533
545
534 return allow
546 return allow
535
547
536 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
537 textView, words, charRange, index):
549 textView, words, charRange, index):
538 try:
550 try:
539 ts = textView.textStorage()
551 ts = textView.textStorage()
540 token = ts.string().substringWithRange_(charRange)
552 token = ts.string().substringWithRange_(charRange)
541 completions = blockingCallFromThread(self.complete, token)
553 completions = blockingCallFromThread(self.complete, token)
542 except:
554 except:
543 completions = objc.nil
555 completions = objc.nil
544 NSBeep()
556 NSBeep()
545
557
546 return (completions,0)
558 return (completions,0)
547
559
548
560
General Comments 0
You need to be logged in to leave comments. Login now