1/*
2 * DiffWindowController.m
3 *
4 * Copyright (c) 2002 Pierre-Yves Rivaille <pyrivail@ens-lyon.fr>
5 *
6 * This file is part of EasyDiff.app.
7 *
8 * EasyDiff.app is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * EasyDiff.app is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with EasyDiff.app; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 *
22 */
23
24#import <AppKit/AppKit.h>
25//#include "DiffTextView.h"
26//#include "DiffMiddleView.h"
27#import "DiffView.h"
28#import "DiffWindowController.h"
29#import "DiffWrapper.h"
30
31//extern void tasktest(NSString *file1, NSString *file2, NSMutableArray **r1, NSMutableArray **r2);
32
33
34@interface PatchData : NSObject
35{
36@public
37  NSMutableString *patchString;
38  int start;
39  int end;
40  int lineDifference;
41}
42@end
43
44@implementation PatchData
45@end
46
47
48
49
50@interface DiffWindowController (Private)
51- (void) diffViewFrameDidChange: (NSNotification*) aNotification;
52- (id) _initWithFilename: (NSString *) filename1
53	     andFilename: (NSString *) filename2;
54- (NSMutableString *) _patchMutableString: (int) direction;
55- (void) saveMergedAt: (NSString *) mergedName;
56@end
57
58@implementation DiffWindowController
59
60
61- (id) initWithFilename: (NSString *) filename1
62	andTempFilename: (NSString *) filename2
63{
64  /* Switch filenames so the CVS version is on the left */
65  [self _initWithFilename: filename2
66	andFilename: filename1];
67
68  tempFilename = filename2;
69
70  [[self window] setTitle:
71		   [NSString
72		     stringWithFormat:
73		       @"Comparing %@ to the CVS version",
74		     [filename1 lastPathComponent]]];
75
76  [[self window] makeKeyAndOrderFront: self];
77
78  leftFileName = nil;
79  rightFileName = RETAIN(filename1);
80
81  return self;
82}
83
84- (id) initWithFilename: (NSString *) filename1
85	    andFilename: (NSString *) filename2
86{
87  [self _initWithFilename: filename1
88	andFilename: filename2];
89
90  tempFilename = nil;
91
92  [[self window] setTitle:
93		   [NSString
94		     stringWithFormat:
95		       @"Comparing %@ to %@",
96		     [filename1 lastPathComponent],
97		     [filename2 lastPathComponent]]];
98
99
100  [[self window] makeKeyAndOrderFront: self];
101
102  /*
103  [NSApp addWindowsItem: [self window]
104	 title: [[self window] title]
105	 filename: NO];
106  */
107
108  leftFileName = RETAIN(filename1);
109  rightFileName = RETAIN(filename2);
110
111  NSDebugLog(@"windowsMenu %@", [NSApp windowsMenu]);
112
113  return self;
114}
115
116
117- (id) _initWithFilename: (NSString *) filename1
118	     andFilename: (NSString *) filename2
119{
120  [self initWithWindowNibName: @"window"];
121
122  [self window];
123
124
125  NSDebugLog(@"%@", scroller);
126  [scroller setArrowsPosition: NSScrollerArrowsMaxEnd];
127  [scroller setFloatValue:0.5 knobProportion:0.2];
128  [scroller setEnabled: YES];
129
130  diffWrapper = [[DiffWrapper alloc] initWithFilename: filename1
131				     andFilename: filename2];
132
133  [diffWrapper compare];
134
135  leftChanges = [diffWrapper leftChanges];
136  rightChanges = [diffWrapper rightChanges];
137  //  tasktest(filename1, filename2, &leftChanges, &rightChanges);
138
139  RETAIN(leftChanges);
140  RETAIN(rightChanges);
141
142  {
143    NSString *string1;
144    NSString *string2;
145
146    string1 = [NSString stringWithContentsOfFile:
147			 filename1];
148    string2 = [NSString stringWithContentsOfFile:
149			 filename2];
150
151
152    [diffView setLeftString: string1];
153    [diffView setRightString: string2];
154
155    [diffView setLeftChanges: leftChanges
156	      andRightChanges: rightChanges];
157
158    [diffView setLeftLineRanges: [diffWrapper leftLineRanges]
159	      andRightLineRanges: [diffWrapper rightLineRanges]];
160
161    [scroller setFloatValue: 0.
162	      knobProportion:
163		([diffView frame].size.height /
164		 [diffView mergeFileHeight])];
165
166    [diffView tile];
167
168    [diffView setPostsFrameChangedNotifications: YES];
169    [[NSNotificationCenter defaultCenter]
170      addObserver: self
171      selector: @selector(diffViewFrameDidChange:)
172      name: NSViewFrameDidChangeNotification
173      object: diffView];
174
175    choices = (int *) calloc([leftChanges count], sizeof(int));
176
177  }
178
179  //  NSLog(@"gonna orderFront %@",[self window]);
180  [self diffViewFrameDidChange: nil];
181  //  [[self window] orderFrontRegardless];
182
183  return self;
184
185}
186
187- (void) diffViewFrameDidChange: (NSNotification*) aNotification
188{
189
190  [diffView computeScrollerSize];
191
192  if ([diffView frame].size.height >= [diffView mergeFileHeight])
193    {
194      [scroller setEnabled: NO];
195    }
196  else
197    {
198      [scroller setEnabled: YES];
199      [scroller setFloatValue: [scroller floatValue]
200		knobProportion:
201		  ([diffView frame].size.height /
202		   [diffView mergeFileHeight])];
203      [diffView doScroll: scroller];
204    }
205}
206
207- (void) windowWillClose: (id) sender
208{
209  NSDebugLog(@"windowWillClose called");
210  //  RELEASE([self window]);
211  AUTORELEASE(self);
212}
213
214- (void) dealloc
215{
216  NSDebugLog(@"dealloc called");
217  if (tempFilename != nil)
218    {
219      if ([[NSFileManager defaultManager]
220	    removeFileAtPath: tempFilename
221	    handler: nil] == NO)
222	NSLog(@"We could not delete %@", tempFilename);
223    }
224  TEST_RELEASE(leftFileName);
225  TEST_RELEASE(rightFileName);
226  [super dealloc];
227}
228
229
230- (void) matrixButtonClicked: (id) sender
231{
232  int oldChoice = choices[[sender tag]];
233
234  NSDebugLog(@"matrixButtonClicked %d, %d", [sender tag],
235	     [sender selectedColumn]);
236
237
238  choices[[sender tag]] = [sender selectedColumn] - 1;
239
240  if ((oldChoice == -1) && (choices[[sender tag]] != -1))
241    {
242      [[diffView leftTextView] setColor:
243				 [NSColor colorWithDeviceRed:0.7
244					   green:0.7
245					   blue:1.
246					   alpha:1.]
247			       forChangeNumber: [sender tag]];
248    }
249  else if ((oldChoice != -1) && (choices[[sender tag]] == -1))
250    {
251      [[diffView leftTextView] setColor:
252				 [NSColor whiteColor]
253			       forChangeNumber: [sender tag]];
254    }
255
256  if ((oldChoice == 1) && (choices[[sender tag]] != 1))
257    {
258      [[diffView rightTextView] setColor:
259				  [NSColor colorWithDeviceRed:0.7
260					    green:0.7
261					    blue:1.
262					    alpha:1.]
263				forChangeNumber: [sender tag]];
264    }
265  else if ((oldChoice != 1) && (choices[[sender tag]] == 1))
266    {
267      [[diffView rightTextView] setColor:
268				  [NSColor whiteColor]
269				forChangeNumber: [sender tag]];
270    }
271}
272
273- (void) selectAllLeftChanges: (id) sender
274{
275  int i;
276  NSArray *matrixArray = [[diffView middleView] matrixArray];
277  int count = [matrixArray count];
278
279  for ( i = 0; i < count; i ++ )
280    {
281      if (choices[i] != - 1)
282	{
283	  choices[i] = -1;
284	  [[diffView leftTextView] setColor:
285				     [NSColor whiteColor]
286				   forChangeNumber: i];
287	  [[diffView rightTextView] setColor:
288				  [NSColor colorWithDeviceRed:0.7
289					    green:0.7
290					    blue:1.
291					    alpha:1.]
292				forChangeNumber: i];
293	  [[matrixArray objectAtIndex: i] selectCellAtRow: 0 column:0];
294	}
295    }
296}
297
298- (void) selectAllRightChanges: (id) sender
299{
300  int i;
301  NSArray *matrixArray = [[diffView middleView] matrixArray];
302  int count = [matrixArray count];
303
304  for ( i = 0; i < count; i ++ )
305    {
306      if (choices[i] != 1)
307	{
308	  choices[i] = 1;
309	  [[diffView rightTextView] setColor:
310				     [NSColor whiteColor]
311				   forChangeNumber: i];
312	  [[diffView leftTextView] setColor:
313				  [NSColor colorWithDeviceRed:0.7
314					    green:0.7
315					    blue:1.
316					    alpha:1.]
317				forChangeNumber: i];
318	  [[matrixArray objectAtIndex: i] selectCellAtRow: 0 column:2];
319	}
320    }
321}
322
323
324- (void) selectNoChanges: (id) sender
325{
326  int i;
327  NSArray *matrixArray = [[diffView middleView] matrixArray];
328  int count = [matrixArray count];
329
330  for ( i = 0; i < count; i ++ )
331    {
332      if (choices[i] != 0)
333	{
334	  choices[i] = 0;
335	  [[diffView leftTextView] setColor:
336				     [NSColor colorWithDeviceRed:0.7
337					      green:0.7
338					      blue:1.
339					      alpha:1.]
340				   forChangeNumber: i];
341	  [[diffView rightTextView] setColor:
342				      [NSColor colorWithDeviceRed:0.7
343					       green:0.7
344					       blue:1.
345					       alpha:1.]
346				    forChangeNumber: i];
347	  [[matrixArray objectAtIndex: i] selectCellAtRow: 0 column:1];
348	}
349    }
350
351}
352
353- (void) savePatchFromRight: (id) sender
354{
355  NSMutableString *patchString;
356
357  int result;
358  NSSavePanel *sPanel;
359  NSString *path = [[NSUserDefaults standardUserDefaults]
360		    objectForKey:@"OpenDirectory"];
361  NSString *mergedName;
362
363
364  sPanel = [NSSavePanel savePanel];
365  //  [sPanel setAllowsMultipleSelection:NO];
366  result = [sPanel runModalForDirectory: path
367		   file: @""];
368
369  if (result != NSOKButton)
370    return;
371
372  path = [sPanel directory];
373
374  mergedName = [sPanel filename];
375
376  [[NSUserDefaults standardUserDefaults]
377    setObject: path
378    forKey:@"OpenDirectory"];
379
380
381  patchString = [self _patchMutableString: -1];
382  [patchString insertString: [NSString stringWithFormat: @"+++ %@\n",
383				       leftFileName]
384	       atIndex: 0];
385  if (rightFileName)
386    {
387      [patchString insertString: [NSString stringWithFormat: @"--- %@\n",
388					   rightFileName]
389		   atIndex: 0];
390    }
391  else
392    {
393      [patchString insertString: [NSString stringWithFormat: @"--- %@\n",
394					   leftFileName]
395		   atIndex: 0];
396    }
397
398  [patchString writeToFile: mergedName atomically: YES];
399}
400
401- (void) savePatchFromLeft: (id) sender
402{
403  NSMutableString *patchString;
404
405  int result;
406  NSSavePanel *sPanel;
407  NSString *path = [[NSUserDefaults standardUserDefaults]
408		    objectForKey:@"OpenDirectory"];
409  NSString *mergedName;
410
411
412  sPanel = [NSSavePanel savePanel];
413  //  [sPanel setAllowsMultipleSelection:NO];
414  result = [sPanel runModalForDirectory: path
415		   file: @""];
416
417  if (result != NSOKButton)
418    return;
419
420  path = [sPanel directory];
421
422  mergedName = [sPanel filename];
423
424  [[NSUserDefaults standardUserDefaults]
425    setObject: path
426    forKey:@"OpenDirectory"];
427
428
429  patchString = [self _patchMutableString: 1];
430  if (rightFileName)
431    {
432      [patchString insertString: [NSString stringWithFormat: @"+++ %@\n",
433					   rightFileName]
434		   atIndex: 0];
435    }
436  else
437    {
438      [patchString insertString: [NSString stringWithFormat: @"+++ %@\n",
439					   leftFileName]
440		   atIndex: 0];
441    }
442  [patchString insertString: [NSString stringWithFormat: @"--- %@\n",
443				       leftFileName]
444	       atIndex: 0];
445
446
447  [patchString writeToFile: mergedName atomically: YES];
448}
449
450- (NSMutableString *) _patchMutableString: (int) direction
451{
452  NSArray *flr;
453  NSArray *tlr;
454  NSString *fstr;
455  NSString *tstr;
456
457  NSMutableArray *patchArray = [[NSMutableArray alloc]
458				 initWithCapacity:
459				   ([leftChanges count] / 2) + 1];
460
461  NSMutableArray *finalArray = [[NSMutableArray alloc]
462				 initWithCapacity:
463				   ([leftChanges count] / 2) + 1];
464  NSArray *fromChanges;
465  NSArray *toChanges;
466  NSMutableString *stringToInsert;
467  PatchData *p, *pp;
468  int i, j, k;
469  int toStart;
470  int toEnd;
471  int count;
472  int linesCount;
473
474  if (direction > 0)
475    {
476      flr = [diffWrapper leftLineRanges];
477      tlr = [diffWrapper rightLineRanges];
478      fstr = [diffWrapper leftString];
479      tstr = [diffWrapper rightString];
480      fromChanges = leftChanges;
481      toChanges = rightChanges;
482    }
483  else
484    {
485      flr = [diffWrapper rightLineRanges];
486      tlr = [diffWrapper leftLineRanges];
487      fstr = [diffWrapper rightString];
488      tstr = [diffWrapper leftString];
489      fromChanges = rightChanges;
490      toChanges = leftChanges;
491    }
492  linesCount = [flr count] - 1;
493
494  for ( i = 0; i < [fromChanges count] - 1; i += 2 )
495    {
496      p = [[PatchData alloc] init];
497      p->patchString = (NSMutableString*)[NSMutableString string];
498
499      if (choices[i/2] * direction > 0)
500	{
501	  p->start = [[fromChanges objectAtIndex: i]
502			   intValue];
503	  p->end = [[fromChanges objectAtIndex: i + 1]
504			 intValue];
505	  toStart = [[toChanges objectAtIndex: i]
506			 intValue];
507	  toEnd = [[toChanges objectAtIndex: i + 1]
508 		       intValue];
509	  p->lineDifference = (toEnd - toStart) -
510	    (p->end - p->start);
511
512	  for (j = p->start; j < p->end; j++ )
513	    {
514	      [p->patchString appendString: @"-"];
515	      [p->patchString appendString:[fstr
516					     substringWithRange:
517					       NSMakeRange
518					     ([[flr objectAtIndex: j] intValue] + 1,
519					      [[flr objectAtIndex: j+1] intValue] -
520					      [[flr objectAtIndex: j] intValue])]];
521	    }
522	  for (j = toStart; j < toEnd; j++ )
523	    {
524	      [p->patchString appendString: @"+"];
525	      [p->patchString appendString:[tstr
526					     substringWithRange:
527					       NSMakeRange
528					     ([[tlr objectAtIndex: j] intValue] + 1,
529					      [[tlr objectAtIndex: j+1] intValue] -
530					      [[tlr objectAtIndex: j] intValue])]];
531	    }
532	  [patchArray addObject: p];
533
534	}
535      else if (choices[i/2] * direction < 0)
536	{
537	}
538      else if (choices[i/2] == 0)
539	{
540	  p->start = [[fromChanges objectAtIndex: i]
541			   intValue];
542	  p->end = [[fromChanges objectAtIndex: i + 1]
543			 intValue];
544	  p->lineDifference = - (p->end - p->start);
545
546	  for (j = p->start; j < p->end; j++ )
547	    {
548	      [p->patchString appendString: @"-"];
549	      [p->patchString appendString:[fstr
550					     substringWithRange:
551					       NSMakeRange
552					     ([[flr objectAtIndex: j] intValue] + 1,
553					      [[flr objectAtIndex: j+1] intValue] -
554					      [[flr objectAtIndex: j] intValue])]];
555	    }
556
557	  if (p->lineDifference != 0)
558	    [patchArray addObject: p];
559
560	}
561    }
562
563
564
565
566  i = 0;
567  count = [patchArray count];
568
569  pp = nil;
570
571  while( i < count )
572    {
573      p = [patchArray objectAtIndex: i];
574
575
576      if ((pp == nil) || (p->start - pp->end >= 6))
577	{
578	  // two separate chunks
579	  if (pp != nil)
580	    {
581	      j = pp->end + 3;
582	      if (j >= linesCount)
583		j = linesCount;
584	      stringToInsert = (NSMutableString*)[NSMutableString string];
585	      for ( k = pp->end; k < j; k++ )
586		{
587		  [stringToInsert appendString: @" "];
588		  [stringToInsert appendString: [fstr
589						  substringWithRange:
590						    NSMakeRange
591						  ([[flr objectAtIndex: k] intValue] + 1,
592						   [[flr objectAtIndex: k+1] intValue] -
593						   [[flr objectAtIndex: k] intValue])]];
594		}
595	      [pp->patchString appendString: stringToInsert];
596	      pp->end = j;
597
598	      [finalArray addObject: pp];
599	    }
600
601	  // new chunk
602	  pp = [[PatchData alloc] init];;
603
604	  j = p->start - 3;
605	  if ( j < 0)
606	    j = 0;
607	  pp = [[PatchData alloc] init];
608	  pp->patchString = p->patchString;
609	  stringToInsert = (NSMutableString*)[NSMutableString string];
610	  for ( k = j; k < p->start; k++ )
611	    {
612	      [stringToInsert appendString: @" "];
613	      [stringToInsert appendString: [fstr
614					     substringWithRange:
615					       NSMakeRange
616					     ([[flr objectAtIndex: k] intValue] + 1,
617					      [[flr objectAtIndex: k+1] intValue] -
618					      [[flr objectAtIndex: k] intValue])]];
619	    }
620	  [pp->patchString insertString: stringToInsert
621	    atIndex: 0];
622	  pp->start = j;
623	  pp->end = p->end;
624	  pp->lineDifference = p->lineDifference;
625
626	}
627      else
628	{
629	  // merge the two chunks
630	  stringToInsert = (NSMutableString*)[NSMutableString string];
631	  for ( k = pp->end; k < p->start; k++ )
632	    {
633	      [stringToInsert appendString: @" "];
634	      [stringToInsert appendString: [fstr
635					     substringWithRange:
636					       NSMakeRange
637					     ([[flr objectAtIndex: k] intValue] + 1,
638					      [[flr objectAtIndex: k+1] intValue] -
639					      [[flr objectAtIndex: k] intValue])]];
640	    }
641
642	  [pp->patchString appendString: stringToInsert];
643	  [pp->patchString appendString: p->patchString];
644	  pp->end = p->end;
645	  pp->lineDifference += p->lineDifference;
646	}
647      i++;
648    }
649
650  if (pp != nil)
651    {
652      j = pp->end + 3;
653      if (j >= linesCount)
654	j = linesCount;
655      stringToInsert = (NSMutableString*)[NSMutableString string];
656      for ( k = pp->end; k < j; k++ )
657	{
658	  [stringToInsert appendString: @" "];
659	  [stringToInsert appendString: [fstr
660					  substringWithRange:
661					    NSMakeRange
662					  ([[flr objectAtIndex: k] intValue] + 1,
663					   [[flr objectAtIndex: k+1] intValue] -
664					   [[flr objectAtIndex: k] intValue])]];
665	}
666      [pp->patchString appendString: stringToInsert];
667      pp->end = j;
668
669      [finalArray addObject: pp];
670    }
671
672
673  {
674    NSMutableString *patchFileString =
675      (NSMutableString*)[NSMutableString string];
676
677    int d = 0;
678    for ( i = 0; i < [finalArray count]; i++ )
679      {
680	p = [finalArray objectAtIndex: i];
681	[patchFileString appendFormat:
682			   @"@@ -%d,%d +%d,%d @@\n%@",
683			 p->start+1,
684			 p->end - p->start,
685			 p->start + d + 1,
686			 p->end - p->start + p->lineDifference,
687			 p->patchString];
688	d += p->lineDifference;
689      }
690
691    //    NSLog(@"\n%@", patchFileString);
692    return patchFileString;
693  }
694
695}
696
697
698- (void) saveMergedAt: (NSString *) mergedName
699{
700  NSMutableString *mergedString = [NSMutableString string];
701  int i;
702
703  {
704    NSArray *llr = [diffWrapper leftLineRanges];
705    NSArray *rlr = [diffWrapper rightLineRanges];
706
707    [mergedString appendString:
708		    [[diffWrapper leftString]
709		      substringWithRange:
710			NSMakeRange
711		      (0,
712		       [[llr objectAtIndex:
713			       [[leftChanges objectAtIndex: 0]
714				 intValue]] intValue]
715		       )]];
716
717
718    for ( i = 0; i < [leftChanges count] - 1; i++ )
719      {
720	if (i % 2 == 1)
721	  {
722	    [mergedString appendString:
723			    [[diffWrapper leftString]
724			      substringWithRange:
725				NSMakeRange
726			      ([[llr objectAtIndex:
727				       [[leftChanges objectAtIndex: i]
728					 intValue]] intValue],
729			       [[llr objectAtIndex:
730				       [[leftChanges objectAtIndex: i+1]
731					 intValue]] intValue] -
732			       [[llr objectAtIndex:
733				       [[leftChanges objectAtIndex: i]
734					 intValue]] intValue]
735			       )]];
736	  }
737	else
738	  {
739	    if (choices[i/2] < 0)
740	      {
741		[mergedString appendString:
742				[[diffWrapper leftString]
743				  substringWithRange:
744				    NSMakeRange
745				  ([[llr objectAtIndex:
746					   [[leftChanges objectAtIndex: i]
747					     intValue]] intValue],
748				   [[llr objectAtIndex:
749					   [[leftChanges objectAtIndex: i+1]
750					     intValue]] intValue] -
751				   [[llr objectAtIndex:
752					   [[leftChanges objectAtIndex: i]
753					     intValue]] intValue]
754				   )]];
755
756	      }
757	    else if (choices[i/2] > 0)
758	      {
759		[mergedString appendString:
760				[[diffWrapper rightString]
761				  substringWithRange:
762				    NSMakeRange
763				  ([[rlr objectAtIndex:
764					   [[rightChanges objectAtIndex: i]
765					     intValue]] intValue],
766				   [[rlr objectAtIndex:
767					   [[rightChanges objectAtIndex: i+1]
768					     intValue]] intValue] -
769				   [[rlr objectAtIndex:
770					   [[rightChanges objectAtIndex: i]
771					     intValue]] intValue]
772				   )]];
773
774	      }
775	    else
776	      {
777
778	      }
779	  }
780      }
781
782    [mergedString appendString:
783		    [[diffWrapper leftString]
784		      substringFromIndex:
785		      [[llr objectAtIndex:
786			       [[leftChanges objectAtIndex:
787					       [leftChanges count] - 1]
788				 intValue]] intValue]]];
789
790    //    NSLog(@"%@", mergedString);
791  }
792
793
794  [mergedString writeToFile: mergedName atomically: YES];
795}
796
797- (void) saveMergedFile: (id) sender
798{
799
800  int result;
801  NSSavePanel *sPanel;
802  NSString *path = [[NSUserDefaults standardUserDefaults]
803		    objectForKey:@"OpenDirectory"];
804  NSString *mergedName;
805
806
807  sPanel = [NSSavePanel savePanel];
808  //  [sPanel setAllowsMultipleSelection:NO];
809  result = [sPanel runModalForDirectory: path
810		   file: @""];
811
812  if (result != NSOKButton)
813    return;
814
815  path = [sPanel directory];
816
817  mergedName = [sPanel filename];
818
819  [[NSUserDefaults standardUserDefaults]
820    setObject: path
821    forKey:@"OpenDirectory"];
822
823
824  //  NSLog(@"%@", mergedName);
825
826  [self saveMergedAt: mergedName];
827
828}
829@end
830