File: | inputmanager/NSTextView_Bibdesk.m |
Location: | line 224, column 17 |
Description: | dead store |
1 | // |
2 | // NSTextView_Bibdesk.m |
3 | // BibDeskInputManager |
4 | // |
5 | // Created by Sven-S. Porst on Sat Jul 17 2004. |
6 | /* |
7 | This software is Copyright (c) 2004-2008 |
8 | Sven-S. Porst. All rights reserved. |
9 | |
10 | Redistribution and use in source and binary forms, with or without |
11 | modification, are permitted provided that the following conditions |
12 | are met: |
13 | |
14 | - Redistributions of source code must retain the above copyright |
15 | notice, this list of conditions and the following disclaimer. |
16 | |
17 | - Redistributions in binary form must reproduce the above copyright |
18 | notice, this list of conditions and the following disclaimer in |
19 | the documentation and/or other materials provided with the |
20 | distribution. |
21 | |
22 | - Neither the name of Sven-S. Porst nor the names of any |
23 | contributors may be used to endorse or promote products derived |
24 | from this software without specific prior written permission. |
25 | |
26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
29 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
30 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
31 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
32 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
33 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
34 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
35 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
37 | */ |
38 | |
39 | #import "NSTextView_Bibdesk.h" |
40 | #import <Foundation/Foundation.h> |
41 | #import <objc/objc-class.h> |
42 | #import <objc/Protocol.h> |
43 | |
44 | NSString *BDSKInputManagerID = @"net.sourceforge.bibdesk.inputmanager"; |
45 | NSString *BDSKInputManagerLoadableApplications = @"Application bundles that we recognize"; |
46 | |
47 | static NSString *BDSKInsertionString = nil( ( void * ) 0 ); |
48 | |
49 | static NSString *BDSKScriptName = @"Bibdesk"; |
50 | static NSString *BDSKScriptType = @"scpt"; |
51 | static NSString *BDSKHandlerName = @"getcitekeys"; |
52 | |
53 | extern void _objc_resolve_categories_for_class(struct objc_class *cls); |
54 | |
55 | // The functions with an OB prefix are from OmniBase/OBUtilities.m |
56 | // and are covered by the Omni source license, and may only be used or |
57 | // reproduced in accordance with that license. |
58 | // http://www.omnigroup.com/developer/sourcecode/sourcelicense/ |
59 | |
60 | static IMP OBBDSKReplaceMethodImplementation(Class aClass, SEL oldSelector, IMP newImp) |
61 | { |
62 | struct objc_method *thisMethod; |
63 | IMP oldImp = NULL( ( void * ) 0 ); |
64 | extern void _objc_flush_caches(Class); |
65 | |
66 | if ((thisMethod = class_getInstanceMethod(aClass, oldSelector))) { |
67 | oldImp = thisMethod->method_imp; |
68 | |
69 | // Replace the method in place |
70 | thisMethod->method_imp = newImp; |
71 | |
72 | // Flush the method cache |
73 | _objc_flush_caches(aClass); |
74 | } |
75 | |
76 | return oldImp; |
77 | } |
78 | |
79 | static IMP OBBDSKReplaceMethodImplementationWithSelector(Class aClass, SEL oldSelector, SEL newSelector) |
80 | { |
81 | struct objc_method *newMethod; |
82 | |
83 | newMethod = class_getInstanceMethod(aClass, newSelector); |
84 | NSCAssertdo { if ( ! ( ( newMethod != ( ( void * ) 0 ) ) ) ) { [ [ NSAssertionHandler currentHandler ] handleFailureInFunction : [ NSString stringWithUTF8String : __PRETTY_FUNCTION__ ] file : [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/inputmanager/NSTextView_Bibdesk.m" ] lineNumber : 84 description : ( ( @ "new method must not be nil" ) ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )(newMethod != nil, @"new method must not be nil"); |
85 | |
86 | return OBBDSKReplaceMethodImplementation(aClass, oldSelector, newMethod->method_imp); |
87 | } |
88 | |
89 | // The compiler won't allow us to call the IMP directly if it returns an NSRange, so I followed Apple's code at |
90 | // http://developer.apple.com/documentation/Performance/Conceptual/CodeSpeed/CodeSpeed.pdf |
91 | // See also the places where Omni uses OBReplaceMethod... calls in OmniAppKit, which is easier to follow. |
92 | static NSRange (*originalRangeIMP)(id, SEL) = NULL( ( void * ) 0 ); |
93 | static void (*originalInsertIMP)(id, SEL, NSString *, NSRange, int, BOOL) = NULL( ( void * ) 0 ); |
94 | static id (*originalCompletionsIMP)(id, SEL, NSRange, int *) = NULL( ( void * ) 0 ); |
95 | |
96 | @implementation NSTextView_Bibdesk |
97 | |
98 | + (void)load{ |
99 | |
100 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
101 | |
102 | // ARM: we just leak these strings; since the bundle only gets loaded once, it's not worth replacing dealloc |
103 | if(BDSKInsertionString == nil( ( void * ) 0 )) |
104 | BDSKInsertionString = [NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ " (Bibdesk)" ) value : @ "" table : ( ( void * ) 0 ) ](@" (Bibdesk)", @"") retain]; |
105 | |
106 | NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; // for the app we are loading into |
107 | NSArray *array = [[[NSUserDefaults standardUserDefaults] persistentDomainForName:BDSKInputManagerID] objectForKey:BDSKInputManagerLoadableApplications]; |
108 | |
109 | NSEnumerator *e = [array objectEnumerator]; |
110 | NSString *str; |
111 | BOOL yn = NO( BOOL ) 0; |
112 | |
113 | while(str = [e nextObject]){ |
114 | if([str caseInsensitiveCompare:bundleID] == NSOrderedSame){ |
115 | yn = YES( BOOL ) 1; |
116 | break; |
117 | } |
118 | } |
119 | |
120 | if(yn && [[self superclass] instancesRespondToSelector:@selector(completionsForPartialWordRange:indexOfSelectedItem:)]){ |
121 | |
122 | // Class posing was cleaner and probably safer than swizzling, but led to unresolved problems with internationalized versions of TeXShop+OgreKit refusing text input for the Ogre find panel. I think this is an OgreKit bug. |
123 | originalInsertIMP = (typeof(originalInsertIMP))OBBDSKReplaceMethodImplementationWithSelector(self, @selector(insertCompletion:forPartialWordRange:movement:isFinal:), @selector(replacementInsertCompletion:forPartialWordRange:movement:isFinal:)); |
124 | originalRangeIMP = (typeof(originalRangeIMP))OBBDSKReplaceMethodImplementationWithSelector(self,@selector(rangeForUserCompletion),@selector(replacementRangeForUserCompletion)); |
125 | |
126 | // have to replace this one since we don't call the delegate method from our implementation, and we don't want to override unless the user chooses to do so |
127 | originalCompletionsIMP = (typeof(originalCompletionsIMP))OBBDSKReplaceMethodImplementationWithSelector(self, @selector(completionsForPartialWordRange:indexOfSelectedItem:),@selector(replacementCompletionsForPartialWordRange:indexOfSelectedItem:)); |
128 | } |
129 | |
130 | [pool release]; |
131 | } |
132 | |
133 | @end |
134 | |
135 | @implementation NSTextView (BDSKCompletion) |
136 | |
137 | #pragma mark - |
138 | #pragma mark Reference-searching heuristics |
139 | |
140 | // ** Check to see if it's TeX |
141 | // - look back to see if { ; if no brace, return not TeX |
142 | // - if { found, look back between insertion point and { to find comma; check to see if it's BibTeX, then return the match range |
143 | // ** Check to see if it's BibTeX |
144 | // - look back to see if it's jurabib with }{ |
145 | // - look back to see if ] ; if no options, then just find the citecommand (or not) by searching back from { |
146 | // - look back to see if ][ ; if so, set ] range again |
147 | // - look back to find [ starting from ] |
148 | // - now we have the last [, see if there is a cite immediately preceding it using rangeOfString:@"cite" || rangeOfString:@"bibentry" |
149 | // - if there were no brackets, but there was a double curly brace, then check for a jurabib citation |
150 | // ** After all of this, we've searched back to a brace, and then checked for a cite command with two optional parameters |
151 | |
152 | - (BOOL)isBibTeXCitation:(NSRange)braceRange{ |
153 | |
154 | NSString *str = [self string]; |
155 | NSRange citeSearchRange = NSMakeRange(NSNotFound, 0); |
156 | NSRange doubleBracketRange = NSMakeRange(NSNotFound, 0); |
157 | |
158 | NSRange rightBracketRange = [str rangeOfString:@"]" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(braceRange, 1)]; // see if there are any optional parameters |
159 | |
160 | // check for jurabib \citefield, which has two mandatory parameters in curly braces, e.g. \citefield[pagerange]{title}{cite:key} |
161 | NSRange doubleBraceRange = [str rangeOfString:@"}{" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange( NSMakeRange(braceRange.location + 1, 1), 10)]; |
162 | |
163 | if(rightBracketRange.location == NSNotFound && doubleBraceRange.location == NSNotFound){ // no options and not jurabib, so life is easy; look backwards 10 characters from the brace and see if there's a citecommand |
164 | citeSearchRange = SafeBackwardSearchRange(braceRange, 20); |
165 | if([str rangeOfString:@"cite" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound || |
166 | [str rangeOfString:@"bibentry" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound){ |
167 | return YES( BOOL ) 1; |
168 | } else { |
169 | return NO( BOOL ) 0; |
170 | } |
171 | } |
172 | |
173 | if(doubleBraceRange.location != NSNotFound) // reset the brace range if we have jurabib |
174 | braceRange = [str rangeOfString:@"{" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(doubleBraceRange, 10)]; |
175 | |
176 | NSRange leftBracketRange = [str rangeOfString:@"[" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(braceRange, 100)]; // first occurrence of it, looking backwards |
177 | // next, see if we have two optional parameters; this range is tricky, since we have to go forward one, then do a safe backward search over the previous characters |
178 | if(leftBracketRange.location != NSNotFound) |
179 | doubleBracketRange = [str rangeOfString:@"][" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange( NSMakeRange(leftBracketRange.location + 1, 3), 3)]; |
180 | |
181 | if(doubleBracketRange.location != NSNotFound) // if we had two parameters, find the last opening bracket |
182 | leftBracketRange = [str rangeOfString:@"[" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(doubleBracketRange, 50)]; |
183 | |
184 | if(leftBracketRange.location != NSNotFound){ |
185 | citeSearchRange = SafeBackwardSearchRange(leftBracketRange, 20); // could be larger |
186 | if([str rangeOfString:@"cite" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound || |
187 | [str rangeOfString:@"bibentry" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound){ |
188 | return YES( BOOL ) 1; |
189 | } else { |
190 | return NO( BOOL ) 0; |
191 | } |
192 | } |
193 | |
194 | if(doubleBraceRange.location != NSNotFound){ // jurabib with no options on it |
195 | citeSearchRange = SafeBackwardSearchRange(braceRange, 20); // could be larger |
196 | if([str rangeOfString:@"cite" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound || |
197 | [str rangeOfString:@"bibentry" options:NSBackwardsSearch | NSLiteralSearch range:citeSearchRange].location != NSNotFound){ |
198 | return YES( BOOL ) 1; |
199 | } else { |
200 | return NO( BOOL ) 0; |
201 | } |
202 | } |
203 | |
204 | return NO( BOOL ) 0; |
205 | } |
206 | |
207 | - (NSRange)citeKeyRange{ |
208 | |
209 | NSString *str = [self string]; |
210 | NSRange r = [self selectedRange]; // here's the insertion point |
211 | NSRange commaRange; |
212 | NSRange finalRange; |
213 | unsigned maxLoc; |
214 | |
215 | NSRange braceRange = [str rangeOfString:@"{" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(r, 100)]; // look for an opening brace |
216 | NSRange closingBraceRange = [str rangeOfString:@"}" options:NSBackwardsSearch | NSLiteralSearch range:SafeBackwardSearchRange(r, 100)]; |
217 | |
218 | if(closingBraceRange.location != NSNotFound && closingBraceRange.location > braceRange.location) // if our { has a matching }, don't bother |
219 | return finalRange = NSMakeRange(NSNotFound, 0); |
220 | |
221 | if(braceRange.location != NSNotFound){ // may be TeX |
222 | commaRange = [str rangeOfString:@"," options:NSBackwardsSearch | NSLiteralSearch range:NSUnionRange(braceRange, r)]; // exclude commas in the optional parameters |
223 | } else { // definitely not TeX |
Although the value stored to 'finalRange' is used in the enclosing expression, the value is never actually read from 'finalRange' | |
224 | return finalRange = NSMakeRange(NSNotFound, 0); |
225 | } |
226 | |
227 | if([self isBibTeXCitation:braceRange]){ |
228 | if(commaRange.location != NSNotFound && r.location > commaRange.location){ |
229 | maxLoc = ( (commaRange.location + 1 > r.location) ? commaRange.location : commaRange.location + 1 ); |
230 | finalRange = SafeForwardSearchRange(maxLoc, r.location - commaRange.location - 1, r.location); |
231 | } else { |
232 | maxLoc = ( (braceRange.location + 1 > r.location) ? braceRange.location : braceRange.location + 1 ); |
233 | finalRange = SafeForwardSearchRange(maxLoc, r.location - braceRange.location - 1, r.location); |
234 | } |
235 | } else { |
236 | finalRange = NSMakeRange(NSNotFound, 0); |
237 | } |
238 | |
239 | return finalRange; |
240 | } |
241 | |
242 | - (NSRange)refLabelRange{ |
243 | |
244 | NSString *s = [self string]; |
245 | NSRange r = [self selectedRange]; |
246 | NSRange searchRange = SafeBackwardSearchRange(r, 12); |
247 | |
248 | // look for standard \ref |
249 | NSRange foundRange = [s rangeOfString:@"\\ref{" options:NSBackwardsSearch range:searchRange]; |
250 | |
251 | if(foundRange.location == NSNotFound){ |
252 | |
253 | // maybe it's a pageref |
254 | foundRange = [s rangeOfString:@"\\pageref{" options:NSBackwardsSearch range:searchRange]; |
255 | |
256 | // could also be an eqref (amsmath) |
257 | if(foundRange.location == NSNotFound) |
258 | foundRange = [s rangeOfString:@"\\eqref{" options:NSBackwardsSearch range:searchRange]; |
259 | } |
260 | unsigned idx = NSMaxRange(foundRange); |
261 | idx = (idx < r.location ? r.location - idx : 0); |
262 | |
263 | return NSMakeRange(NSMaxRange(foundRange), idx); |
264 | } |
265 | |
266 | #pragma mark - |
267 | #pragma mark AppKit overrides |
268 | |
269 | // Override usual behaviour so we can have dots, colons and hyphens in our cite keys |
270 | - (NSRange)rangeForBibTeXUserCompletion{ |
271 | |
272 | NSRange range = [self citeKeyRange]; |
273 | return range.location == NSNotFound ? [self refLabelRange] : range; |
274 | } |
275 | |
276 | static BOOL isCompletingTeX = NO( BOOL ) 0; |
277 | |
278 | // we replace this method since the completion controller uses it to update |
279 | - (NSRange)replacementRangeForUserCompletion{ |
280 | |
281 | NSRange range = [self rangeForBibTeXUserCompletion]; |
282 | isCompletingTeX = range.location != NSNotFound; |
283 | |
284 | return range.location != NSNotFound ? range : originalRangeIMP(self, _cmd); |
285 | } |
286 | |
287 | // this returns -1 instead of NSNotFound for compatibility with the completion controller indexOfSelectedItem parameter |
288 | static inline int |
289 | BDIndexOfItemInArrayWithPrefix(NSArray *array, NSString *prefix) |
290 | { |
291 | unsigned idx, count = [array count]; |
292 | for(idx = 0; idx < count; idx++){ |
293 | if([[array objectAtIndex:idx] hasPrefix:prefix]) |
294 | return idx; |
295 | } |
296 | |
297 | return -1; |
298 | } |
299 | |
300 | // Provide own completions based on results by Bibdesk. |
301 | // Should check whether Bibdesk is available first. |
302 | // Setting initial selection in list to second item doesn't work. |
303 | // Requires X.3 |
304 | - (NSArray *)replacementCompletionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index{ |
305 | |
306 | NSString *s = [self string]; |
307 | NSRange refLabelRange = [self refLabelRange]; |
308 | |
309 | // don't bother checking for a citekey if this is a \ref |
310 | NSRange keyRange = ( (refLabelRange.location == NSNotFound) ? [self citeKeyRange] : NSMakeRange(NSNotFound, 0) ); |
311 | NSMutableArray *returnArray = [NSMutableArray array]; |
312 | int n; |
313 | |
314 | if(keyRange.location != NSNotFound){ |
315 | |
316 | NSString *end = [[s substringWithRange:keyRange] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; |
317 | |
318 | NSDictionary *errorInfo = nil( ( void * ) 0 ); |
319 | static NSAppleScript *script = nil( ( void * ) 0 ); |
320 | if(script == nil( ( void * ) 0 )){ |
321 | NSURL *scriptURL = [NSURL fileURLWithPath:[[NSBundle bundleWithIdentifier:BDSKInputManagerID] pathForResource:BDSKScriptName ofType: BDSKScriptType]]; |
322 | script = [[NSAppleScript alloc] initWithContentsOfURL:scriptURL error:&errorInfo]; |
323 | if(errorInfo != nil( ( void * ) 0 )){ |
324 | [script release]; |
325 | script = nil( ( void * ) 0 ); |
326 | NSLog(@"*** Failed to initialize script at URL %@ because of error %@", scriptURL, errorInfo); |
327 | } |
328 | } |
329 | |
330 | if (script && !errorInfo) { |
331 | |
332 | /* We have to construct an AppleEvent descriptor to contain the arguments for our handler call. Remember that this list is 1, rather than 0, based. */ |
333 | NSAppleEventDescriptor *arguments = [[NSAppleEventDescriptor alloc] initListDescriptor]; |
334 | [arguments insertDescriptor: [NSAppleEventDescriptor descriptorWithString:end] atIndex: 1] ; |
335 | |
336 | /* Call the handler using the method in our special NSAppleScript category */ |
337 | NSAppleEventDescriptor *result = [script callHandler: BDSKHandlerName withArguments: arguments errorInfo: &errorInfo]; |
338 | [arguments release]; |
339 | |
340 | if (!errorInfo) { |
341 | |
342 | if (result && (n = [result numberOfItems])) { |
343 | NSAppleEventDescriptor *stringAEDesc; |
344 | NSString *completionString; |
345 | |
346 | do { |
347 | // run through the list top to bottom, keeping in mind it is 1 based. |
348 | stringAEDesc = [result descriptorAtIndex:n]; |
349 | // insert 'identification string at end so we'll recognise our own completions in -insertCompletion:for... |
350 | completionString = [[stringAEDesc stringValue] stringByAppendingString:BDSKInsertionString]; |
351 | |
352 | // add in at beginning of array |
353 | [returnArray insertObject:completionString atIndex:0]; |
354 | } while(--n); |
355 | } |
356 | } // no script running error |
357 | if(errorInfo) NSLog(@"*** Failed to run script %@ because of error %@", script, errorInfo); |
358 | |
359 | } // no script loading error |
360 | if(errorInfo) NSLog(@"*** Failed to run script %@ because of error %@", script, errorInfo); |
361 | |
362 | *index = BDIndexOfItemInArrayWithPrefix(returnArray, end); |
363 | |
364 | } else if(refLabelRange.location != NSNotFound){ |
365 | NSString *hint = [s substringWithRange:refLabelRange]; |
366 | |
367 | NSScanner *labelScanner = [[NSScanner alloc] initWithString:s]; |
368 | [labelScanner setCharactersToBeSkipped:nil( ( void * ) 0 )]; |
369 | NSString *scanned = nil( ( void * ) 0 ); |
370 | NSMutableSet *setOfLabels = [NSMutableSet setWithCapacity:10]; |
371 | NSString *scanFormat; |
372 | |
373 | scanFormat = [@"\\label{" stringByAppendingString:hint]; |
374 | |
375 | while(![labelScanner isAtEnd]){ |
376 | [labelScanner scanUpToString:scanFormat intoString:nil( ( void * ) 0 )]; // scan for strings with \label{hint in them |
377 | [labelScanner scanString:@"\\label{" intoString:nil( ( void * ) 0 )]; // scan away the \label{ |
378 | [labelScanner scanUpToString:@"}" intoString:&scanned]; // scan up to the next brace |
379 | if(scanned != nil( ( void * ) 0 )) [setOfLabels addObject:[scanned stringByAppendingString:BDSKInsertionString]]; // add it to the set |
380 | } |
381 | [labelScanner release]; |
382 | // return the set as an array, sorted alphabetically |
383 | [returnArray setArray:[[setOfLabels allObjects] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]]; |
384 | *index = BDIndexOfItemInArrayWithPrefix(returnArray, hint); |
385 | } else { |
386 | // return the spellchecker's guesses |
387 | returnArray = (NSMutableArray *)[[NSSpellChecker sharedSpellChecker] completionsForPartialWordRange:charRange inString:s language:nil( ( void * ) 0 ) inSpellDocumentWithTag:[self spellCheckerDocumentTag]]; |
388 | *index = BDIndexOfItemInArrayWithPrefix(returnArray, [s substringWithRange:charRange]); |
389 | } |
390 | return returnArray; |
391 | } |
392 | |
393 | // for legacy reasons, rangeForUserCompletion gives us an incorrect range for replacement; since it's compatible with searching and I don't feel like changing all the range code, we'll fix it up here |
394 | - (void)fixRange:(NSRange *)range{ |
395 | NSString *string = [self string]; |
396 | |
397 | NSRange selRange = [self selectedRange]; |
398 | unsigned minLoc = ( (selRange.location > 100) ? 100 : selRange.location); |
399 | NSRange safeRange = NSMakeRange(selRange.location - minLoc, minLoc); |
400 | |
401 | NSRange braceRange = [string rangeOfString:@"{" options:NSBackwardsSearch | NSLiteralSearch range:safeRange]; // look for an opening brace |
402 | NSRange commaRange = [string rangeOfString:@"," options:NSBackwardsSearch | NSLiteralSearch range:safeRange]; // look for a comma |
403 | unsigned maxLoc = [[self string] length]; |
404 | |
405 | if(braceRange.location != NSNotFound && braceRange.location < range->location){ |
406 | // we found the brace, which must exist if we're here; if not, we won't adjust anything, though |
407 | if(commaRange.location != NSNotFound && commaRange.location > braceRange.location) |
408 | range->location = MIN( { __typeof__ ( commaRange . location + 1 ) __a = ( commaRange . location + 1 ) ; __typeof__ ( maxLoc ) __b = ( maxLoc ) ; __a < __b ? __a : __b ; } )(commaRange.location + 1, maxLoc); |
409 | else |
410 | range->location = MIN( { __typeof__ ( braceRange . location + 1 ) __a = ( braceRange . location + 1 ) ; __typeof__ ( maxLoc ) __b = ( maxLoc ) ; __a < __b ? __a : __b ; } )(braceRange.location + 1, maxLoc); |
411 | } |
412 | } |
413 | |
414 | // finish off the completion, inserting just the cite key |
415 | - (void)replacementInsertCompletion:(NSString *)word forPartialWordRange:(NSRange)charRange movement:(int)movement isFinal:(BOOL)flag { |
416 | |
417 | if(isCompletingTeX || [self refLabelRange].location != NSNotFound) |
418 | [self fixRange:&charRange]; |
419 | |
420 | if (flag == YES( BOOL ) 1 && ([word rangeOfString:BDSKInsertionString].location != NSNotFound)) { |
421 | // this is one of our suggestions, so we need to trim it |
422 | // strip the comment for this, this assumes cite keys can't have spaces in them |
423 | NSRange firstSpace = [word rangeOfString:@" "]; |
424 | word = [word substringToIndex:firstSpace.location]; |
425 | } |
426 | originalInsertIMP(self, _cmd, word, charRange, movement, flag); |
427 | } |
428 | |
429 | @end |