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