forked from vim/vim
-
Notifications
You must be signed in to change notification settings - Fork 0
/
diff.c
3439 lines (3119 loc) · 83 KB
/
diff.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* diff.c: code for diff'ing two, three or four buffers.
*
* There are three ways to diff:
* - Shell out to an external diff program, using files.
* - Use the compiled-in xdiff library.
* - Let 'diffexpr' do the work, using files.
*/
#include "vim.h"
#include "xdiff/xdiff.h"
#if defined(FEAT_DIFF) || defined(PROTO)
static int diff_busy = FALSE; // using diff structs, don't change them
static int diff_need_update = FALSE; // ex_diffupdate needs to be called
// flags obtained from the 'diffopt' option
#define DIFF_FILLER 0x001 // display filler lines
#define DIFF_IBLANK 0x002 // ignore empty lines
#define DIFF_ICASE 0x004 // ignore case
#define DIFF_IWHITE 0x008 // ignore change in white space
#define DIFF_IWHITEALL 0x010 // ignore all white space changes
#define DIFF_IWHITEEOL 0x020 // ignore change in white space at EOL
#define DIFF_HORIZONTAL 0x040 // horizontal splits
#define DIFF_VERTICAL 0x080 // vertical splits
#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static long diff_algorithm = 0;
#define LBUFLEN 50 // length of line in diff file
static int diff_a_works = MAYBE; // TRUE when "diff -a" works, FALSE when it
// doesn't work, MAYBE when not checked yet
#if defined(MSWIN)
static int diff_bin_works = MAYBE; // TRUE when "diff --binary" works, FALSE
// when it doesn't work, MAYBE when not
// checked yet
#endif
// used for diff input
typedef struct {
char_u *din_fname; // used for external diff
mmfile_t din_mmfile; // used for internal diff
} diffin_T;
// used for diff result
typedef struct {
char_u *dout_fname; // used for external diff
garray_T dout_ga; // used for internal diff
} diffout_T;
// used for recording hunks from xdiff
typedef struct {
linenr_T lnum_orig;
long count_orig;
linenr_T lnum_new;
long count_new;
} diffhunk_T;
// two diff inputs and one result
typedef struct {
diffin_T dio_orig; // original file input
diffin_T dio_new; // new file input
diffout_T dio_diff; // diff result
int dio_internal; // using internal diff
} diffio_T;
static int diff_buf_idx(buf_T *buf);
static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp);
static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after);
static void diff_check_unchanged(tabpage_T *tp, diff_T *dp);
static int diff_check_sanity(tabpage_T *tp, diff_T *dp);
static int check_external_diff(diffio_T *diffio);
static int diff_file(diffio_T *diffio);
static int diff_equal_entry(diff_T *dp, int idx1, int idx2);
static int diff_cmp(char_u *s1, char_u *s2);
#ifdef FEAT_FOLDING
static void diff_fold_update(diff_T *dp, int skip_idx);
#endif
static void diff_read(int idx_orig, int idx_new, diffio_T *dio);
static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new);
static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp);
static int parse_diff_ed(char_u *line, diffhunk_T *hunk);
static int parse_diff_unified(char_u *line, diffhunk_T *hunk);
static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv);
#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \
for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next)
/*
* Called when deleting or unloading a buffer: No longer make a diff with it.
*/
void
diff_buf_delete(buf_T *buf)
{
int i;
tabpage_T *tp;
FOR_ALL_TABPAGES(tp)
{
i = diff_buf_idx_tp(buf, tp);
if (i != DB_COUNT)
{
tp->tp_diffbuf[i] = NULL;
tp->tp_diff_invalid = TRUE;
if (tp == curtab)
{
// don't redraw right away, more might change or buffer state
// is invalid right now
need_diff_redraw = TRUE;
redraw_later(UPD_VALID);
}
}
}
}
/*
* Check if the current buffer should be added to or removed from the list of
* diff buffers.
*/
void
diff_buf_adjust(win_T *win)
{
win_T *wp;
int i;
if (!win->w_p_diff)
{
// When there is no window showing a diff for this buffer, remove
// it from the diffs.
FOR_ALL_WINDOWS(wp)
if (wp->w_buffer == win->w_buffer && wp->w_p_diff)
break;
if (wp == NULL)
{
i = diff_buf_idx(win->w_buffer);
if (i != DB_COUNT)
{
curtab->tp_diffbuf[i] = NULL;
curtab->tp_diff_invalid = TRUE;
diff_redraw(TRUE);
}
}
}
else
diff_buf_add(win->w_buffer);
}
/*
* Add a buffer to make diffs for.
* Call this when a new buffer is being edited in the current window where
* 'diff' is set.
* Marks the current buffer as being part of the diff and requiring updating.
* This must be done before any autocmd, because a command may use info
* about the screen contents.
*/
void
diff_buf_add(buf_T *buf)
{
int i;
if (diff_buf_idx(buf) != DB_COUNT)
return; // It's already there.
for (i = 0; i < DB_COUNT; ++i)
if (curtab->tp_diffbuf[i] == NULL)
{
curtab->tp_diffbuf[i] = buf;
curtab->tp_diff_invalid = TRUE;
diff_redraw(TRUE);
return;
}
semsg(_(e_cannot_diff_more_than_nr_buffers), DB_COUNT);
}
/*
* Remove all buffers to make diffs for.
*/
static void
diff_buf_clear(void)
{
int i;
for (i = 0; i < DB_COUNT; ++i)
if (curtab->tp_diffbuf[i] != NULL)
{
curtab->tp_diffbuf[i] = NULL;
curtab->tp_diff_invalid = TRUE;
diff_redraw(TRUE);
}
}
/*
* Find buffer "buf" in the list of diff buffers for the current tab page.
* Return its index or DB_COUNT if not found.
*/
static int
diff_buf_idx(buf_T *buf)
{
int idx;
for (idx = 0; idx < DB_COUNT; ++idx)
if (curtab->tp_diffbuf[idx] == buf)
break;
return idx;
}
/*
* Find buffer "buf" in the list of diff buffers for tab page "tp".
* Return its index or DB_COUNT if not found.
*/
static int
diff_buf_idx_tp(buf_T *buf, tabpage_T *tp)
{
int idx;
for (idx = 0; idx < DB_COUNT; ++idx)
if (tp->tp_diffbuf[idx] == buf)
break;
return idx;
}
/*
* Mark the diff info involving buffer "buf" as invalid, it will be updated
* when info is requested.
*/
void
diff_invalidate(buf_T *buf)
{
tabpage_T *tp;
int i;
FOR_ALL_TABPAGES(tp)
{
i = diff_buf_idx_tp(buf, tp);
if (i != DB_COUNT)
{
tp->tp_diff_invalid = TRUE;
if (tp == curtab)
diff_redraw(TRUE);
}
}
}
/*
* Called by mark_adjust(): update line numbers in "curbuf".
*/
void
diff_mark_adjust(
linenr_T line1,
linenr_T line2,
long amount,
long amount_after)
{
int idx;
tabpage_T *tp;
// Handle all tab pages that use the current buffer in a diff.
FOR_ALL_TABPAGES(tp)
{
idx = diff_buf_idx_tp(curbuf, tp);
if (idx != DB_COUNT)
diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);
}
}
/*
* Update line numbers in tab page "tp" for "curbuf" with index "idx".
* This attempts to update the changes as much as possible:
* When inserting/deleting lines outside of existing change blocks, create a
* new change block and update the line numbers in following blocks.
* When inserting/deleting lines in existing change blocks, update them.
*/
static void
diff_mark_adjust_tp(
tabpage_T *tp,
int idx,
linenr_T line1,
linenr_T line2,
long amount,
long amount_after)
{
diff_T *dp;
diff_T *dprev;
diff_T *dnext;
int i;
int inserted, deleted;
int n, off;
linenr_T last;
linenr_T lnum_deleted = line1; // lnum of remaining deletion
int check_unchanged;
if (diff_internal())
{
// Will update diffs before redrawing. Set _invalid to update the
// diffs themselves, set _update to also update folds properly just
// before redrawing.
// Do update marks here, it is needed for :%diffput.
tp->tp_diff_invalid = TRUE;
tp->tp_diff_update = TRUE;
}
if (line2 == MAXLNUM)
{
// mark_adjust(99, MAXLNUM, 9, 0): insert lines
inserted = amount;
deleted = 0;
}
else if (amount_after > 0)
{
// mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines
inserted = amount_after;
deleted = 0;
}
else
{
// mark_adjust(98, 99, MAXLNUM, -2): delete lines
inserted = 0;
deleted = -amount_after;
}
dprev = NULL;
dp = tp->tp_first_diff;
for (;;)
{
// If the change is after the previous diff block and before the next
// diff block, thus not touching an existing change, create a new diff
// block. Don't do this when ex_diffgetput() is busy.
if ((dp == NULL || dp->df_lnum[idx] - 1 > line2
|| (line2 == MAXLNUM && dp->df_lnum[idx] > line1))
&& (dprev == NULL
|| dprev->df_lnum[idx] + dprev->df_count[idx] < line1)
&& !diff_busy)
{
dnext = diff_alloc_new(tp, dprev, dp);
if (dnext == NULL)
return;
dnext->df_lnum[idx] = line1;
dnext->df_count[idx] = inserted;
for (i = 0; i < DB_COUNT; ++i)
if (tp->tp_diffbuf[i] != NULL && i != idx)
{
if (dprev == NULL)
dnext->df_lnum[i] = line1;
else
dnext->df_lnum[i] = line1
+ (dprev->df_lnum[i] + dprev->df_count[i])
- (dprev->df_lnum[idx] + dprev->df_count[idx]);
dnext->df_count[i] = deleted;
}
}
// if at end of the list, quit
if (dp == NULL)
break;
/*
* Check for these situations:
* 1 2 3
* 1 2 3
* line1 2 3 4 5
* 2 3 4 5
* 2 3 4 5
* line2 2 3 4 5
* 3 5 6
* 3 5 6
*/
// compute last line of this change
last = dp->df_lnum[idx] + dp->df_count[idx] - 1;
// 1. change completely above line1: nothing to do
if (last >= line1 - 1)
{
// 6. change below line2: only adjust for amount_after; also when
// "deleted" became zero when deleted all lines between two diffs
if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2)
{
if (amount_after == 0)
break; // nothing left to change
dp->df_lnum[idx] += amount_after;
}
else
{
check_unchanged = FALSE;
// 2. 3. 4. 5.: inserted/deleted lines touching this diff.
if (deleted > 0)
{
off = 0;
if (dp->df_lnum[idx] >= line1)
{
if (last <= line2)
{
// 4. delete all lines of diff
if (dp->df_next != NULL
&& dp->df_next->df_lnum[idx] - 1 <= line2)
{
// delete continues in next diff, only do
// lines until that one
n = dp->df_next->df_lnum[idx] - lnum_deleted;
deleted -= n;
n -= dp->df_count[idx];
lnum_deleted = dp->df_next->df_lnum[idx];
}
else
n = deleted - dp->df_count[idx];
dp->df_count[idx] = 0;
}
else
{
// 5. delete lines at or just before top of diff
off = dp->df_lnum[idx] - lnum_deleted;
n = off;
dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1;
check_unchanged = TRUE;
}
dp->df_lnum[idx] = line1;
}
else
{
if (last < line2)
{
// 2. delete at end of diff
dp->df_count[idx] -= last - lnum_deleted + 1;
if (dp->df_next != NULL
&& dp->df_next->df_lnum[idx] - 1 <= line2)
{
// delete continues in next diff, only do
// lines until that one
n = dp->df_next->df_lnum[idx] - 1 - last;
deleted -= dp->df_next->df_lnum[idx]
- lnum_deleted;
lnum_deleted = dp->df_next->df_lnum[idx];
}
else
n = line2 - last;
check_unchanged = TRUE;
}
else
{
// 3. delete lines inside the diff
n = 0;
dp->df_count[idx] -= deleted;
}
}
for (i = 0; i < DB_COUNT; ++i)
if (tp->tp_diffbuf[i] != NULL && i != idx)
{
if (dp->df_lnum[i] > off)
dp->df_lnum[i] -= off;
else
dp->df_lnum[i] = 1;
dp->df_count[i] += n;
}
}
else
{
if (dp->df_lnum[idx] <= line1)
{
// inserted lines somewhere in this diff
dp->df_count[idx] += inserted;
check_unchanged = TRUE;
}
else
// inserted lines somewhere above this diff
dp->df_lnum[idx] += inserted;
}
if (check_unchanged)
// Check if inserted lines are equal, may reduce the
// size of the diff. TODO: also check for equal lines
// in the middle and perhaps split the block.
diff_check_unchanged(tp, dp);
}
}
// check if this block touches the previous one, may merge them.
if (dprev != NULL && dprev->df_lnum[idx] + dprev->df_count[idx]
== dp->df_lnum[idx])
{
for (i = 0; i < DB_COUNT; ++i)
if (tp->tp_diffbuf[i] != NULL)
dprev->df_count[i] += dp->df_count[i];
dprev->df_next = dp->df_next;
vim_free(dp);
dp = dprev->df_next;
}
else
{
// Advance to next entry.
dprev = dp;
dp = dp->df_next;
}
}
dprev = NULL;
dp = tp->tp_first_diff;
while (dp != NULL)
{
// All counts are zero, remove this entry.
for (i = 0; i < DB_COUNT; ++i)
if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0)
break;
if (i == DB_COUNT)
{
dnext = dp->df_next;
vim_free(dp);
dp = dnext;
if (dprev == NULL)
tp->tp_first_diff = dnext;
else
dprev->df_next = dnext;
}
else
{
// Advance to next entry.
dprev = dp;
dp = dp->df_next;
}
}
if (tp == curtab)
{
// Don't redraw right away, this updates the diffs, which can be slow.
need_diff_redraw = TRUE;
// Need to recompute the scroll binding, may remove or add filler
// lines (e.g., when adding lines above w_topline). But it's slow when
// making many changes, postpone until redrawing.
diff_need_scrollbind = TRUE;
}
}
/*
* Allocate a new diff block and link it between "dprev" and "dp".
*/
static diff_T *
diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
{
diff_T *dnew;
dnew = ALLOC_ONE(diff_T);
if (dnew == NULL)
return NULL;
dnew->df_next = dp;
if (dprev == NULL)
tp->tp_first_diff = dnew;
else
dprev->df_next = dnew;
return dnew;
}
/*
* Check if the diff block "dp" can be made smaller for lines at the start and
* end that are equal. Called after inserting lines.
* This may result in a change where all buffers have zero lines, the caller
* must take care of removing it.
*/
static void
diff_check_unchanged(tabpage_T *tp, diff_T *dp)
{
int i_org;
int i_new;
int off_org, off_new;
char_u *line_org;
int dir = FORWARD;
// Find the first buffers, use it as the original, compare the other
// buffer lines against this one.
for (i_org = 0; i_org < DB_COUNT; ++i_org)
if (tp->tp_diffbuf[i_org] != NULL)
break;
if (i_org == DB_COUNT) // safety check
return;
if (diff_check_sanity(tp, dp) == FAIL)
return;
// First check lines at the top, then at the bottom.
off_org = 0;
off_new = 0;
for (;;)
{
// Repeat until a line is found which is different or the number of
// lines has become zero.
while (dp->df_count[i_org] > 0)
{
// Copy the line, the next ml_get() will invalidate it.
if (dir == BACKWARD)
off_org = dp->df_count[i_org] - 1;
line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org],
dp->df_lnum[i_org] + off_org, FALSE));
if (line_org == NULL)
return;
for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new)
{
if (tp->tp_diffbuf[i_new] == NULL)
continue;
if (dir == BACKWARD)
off_new = dp->df_count[i_new] - 1;
// if other buffer doesn't have this line, it was inserted
if (off_new < 0 || off_new >= dp->df_count[i_new])
break;
if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new],
dp->df_lnum[i_new] + off_new, FALSE)) != 0)
break;
}
vim_free(line_org);
// Stop when a line isn't equal in all diff buffers.
if (i_new != DB_COUNT)
break;
// Line matched in all buffers, remove it from the diff.
for (i_new = i_org; i_new < DB_COUNT; ++i_new)
if (tp->tp_diffbuf[i_new] != NULL)
{
if (dir == FORWARD)
++dp->df_lnum[i_new];
--dp->df_count[i_new];
}
}
if (dir == BACKWARD)
break;
dir = BACKWARD;
}
}
/*
* Check if a diff block doesn't contain invalid line numbers.
* This can happen when the diff program returns invalid results.
*/
static int
diff_check_sanity(tabpage_T *tp, diff_T *dp)
{
int i;
for (i = 0; i < DB_COUNT; ++i)
if (tp->tp_diffbuf[i] != NULL)
if (dp->df_lnum[i] + dp->df_count[i] - 1
> tp->tp_diffbuf[i]->b_ml.ml_line_count)
return FAIL;
return OK;
}
/*
* Mark all diff buffers in the current tab page for redraw.
*/
void
diff_redraw(
int dofold) // also recompute the folds
{
win_T *wp;
win_T *wp_other = NULL;
int used_max_fill_other = FALSE;
int used_max_fill_curwin = FALSE;
int n;
need_diff_redraw = FALSE;
FOR_ALL_WINDOWS(wp)
{
// when closing windows or wiping buffers skip invalid window
if (!wp->w_p_diff || !buf_valid(wp->w_buffer))
continue;
redraw_win_later(wp, UPD_SOME_VALID);
if (wp != curwin)
wp_other = wp;
#ifdef FEAT_FOLDING
if (dofold && foldmethodIsDiff(wp))
foldUpdateAll(wp);
#endif
// A change may have made filler lines invalid, need to take care of
// that for other windows.
n = diff_check(wp, wp->w_topline);
if ((wp != curwin && wp->w_topfill > 0) || n > 0)
{
if (wp->w_topfill > n)
wp->w_topfill = (n < 0 ? 0 : n);
else if (n > 0 && n > wp->w_topfill)
{
wp->w_topfill = n;
if (wp == curwin)
used_max_fill_curwin = TRUE;
else if (wp_other != NULL)
used_max_fill_other = TRUE;
}
check_topfill(wp, FALSE);
}
}
if (wp_other != NULL && curwin->w_p_scb)
{
if (used_max_fill_curwin)
// The current window was set to use the maximum number of filler
// lines, may need to reduce them.
diff_set_topline(wp_other, curwin);
else if (used_max_fill_other)
// The other window was set to use the maximum number of filler
// lines, may need to reduce them.
diff_set_topline(curwin, wp_other);
}
}
static void
clear_diffin(diffin_T *din)
{
if (din->din_fname == NULL)
{
vim_free(din->din_mmfile.ptr);
din->din_mmfile.ptr = NULL;
}
else
mch_remove(din->din_fname);
}
static void
clear_diffout(diffout_T *dout)
{
if (dout->dout_fname == NULL)
ga_clear_strings(&dout->dout_ga);
else
mch_remove(dout->dout_fname);
}
/*
* Write buffer "buf" to a memory buffer.
* Return FAIL for failure.
*/
static int
diff_write_buffer(buf_T *buf, diffin_T *din)
{
linenr_T lnum;
char_u *s;
long len = 0;
char_u *ptr;
// xdiff requires one big block of memory with all the text.
for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
len += (long)STRLEN(ml_get_buf(buf, lnum, FALSE)) + 1;
ptr = alloc(len);
if (ptr == NULL)
{
// Allocating memory failed. This can happen, because we try to read
// the whole buffer text into memory. Set the failed flag, the diff
// will be retried with external diff. The flag is never reset.
buf->b_diff_failed = TRUE;
if (p_verbose > 0)
{
verbose_enter();
smsg(_("Not enough memory to use internal diff for buffer \"%s\""),
buf->b_fname);
verbose_leave();
}
return FAIL;
}
din->din_mmfile.ptr = (char *)ptr;
din->din_mmfile.size = len;
len = 0;
for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
{
for (s = ml_get_buf(buf, lnum, FALSE); *s != NUL; )
{
if (diff_flags & DIFF_ICASE)
{
int c;
int orig_len;
char_u cbuf[MB_MAXBYTES + 1];
if (*s == NL)
c = NUL;
else
{
// xdiff doesn't support ignoring case, fold-case the text.
c = PTR2CHAR(s);
c = MB_CASEFOLD(c);
}
orig_len = mb_ptr2len(s);
if (mb_char2bytes(c, cbuf) != orig_len)
// TODO: handle byte length difference
mch_memmove(ptr + len, s, orig_len);
else
mch_memmove(ptr + len, cbuf, orig_len);
s += orig_len;
len += orig_len;
}
else
{
ptr[len++] = *s == NL ? NUL : *s;
s++;
}
}
ptr[len++] = NL;
}
return OK;
}
/*
* Write buffer "buf" to file or memory buffer.
* Return FAIL for failure.
*/
static int
diff_write(buf_T *buf, diffin_T *din)
{
int r;
char_u *save_ff;
int save_cmod_flags;
if (din->din_fname == NULL)
return diff_write_buffer(buf, din);
// Always use 'fileformat' set to "unix".
save_ff = buf->b_p_ff;
buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
save_cmod_flags = cmdmod.cmod_flags;
// Writing the buffer is an implementation detail of performing the diff,
// so it shouldn't update the '[ and '] marks.
cmdmod.cmod_flags |= CMOD_LOCKMARKS;
r = buf_write(buf, din->din_fname, NULL,
(linenr_T)1, buf->b_ml.ml_line_count,
NULL, FALSE, FALSE, FALSE, TRUE);
cmdmod.cmod_flags = save_cmod_flags;
free_string_option(buf->b_p_ff);
buf->b_p_ff = save_ff;
return r;
}
/*
* Update the diffs for all buffers involved.
*/
static void
diff_try_update(
diffio_T *dio,
int idx_orig,
exarg_T *eap) // "eap" can be NULL
{
buf_T *buf;
int idx_new;
if (dio->dio_internal)
{
ga_init2(&dio->dio_diff.dout_ga, sizeof(char *), 1000);
}
else
{
// We need three temp file names.
dio->dio_orig.din_fname = vim_tempname('o', TRUE);
dio->dio_new.din_fname = vim_tempname('n', TRUE);
dio->dio_diff.dout_fname = vim_tempname('d', TRUE);
if (dio->dio_orig.din_fname == NULL
|| dio->dio_new.din_fname == NULL
|| dio->dio_diff.dout_fname == NULL)
goto theend;
}
// Check external diff is actually working.
if (!dio->dio_internal && check_external_diff(dio) == FAIL)
goto theend;
// :diffupdate!
if (eap != NULL && eap->forceit)
for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
{
buf = curtab->tp_diffbuf[idx_new];
if (buf_valid(buf))
buf_check_timestamp(buf, FALSE);
}
// Write the first buffer to a tempfile or mmfile_t.
buf = curtab->tp_diffbuf[idx_orig];
if (diff_write(buf, &dio->dio_orig) == FAIL)
goto theend;
// Make a difference between the first buffer and every other.
for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
{
buf = curtab->tp_diffbuf[idx_new];
if (buf == NULL || buf->b_ml.ml_mfp == NULL)
continue; // skip buffer that isn't loaded
// Write the other buffer and diff with the first one.
if (diff_write(buf, &dio->dio_new) == FAIL)
continue;
if (diff_file(dio) == FAIL)
continue;
// Read the diff output and add each entry to the diff list.
diff_read(idx_orig, idx_new, dio);
clear_diffin(&dio->dio_new);
clear_diffout(&dio->dio_diff);
}
clear_diffin(&dio->dio_orig);
theend:
vim_free(dio->dio_orig.din_fname);
vim_free(dio->dio_new.din_fname);
vim_free(dio->dio_diff.dout_fname);
}
/*
* Return TRUE if the options are set to use the internal diff library.
* Note that if the internal diff failed for one of the buffers, the external
* diff will be used anyway.
*/
int
diff_internal(void)
{
return (diff_flags & DIFF_INTERNAL) != 0
#ifdef FEAT_EVAL
&& *p_dex == NUL
#endif
;
}
/*
* Return TRUE if the internal diff failed for one of the diff buffers.
*/
static int
diff_internal_failed(void)
{
int idx;
// Only need to do something when there is another buffer.
for (idx = 0; idx < DB_COUNT; ++idx)
if (curtab->tp_diffbuf[idx] != NULL
&& curtab->tp_diffbuf[idx]->b_diff_failed)
return TRUE;
return FALSE;
}
/*
* Completely update the diffs for the buffers involved.
* When using the external "diff" command the buffers are written to a file,
* also for unmodified buffers (the file could have been produced by
* autocommands, e.g. the netrw plugin).
*/
void
ex_diffupdate(exarg_T *eap) // "eap" can be NULL
{
int idx_orig;
int idx_new;
diffio_T diffio;
int had_diffs = curtab->tp_first_diff != NULL;
if (diff_busy)
{
diff_need_update = TRUE;
return;
}
// Delete all diffblocks.
diff_clear(curtab);
curtab->tp_diff_invalid = FALSE;
// Use the first buffer as the original text.
for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig)
if (curtab->tp_diffbuf[idx_orig] != NULL)
break;
if (idx_orig == DB_COUNT)
goto theend;
// Only need to do something when there is another buffer.
for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
if (curtab->tp_diffbuf[idx_new] != NULL)
break;
if (idx_new == DB_COUNT)
goto theend;
// Only use the internal method if it did not fail for one of the buffers.
CLEAR_FIELD(diffio);
diffio.dio_internal = diff_internal() && !diff_internal_failed();
diff_try_update(&diffio, idx_orig, eap);
if (diffio.dio_internal && diff_internal_failed())
{
// Internal diff failed, use external diff instead.
CLEAR_FIELD(diffio);