1/* 2 GNUstep ProjectCenter - http://www.gnustep.org/experience/ProjectCenter.html 3 4 Copyright (C) 2002-2010 Free Software Foundation 5 6 Authors: Philippe C.D. Robert 7 Serg Stoyan 8 Riccardo Mottola 9 10 This file is part of GNUstep. 11 12 This application is free software; you can redistribute it and/or 13 modify it under the terms of the GNU General Public 14 License as published by the Free Software Foundation; either 15 version 2 of the License, or (at your option) any later version. 16 17 This application is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 Library General Public License for more details. 21 22 You should have received a copy of the GNU General Public 23 License along with this library; if not, write to the Free 24 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. 25*/ 26 27#import "PCEditor.h" 28#import "PCEditorView.h" 29 30@implementation PCEditor (UInterface) 31 32- (void)_createWindow 33{ 34 unsigned int style; 35 NSRect rect; 36 float windowWidth; 37 38// PCLogInfo(self, @"[_createWindow]"); 39 40 style = NSTitledWindowMask 41 | NSClosableWindowMask 42 | NSMiniaturizableWindowMask 43 | NSResizableWindowMask; 44 45 46 windowWidth = [[NSFont userFixedPitchFontOfSize:0.0] widthOfString:@"A"]; 47 windowWidth *= 80; 48 windowWidth += 35; 49 rect = NSMakeRect(0,0,windowWidth,320); 50 51 _window = [[NSWindow alloc] initWithContentRect:rect 52 styleMask:style 53 backing:NSBackingStoreBuffered 54 defer:YES]; 55 [_window setReleasedWhenClosed:NO]; 56 [_window setMinSize:NSMakeSize(512,320)]; 57 [_window setDelegate:self]; 58 [_window center]; 59 rect = [[_window contentView] frame]; 60 61 // Scroll view 62 _extScrollView = [[NSScrollView alloc] initWithFrame:rect]; 63 [_extScrollView setHasHorizontalScroller:NO]; 64 [_extScrollView setHasVerticalScroller:YES]; 65 [_extScrollView setAutoresizingMask: 66 (NSViewWidthSizable|NSViewHeightSizable)]; 67 rect = [[_extScrollView contentView] frame]; 68 69 // Text view 70 _extEditorView = [self _createEditorViewWithFrame:rect]; 71 72 // Include editor view 73 [_extScrollView setDocumentView:_extEditorView]; 74 [_extEditorView setNeedsDisplay:YES]; 75 RELEASE(_extEditorView); 76 77 // Include scroll view 78 [_window setContentView:_extScrollView]; 79 [_window makeFirstResponder:_extEditorView]; 80 RELEASE(_extScrollView); 81 82 // Honor "edited" state 83 [_window setDocumentEdited:_isEdited]; 84} 85 86- (void)_createInternalView 87{ 88 NSRect rect = NSMakeRect(0,0,512,320); 89 90 // Scroll view 91 _intScrollView = [[NSScrollView alloc] initWithFrame:rect]; 92 [_intScrollView setHasHorizontalScroller:NO]; 93 [_intScrollView setHasVerticalScroller:YES]; 94 [_intScrollView setBorderType:NSBezelBorder]; 95 [_intScrollView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)]; 96 rect = [[_intScrollView contentView] frame]; 97 98 // Text view 99 _intEditorView = [self _createEditorViewWithFrame:rect]; 100 101 /* 102 * Setting up ext view / scroll view / window 103 */ 104 [_intScrollView setDocumentView:_intEditorView]; 105 [_intEditorView setNeedsDisplay:YES]; 106 RELEASE(_intEditorView); 107} 108 109- (PCEditorView *)_createEditorViewWithFrame:(NSRect)fr 110{ 111 PCEditorView *ev = nil; 112 NSTextContainer *tc = nil; 113 NSLayoutManager *lm = nil; 114 115 /* 116 * setting up the objects needed to manage the view but using the 117 * shared textStorage. 118 */ 119 120 lm = [[NSLayoutManager alloc] init]; 121 tc = [[NSTextContainer alloc] initWithContainerSize:fr.size]; 122 [lm addTextContainer:tc]; 123 RELEASE(tc); 124 125 [_storage addLayoutManager:lm]; 126 RELEASE(lm); 127 128 ev = [[PCEditorView alloc] initWithFrame:fr textContainer:tc]; 129 [ev setBackgroundColor:textBackground]; 130 [ev setTextColor:textColor]; 131 [ev setEditor:self]; 132 if (_highlightSyntax) 133 { 134 [ev createSyntaxHighlighterForFileType:[_path pathExtension]]; 135 } 136 137 [ev setMinSize:NSMakeSize(0, 0)]; 138 [ev setMaxSize:NSMakeSize(1e7, 1e7)]; 139 [ev setRichText:YES]; 140 [ev setUsesFindPanel: YES]; 141 [ev setVerticallyResizable:YES]; 142 [ev setHorizontallyResizable:NO]; 143 [ev setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 144 [ev setTextContainerInset:NSMakeSize(5, 5)]; 145 [[ev textContainer] setWidthTracksTextView:YES]; 146 147 [[ev textContainer] setContainerSize:NSMakeSize(fr.size.width, 1e7)]; 148 149 [ev setEditable:_isEditable]; 150 151 // Activate undo 152 [ev setAllowsUndo: YES]; 153 154 [[NSNotificationCenter defaultCenter] 155 addObserver:self 156 selector:@selector(textDidChange:) 157 name:NSTextDidChangeNotification 158 object:ev]; 159 160 return ev; 161} 162 163@end 164 165@implementation PCEditor 166 167// =========================================================================== 168// ==== Initialization 169// =========================================================================== 170 171- (id)init 172{ 173 if ((self = [super init])) 174 { 175 _extScrollView = nil; 176 _extEditorView = nil; 177 _intScrollView = nil; 178 _intEditorView = nil; 179 _storage = nil; 180 _categoryPath = nil; 181 _window = nil; 182 183 _isEdited = NO; 184 _isWindowed = NO; 185 _isExternal = YES; 186 187 _highlightSyntax = YES; 188 189 ASSIGN(defaultFont, [PCEditorView defaultEditorFont]); 190 ASSIGN(highlightFont, [PCEditorView defaultEditorFont]); 191 ASSIGN(highlightColor, [NSColor greenColor]); 192 ASSIGN(textColor, [NSColor blackColor]); 193 ASSIGN(backgroundColor, [NSColor whiteColor]); 194 ASSIGN(readOnlyColor, [NSColor lightGrayColor]); 195 196 previousFGColor = nil; 197 previousBGColor = nil; 198 previousFont = nil; 199 200 isCharacterHighlit = NO; 201 highlited_chars[0] = -1; 202 highlited_chars[1] = -1; 203 204 undoManager = [[NSUndoManager alloc] init]; 205 } 206 207 return self; 208} 209 210- (void)dealloc 211{ 212#ifdef DEVELOPMENT 213 NSLog(@"PCEditor: %@ dealloc", [_path lastPathComponent]); 214#endif 215 216 [_extEditorView setEditor: nil]; 217 [_window setDelegate: nil]; 218 219 220 [[NSNotificationCenter defaultCenter] removeObserver:self]; 221 [_window close]; 222 223 // _window is setReleasedWhenClosed:YES 224 RELEASE(_path); 225 RELEASE(_categoryPath); 226 RELEASE(_intScrollView); 227 RELEASE(_storage); 228 RELEASE(_window); 229 230// RELEASE(parserClasses); 231 RELEASE(parserMethods); 232 RELEASE(aParser); 233 234 RELEASE(defaultFont); 235 RELEASE(highlightFont); 236 RELEASE(textColor); 237 RELEASE(backgroundColor); 238 RELEASE(readOnlyColor); 239 240 RELEASE(undoManager); 241 242 [super dealloc]; 243} 244 245// --- Protocol 246- (void)setParser:(id)parser 247{ 248// NSLog(@"RC aParser:%i parser:%i", 249// [aParser retainCount], [parser retainCount]); 250 ASSIGN(aParser, parser); 251// NSLog(@"RC aParser:%i parser:%i", 252// [aParser retainCount], [parser retainCount]); 253} 254 255- (id)openFileAtPath:(NSString *)filePath 256 editorManager:(id)editorManager 257 editable:(BOOL)editable 258{ 259 NSString *text; 260 NSAttributedString *attributedString = [NSAttributedString alloc]; 261 NSMutableDictionary *attributes = [NSMutableDictionary new]; 262 NSFont *font; 263 264 // Inform about future file opening 265 [[NSNotificationCenter defaultCenter] 266 postNotificationName:PCEditorWillOpenNotification 267 object:self]; 268 269 _editorManager = editorManager; 270 _path = [filePath copy]; 271 _isEditable = editable; 272 273 // Prepare 274 font = [NSFont userFixedPitchFontOfSize:0.0]; 275 if (editable) 276 { 277 textBackground = backgroundColor; 278 } 279 else 280 { 281 textBackground = readOnlyColor; 282 } 283 284 [attributes setObject:font forKey:NSFontAttributeName]; 285 [attributes setObject:textBackground forKey:NSBackgroundColorAttributeName]; 286 287 text = [NSString stringWithContentsOfFile:_path]; 288 attributedString = [attributedString initWithString:text attributes:attributes]; 289 290 _storage = [[NSTextStorage alloc] init]; 291 [_storage setAttributedString:attributedString]; 292 RELEASE(attributedString); 293 294// [self _createInternalView]; 295/* if (categoryPath) // category == nil if we're non project editor 296 { 297 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; 298 299 if (![[ud objectForKey:SeparateEditor] isEqualToString:@"YES"]) 300 { 301 [self _createInternalView]; 302 } 303 }*/ 304 305 // File open was finished 306 [[NSNotificationCenter defaultCenter] 307 postNotificationName:PCEditorDidOpenNotification 308 object:self]; 309 310 return self; 311} 312 313- (id)openExternalEditor:(NSString *)editor 314 withPath:(NSString *)file 315 editorManager:(id)aDelegate 316{ 317 NSTask *editorTask = nil; 318 NSArray *ea = nil; 319 NSMutableArray *args = nil; 320 NSString *app = nil; 321 322 if (!(self = [super init])) 323 { 324 return nil; 325 } 326 327 _editorManager = aDelegate; 328 _path = [file copy]; 329 330 // Task 331 ea = [editor componentsSeparatedByString:@" "]; 332 args = [NSMutableArray arrayWithArray:ea]; 333 app = [ea objectAtIndex:0]; 334 335 [[NSNotificationCenter defaultCenter] 336 addObserver:self 337 selector:@selector (externalEditorDidClose:) 338 name:NSTaskDidTerminateNotification 339 object:nil]; 340 341 editorTask = [[NSTask alloc] init]; 342 [editorTask setLaunchPath:app]; 343 [args removeObjectAtIndex:0]; 344 [args addObject:file]; 345 [editorTask setArguments:args]; 346 347 [editorTask launch]; 348// AUTORELEASE(editorTask); 349 350 // Inform about file opening 351 [[NSNotificationCenter defaultCenter] 352 postNotificationName:PCEditorDidOpenNotification 353 object:self]; 354 355 return self; 356} 357// --- Protocol End 358 359- (void)externalEditorDidClose:(NSNotification *)aNotif 360{ 361 NSString *path = [[[aNotif object] arguments] lastObject]; 362 363 if (![path isEqualToString:_path]) 364 { 365 NSLog(@"external editor task terminated"); 366 return; 367 } 368 369 NSLog(@"Our Editor task terminated"); 370 371 // Inform about closing 372 [[NSNotificationCenter defaultCenter] 373 postNotificationName:PCEditorDidCloseNotification 374 object:self]; 375} 376 377// =========================================================================== 378// ==== CodeEditor protocol 379// =========================================================================== 380 381// --- Accessor methods 382 383- (id)editorManager 384{ 385 return _editorManager; 386} 387 388- (NSWindow *)editorWindow 389{ 390 return _window; 391} 392 393- (NSView *)editorView 394{ 395 if (!_intScrollView) 396 { 397 [self _createInternalView]; 398 } 399 return _intEditorView; 400} 401 402- (NSView *)componentView 403{ 404 if (!_intScrollView) 405 { 406 [self _createInternalView]; 407 } 408 return _intScrollView; 409} 410 411- (NSString *)path 412{ 413 return _path; 414} 415 416- (void)setPath:(NSString *)path 417{ 418 NSMutableDictionary *notifDict = [[NSMutableDictionary dictionary] retain]; 419 420 // Prepare notification object 421 [notifDict setObject:self forKey:@"Editor"]; 422 [notifDict setObject:_path forKey:@"OldFile"]; 423 [notifDict setObject:path forKey:@"NewFile"]; 424 425 // Set path 426 [_path autorelease]; 427 _path = [path copy]; 428 429 // Post notification 430 [[NSNotificationCenter defaultCenter] 431 postNotificationName:PCEditorDidChangeFileNameNotification 432 object:notifDict]; 433 434 [notifDict autorelease]; 435} 436 437- (NSString *)categoryPath 438{ 439 return _categoryPath; 440} 441 442- (void)setCategoryPath:(NSString *)path 443{ 444 [_categoryPath autorelease]; 445 _categoryPath = [path copy]; 446} 447 448- (BOOL)isEdited 449{ 450 return _isEdited; 451} 452 453- (void)setIsEdited:(BOOL)yn 454{ 455 if (_window) 456 { 457 [_window setDocumentEdited:yn]; 458 } 459 _isEdited = yn; 460} 461 462- (NSImage *)fileIcon 463{ 464 NSString *fileExtension = [[_path lastPathComponent] uppercaseString]; 465 NSString *imageName = nil; 466 NSString *imagePath = nil; 467 NSBundle *bundle = nil; 468 NSImage *image = nil; 469 470 fileExtension = [[[_path lastPathComponent] pathExtension] uppercaseString]; 471 if (_isEdited) 472 { 473 imageName = [NSString stringWithFormat:@"File%@H", fileExtension]; 474 } 475 else 476 { 477 imageName = [NSString stringWithFormat:@"File%@", fileExtension]; 478 } 479 480 bundle = [NSBundle bundleForClass:NSClassFromString(@"PCEditor")]; 481 imagePath = [bundle pathForResource:imageName ofType:@"tiff"]; 482 483 image = [[NSImage alloc] initWithContentsOfFile:imagePath]; 484 485 return AUTORELEASE(image); 486} 487 488- (NSArray *)_methodsForClass:(NSString *)className 489{ 490 NSEnumerator *enumerator; 491 NSDictionary *method; 492 NSDictionary *class; 493 NSMutableArray *items = [NSMutableArray array]; 494 NSRange classRange; 495 NSRange methodRange; 496 497 ASSIGN(parserClasses, [aParser classNames]); 498 ASSIGN(parserMethods, [aParser methodNames]); 499 500 enumerator = [parserClasses objectEnumerator]; 501 while ((class = [enumerator nextObject])) 502 { 503 if ([[class objectForKey:@"ClassName"] isEqualToString:className]) 504 { 505 classRange = NSRangeFromString([class objectForKey:@"ClassBodyRange"]); 506 break; 507 } 508 } 509 510 methodRange = NSMakeRange(0, 0); 511 enumerator = [parserMethods objectEnumerator]; 512 while ((method = [enumerator nextObject])) 513 { 514 // NSLog(@"Method> %@", method); 515 methodRange = NSRangeFromString([method objectForKey:@"MethodBodyRange"]); 516 if (NSIntersectionRange(classRange, methodRange).length != 0) 517 { 518 [items addObject:[method objectForKey:@"MethodName"]]; 519 } 520 } 521 522 return items; 523} 524 525- (NSArray *)browserItemsForItem:(NSString *)item 526{ 527 NSEnumerator *enumerator; 528// NSDictionary *method; 529 NSDictionary *class; 530 NSMutableArray *items = [NSMutableArray array]; 531 532 NSLog(@"PCEditor: asked for browser items for: %@", item); 533 534 [aParser setString:[_storage string]]; 535 536 // If item is .m or .h file show class list 537 if ([[item pathExtension] isEqualToString:@"m"] 538 || [[item pathExtension] isEqualToString:@"h"]) 539 { 540 ASSIGN(parserClasses, [aParser classNames]); 541 542 enumerator = [parserClasses objectEnumerator]; 543 while ((class = [enumerator nextObject])) 544 { 545 NSLog(@"Class> %@", class); 546 [items addObject:[class objectForKey:@"ClassName"]]; 547 } 548 } 549 550 // If item starts with "@" show method list 551 if ([[item substringToIndex:1] isEqualToString:@"@"]) 552 { 553/* ASSIGN(parserMethods, [aParser methodNames]); 554 555 enumerator = [parserMethods objectEnumerator]; 556 while ((method = [enumerator nextObject])) 557 { 558 // NSLog(@"Method> %@", method); 559 [items addObject:[method objectForKey:@"MethodName"]]; 560 }*/ 561 return [self _methodsForClass:item]; 562 } 563 564 return items; 565} 566 567- (void)show 568{ 569 if (_isWindowed) 570 { 571 [_window makeKeyAndOrderFront:nil]; 572 } 573} 574 575- (void)setWindowed:(BOOL)yn 576{ 577 if ( (yn && _isWindowed) || (!yn && !_isWindowed) ) 578 { 579 return; 580 } 581 582 if (yn && !_isWindowed) 583 { 584 [self _createWindow]; 585 [_window setTitle:[NSString stringWithFormat: @"%@", 586 [_path stringByAbbreviatingWithTildeInPath]]]; 587 } 588 else if (!yn && _isWindowed) 589 { 590 [_window close]; 591 } 592 593 _isWindowed = yn; 594} 595 596- (BOOL)isWindowed 597{ 598 return _isWindowed; 599} 600 601// --- Object managment 602 603- (BOOL)saveFileIfNeeded 604{ 605 if ((_isEdited)) 606 { 607 return [self saveFile]; 608 } 609 610 return YES; 611} 612 613- (BOOL)saveFile 614{ 615 BOOL saved = NO; 616 617 if (_isEdited == NO) 618 { 619 return YES; 620 } 621 622 [[NSNotificationCenter defaultCenter] 623 postNotificationName:PCEditorWillSaveNotification 624 object:self]; 625 626 // Send the notification to Gorm... 627 if([[_path pathExtension] isEqual: @"h"]) 628 { 629 [[NSDistributedNotificationCenter defaultCenter] 630 postNotificationName: @"GormParseClassNotification" 631 object: _path]; 632 } 633 634 saved = [[_storage string] writeToFile:_path atomically:YES]; 635 636 if (saved == YES) 637 { 638 [self setIsEdited:NO]; 639 [[NSNotificationCenter defaultCenter] 640 postNotificationName:PCEditorDidSaveNotification 641 object:self]; 642 } 643 else 644 { 645 NSRunAlertPanel(@"Save File", 646 @"Couldn't save file '%@'!", 647 @"OK", nil, nil, [_path lastPathComponent]); 648 } 649 650 return saved; 651} 652 653- (BOOL)saveFileTo:(NSString *)path 654{ 655 return [[_storage string] writeToFile:path atomically:YES]; 656} 657 658- (BOOL)revertFileToSaved 659{ 660 NSString *text = [NSString stringWithContentsOfFile:_path]; 661 NSAttributedString *as = nil; 662 NSDictionary *at = nil; 663 NSFont *ft = nil; 664 665 if (_isEdited == NO) 666 { 667 return YES; 668 } 669 670 if (NSAlertDefaultReturn != 671 NSRunAlertPanel(@"Revert", 672 @"%@ has been modified. " 673 @"Are you sure you want to undo changes?", 674 @"Revert", @"Cancel", nil, 675 [_path lastPathComponent])) 676 { 677 return NO; 678 } 679 680 [[NSNotificationCenter defaultCenter] 681 postNotificationName:PCEditorWillRevertNotification 682 object:self]; 683 684 // This is temporary 685 ft = [NSFont userFixedPitchFontOfSize:0.0]; 686 at = [NSDictionary dictionaryWithObject:ft forKey:NSFontAttributeName]; 687 as = [[NSAttributedString alloc] initWithString:text attributes:at]; 688 689 [self setIsEdited:NO]; 690 691 // Operate on the text storage! 692 [_storage setAttributedString:as]; 693 RELEASE(as); 694 695 [_intEditorView setNeedsDisplay:YES]; 696 [_extEditorView setNeedsDisplay:YES]; 697 698 [[NSNotificationCenter defaultCenter] 699 postNotificationName:PCEditorDidRevertNotification 700 object:self]; 701 702 return YES; 703} 704 705// FIXME: Do we really need this method? 706- (BOOL)closeFile:(id)sender save:(BOOL)save 707{ 708 if (save == YES) 709 { 710 [self saveFileIfNeeded]; 711 } 712 713 // Close window first if visible 714 if (_isWindowed && [_window isVisible] && (sender != _window)) 715 { 716 [_window close]; 717 } 718 719 // Inform about closing 720 [[NSNotificationCenter defaultCenter] 721 postNotificationName:PCEditorDidCloseNotification 722 object:self]; 723 724 return YES; 725} 726 727- (BOOL)close:(id)sender 728{ 729 if ([self editorShouldClose] == YES) 730 { 731 // Close window first if visible 732 if (_isWindowed && [_window isVisible] && (sender != _window)) 733 { 734 [_window close]; 735 } 736 737 // Inform about closing 738 [[NSNotificationCenter defaultCenter] 739 postNotificationName:PCEditorDidCloseNotification 740 object:self]; 741 742 return YES; 743 } 744 745 return NO; 746} 747 748- (BOOL)editorShouldClose 749{ 750 if (_isEdited) 751 { 752 int ret; 753 754 if (_isWindowed && [_window isVisible]) 755 { 756 [_window makeKeyAndOrderFront:self]; 757 } 758 759 ret = NSRunAlertPanel(@"Close File", 760 @"File %@ has been modified. Save?", 761 @"Save and Close", @"Don't save", @"Cancel", 762 [_path lastPathComponent]); 763 switch (ret) 764 { 765 case NSAlertDefaultReturn: // Save And Close 766 if ([self saveFile] == NO) 767 { 768 return NO; 769 } 770 break; 771 772 case NSAlertAlternateReturn: // Don't save 773 break; 774 775 case NSAlertOtherReturn: // Cancel 776 return NO; 777 break; 778 } 779 780 [self setIsEdited:NO]; 781 } 782 783 return YES; 784} 785 786// =========================================================================== 787// ==== Window delegate 788// =========================================================================== 789 790- (BOOL)windowShouldClose:(id)sender 791{ 792 if ([sender isEqual:_window]) 793 { 794 if (_intScrollView) 795 { 796 // Just close if this file also displayed in int view 797 _isWindowed = NO; 798 return YES; 799 } 800 else 801 { 802 return [self close:sender]; 803 } 804 } 805 806 return NO; 807} 808 809- (void)windowDidBecomeKey:(NSNotification *)aNotification 810{ 811/* if ([[aNotification object] isEqual:_window] && [_window isVisible]) 812 { 813 [_window makeFirstResponder:_extEditorView]; 814 }*/ 815} 816 817- (void)windowDidResignKey:(NSNotification *)aNotification 818{ 819/* if ([[aNotification object] isEqual:_window] && [_window isVisible]) 820 { 821 [_window makeFirstResponder:_extEditorView]; 822 }*/ 823 [self resignFirstResponder:_extEditorView]; 824} 825 826 827- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window 828{ 829 return undoManager; 830} 831 832 833// =========================================================================== 834// ==== TextView (_intEditorView, _extEditorView) delegate 835// =========================================================================== 836 837- (void)textDidChange:(NSNotification *)aNotification 838{ 839 id object = [aNotification object]; 840 841 if ([object isKindOfClass:[PCEditorView class]] 842 && (object == _intEditorView || object == _extEditorView)) 843 { 844 if (_isEdited == NO) 845 { 846 [[NSNotificationCenter defaultCenter] 847 postNotificationName:PCEditorWillChangeNotification 848 object:self]; 849 850 [self setIsEdited:YES]; 851 852 [[NSNotificationCenter defaultCenter] 853 postNotificationName:PCEditorDidChangeNotification 854 object:self]; 855 } 856 } 857} 858 859- (void)textViewDidChangeSelection:(NSNotification *)notification 860{ 861 if (editorTextViewIsPressingKey == NO) 862 { 863 id object; 864 865 object = [notification object]; 866 if (object == _intEditorView || object == _extEditorView) 867 [self computeNewParenthesisNesting: object]; 868 } 869} 870 871- (void)editorTextViewWillPressKey:sender 872{ 873 editorTextViewIsPressingKey = YES; 874// NSLog(@"Will pressing key"); 875 876 if (sender == _intEditorView || sender == _extEditorView) 877 [self unhighlightCharacter: sender]; 878 else 879 NSLog(@"PCEditor: unexpected sender"); 880} 881 882- (void)editorTextViewDidPressKey:sender 883{ 884// NSLog(@"Did pressing key"); 885 if (sender == _intEditorView || sender == _extEditorView) 886 [self computeNewParenthesisNesting: sender]; 887 else 888 NSLog(@"PCEditor: unexpected sender"); 889 890 editorTextViewIsPressingKey = NO; 891} 892 893- (BOOL)becomeFirstResponder:(PCEditorView *)view 894{ 895 [[NSNotificationCenter defaultCenter] 896 postNotificationName:PCEditorDidBecomeActiveNotification 897 object:self]; 898 899 return YES; 900} 901 902- (BOOL)resignFirstResponder:(PCEditorView *)view 903{ 904 [[NSNotificationCenter defaultCenter] 905 postNotificationName:PCEditorDidResignActiveNotification 906 object:self]; 907 908 return YES; 909} 910 911// =========================================================================== 912// ==== Parser and scrolling 913// =========================================================================== 914 915// === Scrolling 916 917- (void)fileStructureItemSelected:(NSString *)item 918{ 919 NSString *firstSymbol; 920 921 NSLog(@"[PCEditor] selected file structure item: %@", item); 922 923 firstSymbol = [item substringToIndex:1]; 924 if ([firstSymbol isEqualToString:@"@"]) // class selected 925 { 926 [self scrollToClassName:item]; 927 } 928 else if ([firstSymbol isEqualToString:@"-"] // method selected 929 || [firstSymbol isEqualToString:@"+"]) 930 { 931 [self scrollToMethodName:item]; 932 } 933} 934 935- (void)scrollToClassName:(NSString *)className 936{ 937 NSEnumerator *enumerator = nil; 938 NSDictionary *class = nil; 939 NSRange classNameRange; 940 941 NSLog(@"SCROLL to class: \"%@\"", className); 942 943 classNameRange = NSMakeRange(0, 0); 944 enumerator = [parserClasses objectEnumerator]; 945 while ((class = [enumerator nextObject])) 946 { 947 if ([[class objectForKey:@"ClassName"] isEqualToString:className]) 948 { 949 classNameRange = 950 NSRangeFromString([class objectForKey:@"ClassNameRange"]); 951 break; 952 } 953 } 954 955 NSLog(@"classNameRange: %@", NSStringFromRange(classNameRange)); 956 if (classNameRange.length != 0) 957 { 958 [_intEditorView setSelectedRange:classNameRange]; 959 [_intEditorView scrollRangeToVisible:classNameRange]; 960 } 961} 962 963- (void)scrollToMethodName:(NSString *)methodName 964{ 965 NSEnumerator *enumerator = nil; 966 NSDictionary *method = nil; 967 NSRange methodNameRange; 968 969 NSLog(@"SCROLL to method: \"%@\"", methodName); 970 971 methodNameRange = NSMakeRange(0, 0); 972 enumerator = [parserMethods objectEnumerator]; 973 while ((method = [enumerator nextObject])) 974 { 975 if ([[method objectForKey:@"MethodName"] isEqualToString:methodName]) 976 { 977 methodNameRange = 978 NSRangeFromString([method objectForKey:@"MethodNameRange"]); 979 break; 980 } 981 } 982 983 NSLog(@"methodNameRange: %@", NSStringFromRange(methodNameRange)); 984 if (methodNameRange.length != 0) 985 { 986 [_intEditorView setSelectedRange:methodNameRange]; 987 [_intEditorView scrollRangeToVisible:methodNameRange]; 988 } 989} 990 991- (void)scrollToLineNumber:(NSUInteger)lineNumber 992{ 993 [_intEditorView goToLineNumber:lineNumber]; 994 [_extEditorView goToLineNumber:lineNumber]; 995} 996 997@end 998 999// =========================================================================== 1000// ==== Menu actions 1001// =========================================================================== 1002@implementation PCEditor (Menu) 1003 1004- (void)pipeOutputOfCommand:(NSString *)command 1005{ 1006 NSTask * task; 1007 NSPipe * inPipe, * outPipe; 1008 NSString * inString, * outString; 1009 NSFileHandle * inputHandle; 1010 1011 inString = [[_intEditorView string] substringWithRange: 1012 [_intEditorView selectedRange]]; 1013 inPipe = [NSPipe pipe]; 1014 outPipe = [NSPipe pipe]; 1015 1016 task = [[NSTask new] autorelease]; 1017 1018 [task setLaunchPath: @"/bin/sh"]; 1019 [task setArguments: [NSArray arrayWithObjects: @"-c", command, nil]]; 1020 [task setStandardInput: inPipe]; 1021 [task setStandardOutput: outPipe]; 1022 [task setStandardError: outPipe]; 1023 1024 inputHandle = [inPipe fileHandleForWriting]; 1025 1026 [task launch]; 1027 [inputHandle writeData: [inString 1028 dataUsingEncoding: NSUTF8StringEncoding]]; 1029 [inputHandle closeFile]; 1030 [task waitUntilExit]; 1031 outString = [[[NSString alloc] 1032 initWithData: [[outPipe fileHandleForReading] availableData] 1033 encoding: NSUTF8StringEncoding] 1034 autorelease]; 1035 if ([task terminationStatus] != 0) 1036 { 1037 if (NSRunAlertPanel(_(@"Error running command"), 1038 _(@"The command returned with a non-zero exit status" 1039 @" -- aborting pipe.\n" 1040 @"Do you want to see the command's output?\n"), 1041 _(@"No"), _(@"Yes"), nil) == NSAlertAlternateReturn) 1042 { 1043 NSRunAlertPanel(_(@"The command's output"), 1044 outString, nil, nil, nil); 1045 } 1046 } 1047 else 1048 { 1049 [_intEditorView replaceCharactersInRange:[_intEditorView selectedRange] 1050 withString:outString]; 1051 [self textDidChange: nil]; 1052 } 1053} 1054 1055- (void)findNext:sender 1056{ 1057// [[TextFinder sharedInstance] findNext:self]; 1058} 1059 1060- (void)findPrevious:sender 1061{ 1062// [[TextFinder sharedInstance] findPrevious:self]; 1063} 1064 1065- (void)jumpToSelection:sender 1066{ 1067 [_intEditorView scrollRangeToVisible:[_intEditorView selectedRange]]; 1068} 1069 1070@end 1071 1072// =========================================================================== 1073// ==== Parenthesis highlighting 1074// =========================================================================== 1075 1076/** 1077 * Checks whether a character is a delimiter. 1078 * 1079 * This function checks whether `character' is a delimiter character, 1080 * (i.e. one of "(", ")", "[", "]", "{", "}") and returns YES if it 1081 * is and NO if it isn't. Additionaly, if `character' is a delimiter, 1082 * `oppositeDelimiter' is set to a string denoting it's opposite 1083 * delimiter and `searchBackwards' is set to YES if the opposite 1084 * delimiter is located before the checked delimiter character, or 1085 * to NO if it is located after the delimiter character. 1086 */ 1087static inline BOOL CheckDelimiter(unichar character, 1088 unichar * oppositeDelimiter, 1089 BOOL * searchBackwards) 1090{ 1091 if (character == '(') 1092 { 1093 *oppositeDelimiter = ')'; 1094 *searchBackwards = NO; 1095 1096 return YES; 1097 } 1098 else if (character == ')') 1099 { 1100 *oppositeDelimiter = '('; 1101 *searchBackwards = YES; 1102 1103 return YES; 1104 } 1105 else if (character == '[') 1106 { 1107 *oppositeDelimiter = ']'; 1108 *searchBackwards = NO; 1109 1110 return YES; 1111 } 1112 else if (character == ']') 1113 { 1114 *oppositeDelimiter = '['; 1115 *searchBackwards = YES; 1116 1117 return YES; 1118 } 1119 else if (character == '{') 1120 { 1121 *oppositeDelimiter = '}'; 1122 *searchBackwards = NO; 1123 1124 return YES; 1125 } 1126 else if (character == '}') 1127 { 1128 *oppositeDelimiter = '{'; 1129 *searchBackwards = YES; 1130 1131 return YES; 1132 } 1133 else 1134 { 1135 return NO; 1136 } 1137} 1138 1139/** 1140 * Attempts to find a delimiter in a certain string around a certain location. 1141 * 1142 * Attempts to locate `delimiter' in `string', starting at 1143 * location `startLocation' a searching forwards (backwards if 1144 * searchBackwards = YES) at most 1000 characters. The argument 1145 * `oppositeDelimiter' denotes what is considered to be the opposite 1146 * delimiter of the one being search for, so that nested delimiters 1147 * are ignored correctly. 1148 * 1149 * @return The location of the delimiter if it is found, or NSNotFound 1150 * if it isn't. 1151 */ 1152NSUInteger FindDelimiterInString(NSString * string, 1153 unichar delimiter, 1154 unichar oppositeDelimiter, 1155 NSUInteger startLocation, 1156 BOOL searchBackwards) 1157{ 1158 NSUInteger i; 1159 NSUInteger length; 1160 unichar (*charAtIndex)(id, SEL, NSUInteger); 1161 SEL sel = @selector(characterAtIndex:); 1162 int nesting = 1; 1163 1164 charAtIndex = (unichar (*)(id, SEL, NSUInteger)) [string 1165 methodForSelector: sel]; 1166 1167 if (searchBackwards) 1168 { 1169 if (startLocation < 1000) 1170 length = startLocation; 1171 else 1172 length = 1000; 1173 1174 for (i=1; i <= length; i++) 1175 { 1176 unichar c; 1177 1178 c = charAtIndex(string, sel, startLocation - i); 1179 if (c == delimiter) 1180 nesting--; 1181 else if (c == oppositeDelimiter) 1182 nesting++; 1183 1184 if (nesting == 0) 1185 break; 1186 } 1187 1188 if (i > length) 1189 return NSNotFound; 1190 else 1191 return startLocation - i; 1192 } 1193 else 1194 { 1195 if ([string length] < startLocation + 1000) 1196 length = [string length] - startLocation; 1197 else 1198 length = 1000; 1199 1200 for (i=1; i < length; i++) 1201 { 1202 unichar c; 1203 1204 c = charAtIndex(string, sel, startLocation + i); 1205 if (c == delimiter) 1206 nesting--; 1207 else if (c == oppositeDelimiter) 1208 nesting++; 1209 1210 if (nesting == 0) 1211 break; 1212 } 1213 1214 if (i == length) 1215 return NSNotFound; 1216 else 1217 return startLocation + i; 1218 } 1219} 1220 1221@implementation PCEditor (Parenthesis) 1222 1223- (void)unhighlightCharacter: (NSTextView *)editorView 1224{ 1225 int i; 1226 NSTextStorage *textStorage = [editorView textStorage]; 1227 1228 [textStorage beginEditing]; 1229 1230// if (isCharacterHighlit) 1231 for (i = 0; i < 2 && highlited_chars[i] != -1; i++) 1232 { 1233 NSRange r = NSMakeRange(highlited_chars[i], 1); 1234// NSRange r = NSMakeRange(highlitCharacterLocation, i); 1235 1236 1237 isCharacterHighlit = NO; 1238 1239 // restore the character's color and font attributes 1240 if (previousFont != nil) 1241 { 1242 [textStorage addAttribute:NSFontAttributeName 1243 value:previousFont 1244 range:r]; 1245 } 1246 else 1247 { 1248 [textStorage removeAttribute:NSFontAttributeName range:r]; 1249 } 1250 1251 if (previousFGColor != nil) 1252 { 1253 [textStorage addAttribute:NSForegroundColorAttributeName 1254 value:previousFGColor 1255 range:r]; 1256 } 1257 else 1258 { 1259 [textStorage removeAttribute:NSForegroundColorAttributeName 1260 range:r]; 1261 } 1262 1263 if (previousBGColor != nil) 1264 { 1265 [textStorage addAttribute:NSBackgroundColorAttributeName 1266 value:previousBGColor 1267 range:r]; 1268 } 1269 else 1270 { 1271 [textStorage removeAttribute:NSBackgroundColorAttributeName 1272 range:r]; 1273 } 1274 1275 highlited_chars[i] = -1; 1276 } 1277 1278 [textStorage endEditing]; 1279} 1280 1281- (void)highlightCharacterAt:(NSUInteger)location inEditor: (NSTextView *)editorView 1282{ 1283 int i; 1284 1285 for (i = 0; i < 2 && highlited_chars[i] != -1; i++) {}; 1286 1287// if (isCharacterHighlit == NO) 1288 if (i < 2) 1289 { 1290 NSTextStorage *textStorage = [editorView textStorage]; 1291 NSRange r = NSMakeRange(location, 1); 1292 NSRange tmp; 1293 1294// NSLog(@"highlight"); 1295 1296// highlitCharacterLocation = location; 1297 highlited_chars[i] = location; 1298 1299 isCharacterHighlit = YES; 1300 NSAssert(textStorage, @"textstorage can't be nil"); 1301 [textStorage beginEditing]; 1302 1303 // store the previous character's attributes 1304 ASSIGN(previousFGColor, 1305 [textStorage attribute:NSForegroundColorAttributeName 1306 atIndex:location 1307 effectiveRange:&tmp]); 1308 ASSIGN(previousBGColor, 1309 [textStorage attribute:NSBackgroundColorAttributeName 1310 atIndex:location 1311 effectiveRange:&tmp]); 1312 ASSIGN(previousFont, [textStorage attribute:NSFontAttributeName 1313 atIndex:location 1314 effectiveRange:&tmp]); 1315 1316 [textStorage addAttribute:NSFontAttributeName 1317 value:highlightFont 1318 range:r]; 1319 [textStorage addAttribute:NSBackgroundColorAttributeName 1320 value:highlightColor 1321 range:r]; 1322/* [textStorage addAttribute:NSForegroundColorAttributeName 1323 value:highlightColor 1324 range:r]; 1325 1326 [textStorage removeAttribute:NSBackgroundColorAttributeName 1327 range:r];*/ 1328 1329 [textStorage endEditing]; 1330 } 1331} 1332 1333- (void)computeNewParenthesisNesting: (NSTextView *)editorView 1334{ 1335 NSRange selectedRange; 1336 NSString *myString; 1337 1338 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DontTrackNesting"]) 1339 { 1340 return; 1341 } 1342 1343 NSAssert(editorView, @"computeNewParenthesis: editorView is nil"); 1344 selectedRange = [editorView selectedRange]; 1345 1346 // make sure we un-highlight a previously highlit delimiter 1347 [self unhighlightCharacter :editorView]; 1348 1349 // if we have a character at the selected location, check 1350 // to see if it is a delimiter character 1351 myString = [editorView string]; 1352 if (selectedRange.length <= 1 && [myString length] > selectedRange.location) 1353 { 1354 unichar c; 1355 unichar oppositeDelimiter = 0; 1356 BOOL searchBackwards = NO; 1357 1358 c = [myString characterAtIndex:selectedRange.location]; 1359 1360 // if it is, search for the opposite delimiter in a range 1361 // of at most 1000 characters around it in either forward 1362 // or backward direction (depends on the kind of delimiter 1363 // we're searching for). 1364 if (CheckDelimiter(c, &oppositeDelimiter, &searchBackwards)) 1365 { 1366 NSUInteger result; 1367 1368 result = FindDelimiterInString(myString, 1369 oppositeDelimiter, 1370 c, 1371 selectedRange.location, 1372 searchBackwards); 1373 1374 // and in case a delimiter is found, highlight it 1375 if (result != NSNotFound) 1376 { 1377 [self highlightCharacterAt:selectedRange.location inEditor:editorView]; 1378 [self highlightCharacterAt:result inEditor:editorView]; 1379 } 1380 } 1381 } 1382} 1383 1384@end 1385 1386