Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / editors / scapp / SCTextView.M
blob5de5d972e7480d77bac3b7cc0aec5ccdafc8e492
1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #import "SCTextView.h"
22 #import "MyDocument.h"
23 #import "SCCocoaView.h"
24 #import "SC_DirUtils.h"
25 #include "SCBase.h"
26 #include "PyrSymbol.h"
27 #include <pthread.h>
29 extern bool compiledOK;
30 //*)acceptableDragTypes
31 @implementation SCTextView
33 - (id)initWithFrame:(NSRect)frameRect {
34 useAutoInOutdent = false;
35 return [super initWithFrame:frameRect];
38 - (void) setLangClassToCall: (NSString*) stringin withKeyDownActionIndex:(int) downIndex withKeyUpActionIndex:(int) upIndex
40 langClassToCall = stringin;
41 keyDownActionIndex = downIndex;
42 keyUpActionIndex = upIndex;
43 [langClassToCall autorelease];
46 - (void) setObjectKeyDownActionIndex:(int) upindex setObjectKeyUpActionIndex:(int) downindex
48 objectKeyDownActionIndex = downindex;
49 objectKeyUpActionIndex = upindex;
52 -(BOOL) acceptsFirstResponder
54 return mAcceptsFirstResponder;
57 -(void) setAcceptsFirstResponder: (BOOL) flag
59 mAcceptsFirstResponder = flag;
62 - (BOOL)becomeFirstResponder
64 BOOL accept = [super becomeFirstResponder];
65 if(accept) [[self delegate] setActiveTextView: self];
66 return accept;
69 - (void) setUsesAutoInOutdent: (bool) flag
71 useAutoInOutdent = flag;
74 - (bool) usesAutoInOutdent
76 return useAutoInOutdent;
79 - (void) autoIndent: (NSEvent*) event
81 unichar c, d;
82 unichar spaces[128];
83 int nspaces = 0;
84 NSRange range = [self selectedRange];
86 NSString *string = [self string];
87 int pos = range.location;
89 for (; pos > 0;) {
90 c = [string characterAtIndex: --pos];
91 if (c == '\r' || c == '\n') break;
92 if ((c == '\t' || c == ' ') && nspaces < 126) spaces[nspaces++] = c;
93 else nspaces = 0;
96 [super keyDown: event];
97 range = [self selectedRange];
98 pos = range.location;
99 string = [self string];
100 d = [string characterAtIndex: --pos]; // did a newline actually get inserted? (maybe not if using foreign language input mode)
101 c = d;
103 // autoindent
104 if(useAutoInOutdent) {
105 for (; pos > 0;) {
106 d = [string characterAtIndex: --pos];
107 if(d == '{') {
108 spaces[nspaces++] = '\t';
109 spaces[nspaces] = 0;
110 break;
112 if (d == '\r' || d == '\n' || d == '}') break;
116 if (nspaces && (c == '\r' || c == '\n')) {
117 spaces[nspaces] = 0;
119 // reverse the string
120 for (int i=0; i<nspaces/2; ++i) {
121 c = spaces[i];
122 spaces[i] = spaces[nspaces-1-i];
123 spaces[nspaces-1-i] = c;
125 NSString *newString = [NSString stringWithCharacters: spaces length: nspaces];
126 if ([self shouldChangeTextInRange: range replacementString: newString]) {
127 [self replaceCharactersInRange: range withString: newString];
128 [self didChangeText];
133 - (NSString*)currentlySelectedTextOrLine: (NSRange*) outRange
135 NSString* string = [self string];
136 NSRange selectedRange = [self selectedRange];
137 if (selectedRange.length <= 0) {
138 unsigned long lineStart, lineEnd;
139 [string getLineStart: (NSUInteger*)&lineStart end: (NSUInteger*)&lineEnd
140 contentsEnd: nil forRange:selectedRange];
141 selectedRange = NSMakeRange(lineStart, lineEnd - lineStart);
143 if (outRange) *outRange = selectedRange;
144 return [string substringWithRange: selectedRange];
147 - (NSString*)currentlySelectedText: (NSRange*) outRange
149 NSString* string = [self string];
150 NSRange selectedRange = [self selectedRange];
151 if (outRange) *outRange = selectedRange;
152 return [string substringWithRange: selectedRange];
155 - (void) keyUp: (NSEvent*) event
157 NSString *characters = [event characters];
158 if(compiledOK){
159 unsigned int modifiers = [event modifierFlags];
160 unichar character = 0;
161 if([characters length] > 0) {
162 character = [characters characterAtIndex: 0];
164 unsigned int keycode = [event keyCode];
165 PyrObject * pobj = (PyrObject*)[[self delegate] getSCObject];
166 if (pobj) {
167 pthread_mutex_lock (&gLangMutex);
168 PyrSymbol *documentclass = getsym([langClassToCall cStringUsingEncoding:[NSString defaultCStringEncoding]]);
169 PyrObject *classobj = (PyrObject*) documentclass->u.classobj;
170 if(NotNil(pobj->slots+objectKeyUpActionIndex) || NotNil(classobj->slots+keyUpActionIndex)){
171 if(compiledOK){
172 PyrSymbol *method = getsym("keyUp");
173 VMGlobals *g = gMainVMGlobals;
174 g->canCallOS = true;
175 ++g->sp; SetObject(g->sp, pobj);
176 ++g->sp; SetChar(g->sp, character);
177 ++g->sp; SetInt(g->sp, modifiers);
178 ++g->sp; SetInt(g->sp, character);
179 ++g->sp; SetInt(g->sp, keycode);
180 runInterpreter(g, method, 5);
181 g->canCallOS = false;
184 pthread_mutex_unlock (&gLangMutex);
187 if ([characters isEqual: @"\03"]) {
188 } else if (([characters isEqual: @"\n"] || [characters isEqual: @"\r"]) && !([event modifierFlags] & NSAlternateKeyMask)) {
189 } else {
190 [super keyUp: event];
194 - (void) keyDown: (NSEvent*) event
196 NSString *characters = [event characters];
197 BOOL ignoreControlKeys = NO;
198 if(compiledOK){
199 unsigned int modifiers = [event modifierFlags];
200 unichar character = 0;
201 if([characters length] > 0) {
202 character = [characters characterAtIndex: 0];
204 unsigned int keycode = [event keyCode];
206 PyrObject * pobj = (PyrObject*)[[self delegate] getSCObject];
208 if (pobj) {
209 pthread_mutex_lock (&gLangMutex);
210 PyrSymbol *documentclass = getsym([langClassToCall cStringUsingEncoding:[NSString defaultCStringEncoding]]);
211 PyrObject *classobj = (PyrObject*) documentclass->u.classobj;
212 if(NotNil(pobj->slots+objectKeyDownActionIndex) || NotNil(classobj->slots+keyDownActionIndex)){
213 if(compiledOK){
214 PyrSymbol *method = getsym("keyDown");
215 VMGlobals *g = gMainVMGlobals;
216 g->canCallOS = true;
217 ++g->sp; SetObject(g->sp, pobj);
218 ++g->sp; SetChar(g->sp, character);
219 ++g->sp; SetInt(g->sp, modifiers);
220 ++g->sp; SetInt(g->sp, character);
221 ++g->sp; SetInt(g->sp, keycode);
222 runInterpreter(g, method, 5);
223 g->canCallOS = false;
226 pthread_mutex_unlock (&gLangMutex);
229 // shift- or control-return added by hjh
230 if ([characters isEqual: @"\03"] ||
231 (([characters isEqual: @"\n"] || [characters isEqual: @"\r"])
232 && ([event modifierFlags] & (NSControlKeyMask | NSShiftKeyMask)))) {
233 [[self delegate] executeSelection: self];
234 } else if (([characters isEqual: @"\n"] || [characters isEqual: @"\r"]) && !([event modifierFlags] & NSAlternateKeyMask)) {
235 [self autoIndent: event];
236 } else {
237 //call lang
238 if([[self delegate] handleKeyDown: event]) return;
239 if(ignoreControlKeys && ([event modifierFlags] & NSCommandKeyMask)) return;
240 [super keyDown: event];
242 if ([characters isEqual: @"}"] && useAutoInOutdent) {
243 [self outdentBrack];
248 - (void)outdentBrack
250 NSString *string = [self string];
251 NSRange range = [self selectedRange];
252 int pos = range.location - 1;
253 int nspaces = 0;
254 unichar c;
256 // make sure I have only whitespace before me
257 for (; pos > 0;) {
258 c = [string characterAtIndex: --pos];
259 if (c == '\r' || c == '\n') break;
260 if ((c == '\t' || c == ' ') && nspaces < 126) nspaces++;
261 else return;
263 if(nspaces == 0) return; // bail
265 // okay now see if I have a matching bracket
266 unsigned int start, end;
267 start = end = range.location - 1;
268 int length = [string length];
269 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
270 [string getCharacters: buffer];
272 if(!blankUnparsedChars(buffer, end, false))
273 blankUnparsedChars(buffer, length, true);
275 bool res = matchBraks(&start, &end, buffer, length, '}', false);
277 if(!res) return; // bail
278 unichar spaces[128];
279 nspaces = 0;
281 for (; start > 0;) {
282 c = [string characterAtIndex: --start];
283 if (c == '\r' || c == '\n') break;
284 if ((c == '\t' || c == ' ') && nspaces < 126) spaces[nspaces++] = c;
285 else nspaces = 0;
288 c = [string characterAtIndex: pos-1];
289 range = NSMakeRange(pos + 1, range.location - (pos + 2));
291 // if (nspaces && (c == '\r' || c == '\n')) {
292 spaces[nspaces] = 0;
294 // reverse the string
295 for (int i=0; i<nspaces/2; ++i) {
296 c = spaces[i];
297 spaces[i] = spaces[nspaces-1-i];
298 spaces[nspaces-1-i] = c;
300 NSString *newString = [NSString stringWithCharacters: spaces length: nspaces];
301 if ([self shouldChangeTextInRange: range replacementString: newString]) {
302 [self replaceCharactersInRange: range withString: newString];
303 [self didChangeText];
305 // }
308 - (IBAction) executeSelection: (id) sender {
309 [[self delegate] executeSelection: sender];
312 bool matchBraks(unsigned int *startpos, unsigned int *endpos, unichar *text, int length, unichar rightBrak, bool ignoreImmediateParens);
313 //-(void)rightMouseDown:(NSEvent*)theEvent { [[self delegate] mouseDown:theEvent]; [super rightMouseDown: theEvent]; }
314 //-(void)otherMouseDown:(NSEvent*)theEvent {[[self delegate] mouseDown:theEvent]; [super otherMouseDown: theEvent]; }
315 //-(void) mouseDragged:(NSEvent*)theEvent {[[self delegate] mouseDown:theEvent]; [super mouseDragged: theEvent]; }
316 -(BOOL) dragSelectionWithEvent:(NSEvent *)event offset:(NSSize)mouseOffset slideBack:(BOOL)slideBack
318 [[self delegate] mouseDown:event];
319 [super dragSelectionWithEvent:event offset:mouseOffset slideBack:slideBack];
321 - (void) mouseDown: (NSEvent*) event
323 NSWindow *window = [self window];
324 NSPoint p = [window convertBaseToScreen: [event locationInWindow]];
325 int index = [self characterIndexForPoint: p];
326 if ([event clickCount] == 2) {
327 NSString *string = [self string];
328 int length = [string length];
329 if (index < 0 || index >= length) { goto below; }
330 unichar c = [string characterAtIndex: index];
331 if (index > 0 && (c == '\n' || c == '\r')) {
332 c = [string characterAtIndex: --index];
334 if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}') {
335 unsigned int start, end;
336 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
337 [string getCharacters: buffer];
338 if (c == '[' || c == '(' || c == '{') {
339 start = end = index + 1;
340 } else if (c == ']' || c == ')' || c == '}') {
341 start = end = index;
344 if(!blankUnparsedChars(buffer, end, false))
345 blankUnparsedChars(buffer, length, true);
347 bool res = matchBraks(&start, &end, buffer, length, 0, false);
348 free(buffer);
349 if (res) {
350 NSRange newSelectedRange = NSMakeRange(start, end - start);
351 [self setSelectedRange: newSelectedRange];
353 } else goto below;
354 } else {
355 below:
357 [super mouseDown: event];
358 [self mouseUpAction: event index: index];
359 [[self delegate] mouseDown: event];
364 extern PyrSymbol * s_mouseUp;
365 - (void) mouseUpAction: (NSEvent*) theEvent index: (int) index
367 if(!compiledOK) {
368 return;
370 NSPoint mouseLoc;
371 unsigned int modifiers = [theEvent modifierFlags];
372 mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
373 // SCPoint scpoint = SCMakePoint(mouseLoc.x, mouseLoc.y);
374 int clickCount = [theEvent clickCount];
375 int buttonNum = [theEvent buttonNumber];
376 pthread_mutex_lock (&gLangMutex);
377 PyrObject * pobj = (PyrObject*)[[self delegate] getSCObject];
378 if (pobj) {
379 VMGlobals *g = gMainVMGlobals;
380 g->canCallOS = true;
381 ++g->sp; SetObject(g->sp, pobj);
382 ++g->sp; SetInt(g->sp, mouseLoc.x);
383 ++g->sp; SetInt(g->sp, mouseLoc.y);
384 ++g->sp; SetInt(g->sp, modifiers);
385 ++g->sp; SetInt(g->sp,buttonNum);
386 ++g->sp; SetInt(g->sp,clickCount);
387 ++g->sp; SetInt(g->sp,index);
388 runInterpreter(g, s_mouseUp, 7);
389 g->canCallOS = false;
391 pthread_mutex_unlock (&gLangMutex);
393 - (void)setDefaultTabsTo:(float)value {
394 NSTextStorage *text = [self textStorage];
395 int length = [text length], i = 0;
396 NSRange range;
397 while(i < length) {
398 NSMutableParagraphStyle *style = [[text attribute: NSParagraphStyleAttributeName atIndex: i longestEffectiveRange: &range inRange: NSMakeRange(0, [text length])] mutableCopy];
399 if(style) {
400 [style setDefaultTabInterval: value];
401 [text addAttribute: NSParagraphStyleAttributeName value: style range: range];
402 [style release];
404 i = i + range.length;
408 // we need to override this because RTFfromRange converts relative links to http scheme links
409 // this makes a copy of selected text and converts links to absolute file scheme links
410 // when the target instance of MyDocument is saved it will convert any file scheme links back to relative
411 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard type:(NSString *)type {
412 BOOL res;
413 MyDocument *doc = [[NSDocumentController sharedDocumentController] documentForWindow: [self window]];
414 if(([type isEqualToString:NSRTFPboardType] || [type isEqualToString:NSRTFDPboardType]) && doc){
415 NSRange range = [self selectedRange];
416 NSMutableAttributedString *selectedText = [[[self textStorage] attributedSubstringFromRange: range] mutableCopy];
417 range.location = 0;
418 NSRange linkRange;
419 while (range.length > 0) {
420 id link = [selectedText attribute: NSLinkAttributeName
421 atIndex: range.location
422 longestEffectiveRange: &linkRange
423 inRange: range];
424 if(linkRange.length<=0) break;
425 if (link && [link isKindOfClass: [NSString class]] && (![link hasPrefix:@"SC://"] || ![link hasPrefix:@"sc://"]) && ![link isAbsolutePath]) {
426 // convert to a file:// URL
427 NSURL *newLink = [NSURL URLWithString: [link stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding] relativeToURL: [doc fileURL]];
428 newLink = [newLink absoluteURL];
429 [selectedText addAttribute: NSLinkAttributeName value: newLink range: linkRange];
431 range = NSMakeRange(NSMaxRange(linkRange), NSMaxRange(range) - NSMaxRange(linkRange));
433 if([type isEqualToString:NSRTFPboardType]){
434 res = [pboard setData:[selectedText RTFFromRange:NSMakeRange(0, [selectedText length]) documentAttributes:nil] forType:NSRTFPboardType];
435 } else if([type isEqualToString:NSRTFDPboardType]) {
436 res = [pboard setData:[selectedText RTFDFromRange:NSMakeRange(0, [selectedText length]) documentAttributes:nil] forType:NSRTFDPboardType];
437 } else {
438 res = NO;
440 [selectedText release];
441 } else {
442 res = [super writeSelectionToPasteboard:pboard type:type];
444 return res;
447 - (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender {
448 NSDragOperation sourceDragMask;
449 sourceDragMask = [sender draggingSourceOperationMask];
451 if(sourceDragMask == NSDragOperationCopy) { // we're holding the alt key
452 // block if we haven't been saved or aren't in a document
453 if(![[[NSDocumentController sharedDocumentController] documentForWindow: [self window]] fileURL]) {
454 return NSDragOperationNone;
455 } else { return NSDragOperationCopy; }
458 return [super draggingEntered:sender]; // pass on to NSTextView
461 NSString* pathOfFileRelativeToBaseDir(NSString *filepath, NSString *baseDir); // from MyDocument.M
463 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
464 NSPasteboard *pboard;
465 NSDragOperation sourceDragMask;
467 sourceDragMask = [sender draggingSourceOperationMask];
468 pboard = [sender draggingPasteboard];
470 NSPoint mouseLoc = [[self window] convertBaseToScreen:[sender draggingLocation]];
471 unsigned int charIndex = [self characterIndexForPoint:mouseLoc];
473 if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
474 NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
475 NSEnumerator *enumerator = [files objectEnumerator];
476 id anObject;
477 NSString *filesString = @"", *docDir;
478 BOOL commaSpace = NO;
479 BOOL alt = (sourceDragMask == NSDragOperationCopy);
480 // we already checked in draggingEntered if this is in a document
481 if(alt) docDir = [[[[[NSDocumentController sharedDocumentController] documentForWindow: [self window]] fileURL] path] stringByDeletingLastPathComponent];
482 while (anObject = [enumerator nextObject]) {
483 if(commaSpace) filesString = [filesString stringByAppendingString:@", "];
484 filesString = [filesString stringByAppendingString:@"\""];
485 if(alt) anObject = pathOfFileRelativeToBaseDir(anObject, docDir); // convert to relative
486 filesString = [filesString stringByAppendingString:anObject];
487 filesString = [filesString stringByAppendingString:@"\""];
488 if(alt) filesString = [filesString stringByAppendingString:@".resolveRelative"];
489 commaSpace = YES;
491 if([files count] > 1) filesString = [[@"[" stringByAppendingString:filesString] stringByAppendingString:@"]"];
493 if ([self shouldChangeTextInRange:NSMakeRange(charIndex, 0) replacementString:filesString]) {
494 [[self textStorage] replaceCharactersInRange:NSMakeRange(charIndex, 0) withString:filesString];
495 [self setSelectedRange:NSMakeRange(charIndex, [filesString length])];
496 [self didChangeText];
497 return YES;
500 return [super performDragOperation:sender];
503 #define GETTEXTCHAR(pos, text, textlen) (((int)pos<0) ? 0 : (((int)pos>=(int)textlen) ? 0 : text[pos]))
504 #define MAXBRAX 256
505 unichar braks[MAXBRAX];
506 int brakptr = 0;
508 bool checkBraks(unsigned int startpos, unsigned int endpos, unichar *text, int length);
509 bool checkBraks(unsigned int startpos, unsigned int endpos, unichar *text, int length)
511 unsigned int pos;
512 unichar c;
514 brakptr = 0;
515 pos = startpos;
516 for (; pos < endpos; ++pos) {
517 c = GETTEXTCHAR(pos, text, length);
518 if (c == 0) return false;
520 if (c == '(') {
521 if (brakptr+1 < MAXBRAX) {
522 braks[brakptr++] = ')';
523 } else return false;
524 } else if (c == '[') {
525 if (brakptr+1 < MAXBRAX) {
526 braks[brakptr++] = ']';
527 } else return false;
528 } else if (c == '{') {
529 if (brakptr+1 < MAXBRAX) {
530 braks[brakptr++] = '}';
531 } else return false;
532 } else if (c == ')' || c == ']' || c == '}') {
533 if (brakptr > 0) {
534 if (braks[--brakptr] != c) return false;
538 return brakptr == 0;
541 //bool matchBraks(unsigned int *startpos, unsigned int *endpos, unichar *text, int length, unichar rightBrak, bool ignoreImmediateParens);
542 bool matchBraks(unsigned int *startpos, unsigned int *endpos, unichar *text, int length, unichar rightBrak, bool ignoreImmediateParens)
544 unichar c, d;
546 // check selection internally
547 if (!rightBrak && *endpos > *startpos && !checkBraks(*startpos, *endpos, text, length)) return false;
549 c = GETTEXTCHAR(((*startpos)-1), text, length);
550 d = GETTEXTCHAR(*endpos, text, length);
552 if (ignoreImmediateParens) {
553 if ((c == '(' || c == '[' || c == '{') && (d == ')' || d == ']' || d == '}')) {
554 // if selection is bounded by brackets but they do not match then fail
555 if (!((c == '(' && d == ')') || (c == '[' && d == ']') || (c == '{' && d == '}'))) {
556 return false;
557 } else {
558 // else expand selection by one before searching for next outer pair
559 --(*startpos);
560 ++(*endpos);
565 brakptr = 0;
566 if (rightBrak) {
567 d = rightBrak;
570 do {
571 --(*startpos);
572 c = GETTEXTCHAR(*startpos, text, length);
573 if (c == ')') {
574 if (brakptr+1 < MAXBRAX) {
575 braks[brakptr++] = '(';
576 } else return false;
577 } else if (c == ']') {
578 if (brakptr+1 < MAXBRAX) {
579 braks[brakptr++] = '[';
580 } else return false;
581 } else if (c == '}') {
582 if (brakptr+1 < MAXBRAX) {
583 braks[brakptr++] = '{';
584 } else return false;
585 } else if (c == '(' || c == '[' || c == '{') {
586 if (brakptr > 0) {
587 if (braks[--brakptr] != c) return false;
588 } else break;
590 } while (c);
591 if (c == 0) return false;
593 if (!rightBrak) {
594 do {
595 d = GETTEXTCHAR(*endpos, text, length);
596 (*endpos)++;
597 if (d == '(') {
598 if (brakptr+1 < MAXBRAX) {
599 braks[brakptr++] = ')';
600 } else return false;
601 } else if (d == '[') {
602 if (brakptr+1 < MAXBRAX) {
603 braks[brakptr++] = ']';
604 } else return false;
605 } else if (d == '{') {
606 if (brakptr+1 < MAXBRAX) {
607 braks[brakptr++] = '}';
608 } else return false;
609 } else if (d == ')' || d == ']' || d == '}') {
610 if (brakptr > 0) {
611 if (braks[--brakptr] != d) return false;
612 } else break;
614 } while (d);
615 if (d == 0) return false;
618 if (!((c == '(' && d == ')') || (c == '[' && d == ']') || (c == '{' && d == '}'))) {
619 return false;
622 if (!rightBrak) {
623 // success. shrink selection by one.
624 ++(*startpos);
625 --(*endpos);
628 return true;
631 bool blankUnparsedChars(unichar* buffer, int length, bool process)
633 unsigned int i;
634 unichar c;
636 bool blankNext = false;
637 bool blankThis = false;
639 bool isString = false;
640 bool isSingleLineComment = false;
641 bool isMultiLineComment = false;
642 bool isSymbol = false;
644 for(i = 0;i<length;i++) {
646 c = GETTEXTCHAR(i, buffer, length);
648 if(blankNext) {
649 blankThis = true;
650 blankNext = false;
653 if(!blankThis) {
655 if(c == '/' && !isString && !isSymbol && !isSingleLineComment && !isMultiLineComment) {
656 unichar d = GETTEXTCHAR(i+1,buffer,length);
657 if(d == '/')
658 isSingleLineComment = true;
660 if(d == '*')
661 isMultiLineComment = true;
664 if(isSingleLineComment && c == '\n')
665 isSingleLineComment = false;
667 if(isMultiLineComment && c == '*')
668 if(GETTEXTCHAR(i+1,buffer,length) == '/')
669 isMultiLineComment = false;
671 if(c == '\"' && !isSingleLineComment && !isMultiLineComment && !isSymbol)
672 isString = !isString;
674 if(c == '\'' && !isSingleLineComment && !isMultiLineComment && !isString)
675 isSymbol = !isSymbol;
677 if(c == '$')
678 blankNext = true;
681 if(c == '\\')
682 blankNext = true;
685 if(process && (isString || isSingleLineComment || isMultiLineComment || isSymbol || blankThis))
687 buffer[i] = ' ';
690 blankThis = false;
693 return blankNext || isString || isSingleLineComment || isMultiLineComment || isSymbol;
696 - (void)balanceParens: (id)sender
698 NSRange selectedRange = [self selectedRange];
699 NSString *string = [self string];
701 int length = [string length];
702 unichar* buffer = (unichar*)malloc((length+1) * sizeof(unichar));
703 [string getCharacters: buffer];
705 unsigned int start, end;
706 start = selectedRange.location;
707 end = start + selectedRange.length;
709 if(!blankUnparsedChars(buffer, end, false))
710 blankUnparsedChars(buffer, length, true);
712 bool res = matchBraks(&start, &end, buffer, length, 0, true);
713 free(buffer);
714 if (res) {
715 NSRange newSelectedRange = NSMakeRange(start, end - start);
716 [self setSelectedRange: newSelectedRange];
720 - (NSArray*)completionsForPartialWordRange:(NSRange)charRange indexOfSelectedItem:(NSInteger*)index
722 if([self loadCompletionDict]){
723 NSString* queryStr = [(NSAttributedString *)[self attributedSubstringFromRange:charRange] string];
724 // create an NSArray containing all object names which match the query
725 NSMutableArray* completions;
726 completions = [[NSMutableArray alloc] init];
727 NSEnumerator * enumerator = [completionDict objectEnumerator];
728 NSString* element;
729 while(element = (NSString*)[enumerator nextObject]){
730 //post("%s hasPrefix: %s\n", element, theStr);
731 if([element hasPrefix: queryStr]){
732 [completions addObject: element];
735 //post("completionDict filtered from %u to %u entries\n", [completionDict count], [completions count]);
736 // Also append natural-language possibilities:
737 [completions addObjectsFromArray: [super completionsForPartialWordRange: charRange indexOfSelectedItem: index]];
738 return completions;
740 // no dict file, or failure - pass through to standard non-SC suggestions.
741 return [super completionsForPartialWordRange: charRange indexOfSelectedItem: index];
745 - (bool) loadCompletionDict
747 // if nsarray looks already ready, then return true
748 if(completionDict != NULL){
749 return true;
752 char* fpath = (char*)malloc(PATH_MAX);
753 if(fpath==NULL) return false;
755 sc_GetUserAppSupportDirectory(fpath, PATH_MAX);
756 strncat(fpath, "/sclang_completion_dict", PATH_MAX);
758 // if file not exists or not openable, return false
759 FILE* fp = fopen(fpath, "r");
760 if(fp==NULL){
761 free(fpath);
762 //post("couldn't open dict file :(\n");
763 return false;
766 // each line in the file becomes an entry in our array
767 char* line = (char*)malloc(100);
768 completionDict = [[NSMutableArray alloc] init];
769 while( fgets(line, 100, fp) != NULL){
770 if((line[0] != '\0') && (line[strlen(line) - 1] == '\n')){
771 line[strlen(line) - 1] = '\0'; // rm trailing newline
773 //post(line);
774 [completionDict addObject: [NSString stringWithCString: line encoding: [NSString defaultCStringEncoding]]];
777 fclose(fp);
778 free(fpath);
779 free(line);
780 //post("completionDict contains %u entries\n", [completionDict count]);
781 return true;
785 - (IBAction)openCode:(id)sender
787 [[self delegate] sendSelection: "openCodeFile"];
790 - (IBAction) showHelpFor: (id) sender
792 [[self delegate] sendSelection: "showHelp"];
795 - (IBAction)showHelpSearch:(id)sender {
796 [[self delegate] sendSelection: "showHelpSearch"];
799 - (IBAction)methodTemplates: (id)sender
801 [[self delegate] sendSelection: "methodTemplates"];
804 - (IBAction)methodReferences: (id)sender
806 [[self delegate] sendSelection: "methodReferences"];
809 - (void)print:(id)sender
811 NSPrintInfo *printInfo = [[[NSPrintInfo sharedPrintInfo] copy] autorelease];
812 if( ! printInfo)
813 return;
815 [printInfo setHorizontalPagination: NSFitPagination];
816 [printInfo setHorizontallyCentered: YES];
817 [printInfo setVerticallyCentered: NO];
818 [[NSPrintOperation printOperationWithView:self printInfo:printInfo] runOperation];
822 // redirect here to allow SCViews to get Cmd-F
823 - (void)cmdF:(id)sender {
824 [[TextFinder sharedInstance] orderFrontFindPanel:sender];
827 @end