blob: 3adcdb7dba67a493e325c561990780d183e5a97e [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
Bram Moolenaar5cc6a6e2009-01-22 19:48:55 +000011 * diff.c: code for diff'ing two, three or four buffers.
Bram Moolenaare828b762018-09-10 17:51:58 +020012 *
13 * There are three ways to diff:
14 * - Shell out to an external diff program, using files.
15 * - Use the compiled-in xdiff library.
16 * - Let 'diffexpr' do the work, using files.
Bram Moolenaar071d4272004-06-13 20:20:40 +000017 */
18
19#include "vim.h"
Bram Moolenaare828b762018-09-10 17:51:58 +020020#include "xdiff/xdiff.h"
Bram Moolenaar071d4272004-06-13 20:20:40 +000021
22#if defined(FEAT_DIFF) || defined(PROTO)
23
Bram Moolenaard2b58c02018-09-16 18:10:48 +020024static int diff_busy = FALSE; // using diff structs, don't change them
25static int diff_need_update = FALSE; // ex_diffupdate needs to be called
Bram Moolenaar071d4272004-06-13 20:20:40 +000026
Bram Moolenaar5d18efe2019-12-01 21:11:22 +010027// flags obtained from the 'diffopt' option
Bram Moolenaar785fc652018-09-15 19:17:38 +020028#define DIFF_FILLER 0x001 // display filler lines
29#define DIFF_IBLANK 0x002 // ignore empty lines
30#define DIFF_ICASE 0x004 // ignore case
31#define DIFF_IWHITE 0x008 // ignore change in white space
32#define DIFF_IWHITEALL 0x010 // ignore all white space changes
33#define DIFF_IWHITEEOL 0x020 // ignore change in white space at EOL
34#define DIFF_HORIZONTAL 0x040 // horizontal splits
35#define DIFF_VERTICAL 0x080 // vertical splits
36#define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden
37#define DIFF_INTERNAL 0x200 // use internal xdiff algorithm
Bram Moolenaarc8234772019-11-10 21:00:27 +010038#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window
Bram Moolenaar4223d432021-02-10 13:18:17 +010039#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
Jonathon7c7a4e62025-01-12 09:58:00 +010040#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff
Yee Cheng Chin9943d472025-03-26 19:41:02 +010041#define DIFF_INLINE_NONE 0x2000 // no inline highlight
42#define DIFF_INLINE_SIMPLE 0x4000 // inline highlight with simple algorithm
43#define DIFF_INLINE_CHAR 0x8000 // inline highlight with character diff
44#define DIFF_INLINE_WORD 0x10000 // inline highlight with word diff
Bram Moolenaar785fc652018-09-15 19:17:38 +020045#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
Yee Cheng Chin9943d472025-03-26 19:41:02 +010046#define ALL_INLINE (DIFF_INLINE_NONE | DIFF_INLINE_SIMPLE | DIFF_INLINE_CHAR | DIFF_INLINE_WORD)
47#define ALL_INLINE_DIFF (DIFF_INLINE_CHAR | DIFF_INLINE_WORD)
Bram Moolenaarc8234772019-11-10 21:00:27 +010048static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
Bram Moolenaar071d4272004-06-13 20:20:40 +000049
Bram Moolenaare828b762018-09-10 17:51:58 +020050static long diff_algorithm = 0;
51
Bram Moolenaar5d18efe2019-12-01 21:11:22 +010052#define LBUFLEN 50 // length of line in diff file
Bram Moolenaar071d4272004-06-13 20:20:40 +000053
Bram Moolenaar5d18efe2019-12-01 21:11:22 +010054static int diff_a_works = MAYBE; // TRUE when "diff -a" works, FALSE when it
55 // doesn't work, MAYBE when not checked yet
Bram Moolenaar48e330a2016-02-23 14:53:34 +010056#if defined(MSWIN)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +010057static int diff_bin_works = MAYBE; // TRUE when "diff --binary" works, FALSE
58 // when it doesn't work, MAYBE when not
59 // checked yet
Bram Moolenaar071d4272004-06-13 20:20:40 +000060#endif
61
Bram Moolenaare828b762018-09-10 17:51:58 +020062// used for diff input
63typedef struct {
64 char_u *din_fname; // used for external diff
65 mmfile_t din_mmfile; // used for internal diff
66} diffin_T;
67
68// used for diff result
69typedef struct {
70 char_u *dout_fname; // used for external diff
71 garray_T dout_ga; // used for internal diff
72} diffout_T;
73
Lewis Russelld9da86e2021-12-28 13:54:41 +000074// used for recording hunks from xdiff
75typedef struct {
76 linenr_T lnum_orig;
77 long count_orig;
78 linenr_T lnum_new;
79 long count_new;
80} diffhunk_T;
81
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +010082typedef enum {
83 DIO_OUTPUT_INDICES = 0, // default
84 DIO_OUTPUT_UNIFIED = 1 // unified diff format
85} dio_outfmt_T;
86
Bram Moolenaare828b762018-09-10 17:51:58 +020087// two diff inputs and one result
88typedef struct {
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +010089 diffin_T dio_orig; // original file input
90 diffin_T dio_new; // new file input
91 diffout_T dio_diff; // diff result
92 int dio_internal; // using internal diff
93 dio_outfmt_T dio_outfmt; // internal diff output format
94 int dio_ctxlen; // unified diff context length
Bram Moolenaare828b762018-09-10 17:51:58 +020095} diffio_T;
96
Bram Moolenaarf28dbce2016-01-29 22:03:47 +010097static int diff_buf_idx(buf_T *buf);
98static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp);
99static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after);
100static void diff_check_unchanged(tabpage_T *tp, diff_T *dp);
101static int diff_check_sanity(tabpage_T *tp, diff_T *dp);
Bram Moolenaare828b762018-09-10 17:51:58 +0200102static int check_external_diff(diffio_T *diffio);
103static int diff_file(diffio_T *diffio);
Bram Moolenaarf28dbce2016-01-29 22:03:47 +0100104static int diff_equal_entry(diff_T *dp, int idx1, int idx2);
105static int diff_cmp(char_u *s1, char_u *s2);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000106#ifdef FEAT_FOLDING
Bram Moolenaarf28dbce2016-01-29 22:03:47 +0100107static void diff_fold_update(diff_T *dp, int skip_idx);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000108#endif
Lewis Russelld9da86e2021-12-28 13:54:41 +0000109static void diff_read(int idx_orig, int idx_new, diffio_T *dio);
Bram Moolenaarf28dbce2016-01-29 22:03:47 +0100110static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new);
111static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp);
Lewis Russelld9da86e2021-12-28 13:54:41 +0000112static int parse_diff_ed(char_u *line, diffhunk_T *hunk);
113static int parse_diff_unified(char_u *line, diffhunk_T *hunk);
Yegappan Lakshmananfa378352024-02-01 22:05:27 +0100114static int xdiff_out_indices(long start_a, long count_a, long start_b, long count_b, void *priv);
115static int xdiff_out_unified(void *priv, mmbuffer_t *mb, int nbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000116
Bram Moolenaaraeea7212020-04-02 18:50:46 +0200117#define FOR_ALL_DIFFBLOCKS_IN_TAB(tp, dp) \
118 for ((dp) = (tp)->tp_first_diff; (dp) != NULL; (dp) = (dp)->df_next)
119
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100120 static void
121clear_diffblock(diff_T *dp)
122{
123 ga_clear(&dp->df_changes);
124 vim_free(dp);
125}
126
Bram Moolenaar071d4272004-06-13 20:20:40 +0000127/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000128 * Called when deleting or unloading a buffer: No longer make a diff with it.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000129 */
130 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100131diff_buf_delete(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000132{
133 int i;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000134 tabpage_T *tp;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000135
Bram Moolenaar29323592016-07-24 22:04:11 +0200136 FOR_ALL_TABPAGES(tp)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000137 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000138 i = diff_buf_idx_tp(buf, tp);
139 if (i != DB_COUNT)
140 {
141 tp->tp_diffbuf[i] = NULL;
142 tp->tp_diff_invalid = TRUE;
Bram Moolenaar5d55c0f2008-11-30 14:16:57 +0000143 if (tp == curtab)
Bram Moolenaarcd38bb42022-06-26 14:04:07 +0100144 {
145 // don't redraw right away, more might change or buffer state
146 // is invalid right now
147 need_diff_redraw = TRUE;
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100148 redraw_later(UPD_VALID);
Bram Moolenaarcd38bb42022-06-26 14:04:07 +0100149 }
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000150 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000151 }
152}
153
154/*
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +0000155 * Check if the current buffer should be added to or removed from the list of
156 * diff buffers.
157 */
158 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100159diff_buf_adjust(win_T *win)
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +0000160{
161 win_T *wp;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000162 int i;
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +0000163
164 if (!win->w_p_diff)
165 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100166 // When there is no window showing a diff for this buffer, remove
167 // it from the diffs.
Bram Moolenaar29323592016-07-24 22:04:11 +0200168 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +0000169 if (wp->w_buffer == win->w_buffer && wp->w_p_diff)
170 break;
171 if (wp == NULL)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000172 {
173 i = diff_buf_idx(win->w_buffer);
174 if (i != DB_COUNT)
175 {
176 curtab->tp_diffbuf[i] = NULL;
177 curtab->tp_diff_invalid = TRUE;
Bram Moolenaar5d55c0f2008-11-30 14:16:57 +0000178 diff_redraw(TRUE);
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000179 }
180 }
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +0000181 }
182 else
183 diff_buf_add(win->w_buffer);
184}
185
186/*
Bram Moolenaar071d4272004-06-13 20:20:40 +0000187 * Add a buffer to make diffs for.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000188 * Call this when a new buffer is being edited in the current window where
189 * 'diff' is set.
Bram Moolenaar5cc6a6e2009-01-22 19:48:55 +0000190 * Marks the current buffer as being part of the diff and requiring updating.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000191 * This must be done before any autocmd, because a command may use info
192 * about the screen contents.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000193 */
194 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100195diff_buf_add(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000196{
197 int i;
198
199 if (diff_buf_idx(buf) != DB_COUNT)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100200 return; // It's already there.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000201
202 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000203 if (curtab->tp_diffbuf[i] == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000204 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000205 curtab->tp_diffbuf[i] = buf;
206 curtab->tp_diff_invalid = TRUE;
Bram Moolenaar5d55c0f2008-11-30 14:16:57 +0000207 diff_redraw(TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000208 return;
209 }
210
Bram Moolenaare1242042021-12-16 20:56:57 +0000211 semsg(_(e_cannot_diff_more_than_nr_buffers), DB_COUNT);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000212}
213
214/*
Bram Moolenaar25ea0542017-02-03 23:16:28 +0100215 * Remove all buffers to make diffs for.
216 */
217 static void
218diff_buf_clear(void)
219{
220 int i;
221
222 for (i = 0; i < DB_COUNT; ++i)
223 if (curtab->tp_diffbuf[i] != NULL)
224 {
225 curtab->tp_diffbuf[i] = NULL;
226 curtab->tp_diff_invalid = TRUE;
227 diff_redraw(TRUE);
228 }
229}
230
231/*
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000232 * Find buffer "buf" in the list of diff buffers for the current tab page.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000233 * Return its index or DB_COUNT if not found.
234 */
235 static int
Bram Moolenaar7454a062016-01-30 15:14:10 +0100236diff_buf_idx(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000237{
238 int idx;
239
240 for (idx = 0; idx < DB_COUNT; ++idx)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000241 if (curtab->tp_diffbuf[idx] == buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000242 break;
243 return idx;
244}
245
246/*
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000247 * Find buffer "buf" in the list of diff buffers for tab page "tp".
248 * Return its index or DB_COUNT if not found.
249 */
250 static int
Bram Moolenaar7454a062016-01-30 15:14:10 +0100251diff_buf_idx_tp(buf_T *buf, tabpage_T *tp)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000252{
253 int idx;
254
255 for (idx = 0; idx < DB_COUNT; ++idx)
256 if (tp->tp_diffbuf[idx] == buf)
257 break;
258 return idx;
259}
260
261/*
262 * Mark the diff info involving buffer "buf" as invalid, it will be updated
263 * when info is requested.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000264 */
265 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100266diff_invalidate(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000267{
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000268 tabpage_T *tp;
269 int i;
270
Bram Moolenaar29323592016-07-24 22:04:11 +0200271 FOR_ALL_TABPAGES(tp)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000272 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000273 i = diff_buf_idx_tp(buf, tp);
274 if (i != DB_COUNT)
275 {
276 tp->tp_diff_invalid = TRUE;
277 if (tp == curtab)
278 diff_redraw(TRUE);
279 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000280 }
281}
282
283/*
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000284 * Called by mark_adjust(): update line numbers in "curbuf".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000285 */
286 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100287diff_mark_adjust(
288 linenr_T line1,
289 linenr_T line2,
290 long amount,
291 long amount_after)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000292{
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000293 int idx;
294 tabpage_T *tp;
295
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100296 // Handle all tab pages that use the current buffer in a diff.
Bram Moolenaar29323592016-07-24 22:04:11 +0200297 FOR_ALL_TABPAGES(tp)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000298 {
299 idx = diff_buf_idx_tp(curbuf, tp);
300 if (idx != DB_COUNT)
301 diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);
302 }
303}
304
305/*
306 * Update line numbers in tab page "tp" for "curbuf" with index "idx".
307 * This attempts to update the changes as much as possible:
308 * When inserting/deleting lines outside of existing change blocks, create a
309 * new change block and update the line numbers in following blocks.
310 * When inserting/deleting lines in existing change blocks, update them.
311 */
312 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100313diff_mark_adjust_tp(
314 tabpage_T *tp,
315 int idx,
316 linenr_T line1,
317 linenr_T line2,
318 long amount,
319 long amount_after)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000320{
Bram Moolenaar071d4272004-06-13 20:20:40 +0000321 diff_T *dp;
322 diff_T *dprev;
323 diff_T *dnext;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000324 int i;
325 int inserted, deleted;
326 int n, off;
327 linenr_T last;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100328 linenr_T lnum_deleted = line1; // lnum of remaining deletion
Bram Moolenaar071d4272004-06-13 20:20:40 +0000329 int check_unchanged;
330
Bram Moolenaare3521d92018-09-16 14:10:31 +0200331 if (diff_internal())
332 {
Bram Moolenaar198fa062018-09-18 21:20:26 +0200333 // Will update diffs before redrawing. Set _invalid to update the
Bram Moolenaare3521d92018-09-16 14:10:31 +0200334 // diffs themselves, set _update to also update folds properly just
335 // before redrawing.
Bram Moolenaar5f57bdc2018-10-25 17:52:23 +0200336 // Do update marks here, it is needed for :%diffput.
Bram Moolenaare3521d92018-09-16 14:10:31 +0200337 tp->tp_diff_invalid = TRUE;
338 tp->tp_diff_update = TRUE;
Bram Moolenaare3521d92018-09-16 14:10:31 +0200339 }
340
Bram Moolenaar071d4272004-06-13 20:20:40 +0000341 if (line2 == MAXLNUM)
342 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100343 // mark_adjust(99, MAXLNUM, 9, 0): insert lines
Bram Moolenaar071d4272004-06-13 20:20:40 +0000344 inserted = amount;
345 deleted = 0;
346 }
347 else if (amount_after > 0)
348 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100349 // mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines
Bram Moolenaar071d4272004-06-13 20:20:40 +0000350 inserted = amount_after;
351 deleted = 0;
352 }
353 else
354 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100355 // mark_adjust(98, 99, MAXLNUM, -2): delete lines
Bram Moolenaar071d4272004-06-13 20:20:40 +0000356 inserted = 0;
357 deleted = -amount_after;
358 }
359
360 dprev = NULL;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000361 dp = tp->tp_first_diff;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000362 for (;;)
363 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100364 // If the change is after the previous diff block and before the next
365 // diff block, thus not touching an existing change, create a new diff
366 // block. Don't do this when ex_diffgetput() is busy.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000367 if ((dp == NULL || dp->df_lnum[idx] - 1 > line2
368 || (line2 == MAXLNUM && dp->df_lnum[idx] > line1))
369 && (dprev == NULL
370 || dprev->df_lnum[idx] + dprev->df_count[idx] < line1)
371 && !diff_busy)
372 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000373 dnext = diff_alloc_new(tp, dprev, dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000374 if (dnext == NULL)
375 return;
376
377 dnext->df_lnum[idx] = line1;
378 dnext->df_count[idx] = inserted;
379 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000380 if (tp->tp_diffbuf[i] != NULL && i != idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000381 {
382 if (dprev == NULL)
383 dnext->df_lnum[i] = line1;
384 else
385 dnext->df_lnum[i] = line1
386 + (dprev->df_lnum[i] + dprev->df_count[i])
387 - (dprev->df_lnum[idx] + dprev->df_count[idx]);
388 dnext->df_count[i] = deleted;
389 }
390 }
391
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100392 // if at end of the list, quit
Bram Moolenaar071d4272004-06-13 20:20:40 +0000393 if (dp == NULL)
394 break;
395
396 /*
397 * Check for these situations:
398 * 1 2 3
399 * 1 2 3
400 * line1 2 3 4 5
401 * 2 3 4 5
402 * 2 3 4 5
403 * line2 2 3 4 5
404 * 3 5 6
405 * 3 5 6
406 */
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100407 // compute last line of this change
Bram Moolenaar071d4272004-06-13 20:20:40 +0000408 last = dp->df_lnum[idx] + dp->df_count[idx] - 1;
409
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100410 // 1. change completely above line1: nothing to do
Bram Moolenaar071d4272004-06-13 20:20:40 +0000411 if (last >= line1 - 1)
412 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100413 // 6. change below line2: only adjust for amount_after; also when
414 // "deleted" became zero when deleted all lines between two diffs
Jonathon7c7a4e62025-01-12 09:58:00 +0100415 if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2 -
416 (dp->is_linematched ? 1 : 0))
Bram Moolenaar071d4272004-06-13 20:20:40 +0000417 {
418 if (amount_after == 0)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100419 break; // nothing left to change
Bram Moolenaar071d4272004-06-13 20:20:40 +0000420 dp->df_lnum[idx] += amount_after;
421 }
422 else
423 {
424 check_unchanged = FALSE;
425
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100426 // 2. 3. 4. 5.: inserted/deleted lines touching this diff.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000427 if (deleted > 0)
428 {
Bram Moolenaarc101abf2022-06-26 16:53:34 +0100429 off = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000430 if (dp->df_lnum[idx] >= line1)
431 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000432 if (last <= line2)
433 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100434 // 4. delete all lines of diff
Bram Moolenaar071d4272004-06-13 20:20:40 +0000435 if (dp->df_next != NULL
436 && dp->df_next->df_lnum[idx] - 1 <= line2)
437 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100438 // delete continues in next diff, only do
439 // lines until that one
Bram Moolenaar071d4272004-06-13 20:20:40 +0000440 n = dp->df_next->df_lnum[idx] - lnum_deleted;
441 deleted -= n;
442 n -= dp->df_count[idx];
443 lnum_deleted = dp->df_next->df_lnum[idx];
444 }
445 else
446 n = deleted - dp->df_count[idx];
447 dp->df_count[idx] = 0;
448 }
449 else
450 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100451 // 5. delete lines at or just before top of diff
Bram Moolenaarc101abf2022-06-26 16:53:34 +0100452 off = dp->df_lnum[idx] - lnum_deleted;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000453 n = off;
454 dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1;
455 check_unchanged = TRUE;
456 }
457 dp->df_lnum[idx] = line1;
458 }
459 else
460 {
Bram Moolenaar071d4272004-06-13 20:20:40 +0000461 if (last < line2)
462 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100463 // 2. delete at end of diff
Bram Moolenaar071d4272004-06-13 20:20:40 +0000464 dp->df_count[idx] -= last - lnum_deleted + 1;
465 if (dp->df_next != NULL
466 && dp->df_next->df_lnum[idx] - 1 <= line2)
467 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100468 // delete continues in next diff, only do
469 // lines until that one
Bram Moolenaar071d4272004-06-13 20:20:40 +0000470 n = dp->df_next->df_lnum[idx] - 1 - last;
471 deleted -= dp->df_next->df_lnum[idx]
472 - lnum_deleted;
473 lnum_deleted = dp->df_next->df_lnum[idx];
474 }
475 else
476 n = line2 - last;
477 check_unchanged = TRUE;
478 }
479 else
480 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100481 // 3. delete lines inside the diff
Bram Moolenaar071d4272004-06-13 20:20:40 +0000482 n = 0;
483 dp->df_count[idx] -= deleted;
484 }
485 }
486
487 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000488 if (tp->tp_diffbuf[i] != NULL && i != idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000489 {
Bram Moolenaar4e677b92022-07-28 18:44:27 +0100490 if (dp->df_lnum[i] > off)
491 dp->df_lnum[i] -= off;
492 else
493 dp->df_lnum[i] = 1;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000494 dp->df_count[i] += n;
495 }
496 }
497 else
498 {
499 if (dp->df_lnum[idx] <= line1)
500 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100501 // inserted lines somewhere in this diff
Bram Moolenaar071d4272004-06-13 20:20:40 +0000502 dp->df_count[idx] += inserted;
503 check_unchanged = TRUE;
504 }
505 else
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100506 // inserted lines somewhere above this diff
Bram Moolenaar071d4272004-06-13 20:20:40 +0000507 dp->df_lnum[idx] += inserted;
508 }
509
510 if (check_unchanged)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100511 // Check if inserted lines are equal, may reduce the
512 // size of the diff. TODO: also check for equal lines
513 // in the middle and perhaps split the block.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000514 diff_check_unchanged(tp, dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000515 }
516 }
517
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100518 // check if this block touches the previous one, may merge them.
Jonathon7c7a4e62025-01-12 09:58:00 +0100519 if (dprev != NULL && !dp->is_linematched
520 && dprev->df_lnum[idx] + dprev->df_count[idx]
521 == dp->df_lnum[idx])
Bram Moolenaar071d4272004-06-13 20:20:40 +0000522 {
523 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000524 if (tp->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000525 dprev->df_count[i] += dp->df_count[i];
526 dprev->df_next = dp->df_next;
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100527 clear_diffblock(dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000528 dp = dprev->df_next;
529 }
530 else
531 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100532 // Advance to next entry.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000533 dprev = dp;
534 dp = dp->df_next;
535 }
536 }
537
538 dprev = NULL;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000539 dp = tp->tp_first_diff;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000540 while (dp != NULL)
541 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100542 // All counts are zero, remove this entry.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000543 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000544 if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000545 break;
546 if (i == DB_COUNT)
547 {
548 dnext = dp->df_next;
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100549 clear_diffblock(dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000550 dp = dnext;
551 if (dprev == NULL)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000552 tp->tp_first_diff = dnext;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000553 else
554 dprev->df_next = dnext;
555 }
556 else
557 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100558 // Advance to next entry.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000559 dprev = dp;
560 dp = dp->df_next;
561 }
562
563 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000564
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000565 if (tp == curtab)
566 {
Bram Moolenaar4f57eef2019-08-24 20:54:19 +0200567 // Don't redraw right away, this updates the diffs, which can be slow.
568 need_diff_redraw = TRUE;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000569
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100570 // Need to recompute the scroll binding, may remove or add filler
571 // lines (e.g., when adding lines above w_topline). But it's slow when
572 // making many changes, postpone until redrawing.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000573 diff_need_scrollbind = TRUE;
574 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000575}
576
577/*
578 * Allocate a new diff block and link it between "dprev" and "dp".
579 */
580 static diff_T *
Bram Moolenaar7454a062016-01-30 15:14:10 +0100581diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000582{
583 diff_T *dnew;
584
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100585 dnew = ALLOC_CLEAR_ONE(diff_T);
Yegappan Lakshmanan1cfb14a2023-01-09 19:04:23 +0000586 if (dnew == NULL)
587 return NULL;
588
Jonathon7c7a4e62025-01-12 09:58:00 +0100589 dnew->is_linematched = FALSE;
Yegappan Lakshmanan1cfb14a2023-01-09 19:04:23 +0000590 dnew->df_next = dp;
591 if (dprev == NULL)
592 tp->tp_first_diff = dnew;
593 else
594 dprev->df_next = dnew;
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100595
596 dnew->has_changes = FALSE;
597 ga_init2(&dnew->df_changes, sizeof(diffline_change_T), 20);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000598 return dnew;
599}
600
601/*
602 * Check if the diff block "dp" can be made smaller for lines at the start and
603 * end that are equal. Called after inserting lines.
604 * This may result in a change where all buffers have zero lines, the caller
605 * must take care of removing it.
606 */
607 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100608diff_check_unchanged(tabpage_T *tp, diff_T *dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000609{
610 int i_org;
611 int i_new;
612 int off_org, off_new;
613 char_u *line_org;
614 int dir = FORWARD;
615
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100616 // Find the first buffers, use it as the original, compare the other
617 // buffer lines against this one.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000618 for (i_org = 0; i_org < DB_COUNT; ++i_org)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000619 if (tp->tp_diffbuf[i_org] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000620 break;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100621 if (i_org == DB_COUNT) // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +0000622 return;
623
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000624 if (diff_check_sanity(tp, dp) == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000625 return;
626
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100627 // First check lines at the top, then at the bottom.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000628 off_org = 0;
629 off_new = 0;
630 for (;;)
631 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100632 // Repeat until a line is found which is different or the number of
633 // lines has become zero.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000634 while (dp->df_count[i_org] > 0)
635 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100636 // Copy the line, the next ml_get() will invalidate it.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000637 if (dir == BACKWARD)
638 off_org = dp->df_count[i_org] - 1;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000639 line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org],
Bram Moolenaar071d4272004-06-13 20:20:40 +0000640 dp->df_lnum[i_org] + off_org, FALSE));
641 if (line_org == NULL)
642 return;
643 for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new)
644 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000645 if (tp->tp_diffbuf[i_new] == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000646 continue;
647 if (dir == BACKWARD)
648 off_new = dp->df_count[i_new] - 1;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100649 // if other buffer doesn't have this line, it was inserted
Bram Moolenaar071d4272004-06-13 20:20:40 +0000650 if (off_new < 0 || off_new >= dp->df_count[i_new])
651 break;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000652 if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new],
Bram Moolenaar071d4272004-06-13 20:20:40 +0000653 dp->df_lnum[i_new] + off_new, FALSE)) != 0)
654 break;
655 }
656 vim_free(line_org);
657
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100658 // Stop when a line isn't equal in all diff buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000659 if (i_new != DB_COUNT)
660 break;
661
Bram Moolenaar5d18efe2019-12-01 21:11:22 +0100662 // Line matched in all buffers, remove it from the diff.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000663 for (i_new = i_org; i_new < DB_COUNT; ++i_new)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000664 if (tp->tp_diffbuf[i_new] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000665 {
666 if (dir == FORWARD)
667 ++dp->df_lnum[i_new];
668 --dp->df_count[i_new];
669 }
670 }
671 if (dir == BACKWARD)
672 break;
673 dir = BACKWARD;
674 }
675}
676
677/*
678 * Check if a diff block doesn't contain invalid line numbers.
679 * This can happen when the diff program returns invalid results.
680 */
681 static int
Bram Moolenaar7454a062016-01-30 15:14:10 +0100682diff_check_sanity(tabpage_T *tp, diff_T *dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000683{
684 int i;
685
686 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000687 if (tp->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000688 if (dp->df_lnum[i] + dp->df_count[i] - 1
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000689 > tp->tp_diffbuf[i]->b_ml.ml_line_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000690 return FAIL;
691 return OK;
692}
693
694/*
Bram Moolenaar49d7bf12006-02-17 21:45:41 +0000695 * Mark all diff buffers in the current tab page for redraw.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000696 */
Bram Moolenaar4f57eef2019-08-24 20:54:19 +0200697 void
Bram Moolenaar7454a062016-01-30 15:14:10 +0100698diff_redraw(
Bram Moolenaare3521d92018-09-16 14:10:31 +0200699 int dofold) // also recompute the folds
Bram Moolenaar071d4272004-06-13 20:20:40 +0000700{
701 win_T *wp;
Bram Moolenaar04626c22021-09-01 16:02:07 +0200702 win_T *wp_other = NULL;
Bram Moolenaar841c2252021-10-22 20:56:55 +0100703 int used_max_fill_other = FALSE;
704 int used_max_fill_curwin = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000705 int n;
706
Bram Moolenaar4f57eef2019-08-24 20:54:19 +0200707 need_diff_redraw = FALSE;
Bram Moolenaar29323592016-07-24 22:04:11 +0200708 FOR_ALL_WINDOWS(wp)
zeertzjq101d57b2022-07-31 18:34:32 +0100709 {
Bram Moolenaarcd38bb42022-06-26 14:04:07 +0100710 // when closing windows or wiping buffers skip invalid window
zeertzjq101d57b2022-07-31 18:34:32 +0100711 if (!wp->w_p_diff || !buf_valid(wp->w_buffer))
712 continue;
713
Bram Moolenaara4d158b2022-08-14 14:17:45 +0100714 redraw_win_later(wp, UPD_SOME_VALID);
zeertzjq101d57b2022-07-31 18:34:32 +0100715 if (wp != curwin)
716 wp_other = wp;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000717#ifdef FEAT_FOLDING
zeertzjq101d57b2022-07-31 18:34:32 +0100718 if (dofold && foldmethodIsDiff(wp))
719 foldUpdateAll(wp);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000720#endif
zeertzjq101d57b2022-07-31 18:34:32 +0100721 // A change may have made filler lines invalid, need to take care of
722 // that for other windows.
723 n = diff_check(wp, wp->w_topline);
724 if ((wp != curwin && wp->w_topfill > 0) || n > 0)
725 {
726 if (wp->w_topfill > n)
727 wp->w_topfill = (n < 0 ? 0 : n);
728 else if (n > 0 && n > wp->w_topfill)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000729 {
zeertzjq101d57b2022-07-31 18:34:32 +0100730 wp->w_topfill = n;
731 if (wp == curwin)
732 used_max_fill_curwin = TRUE;
733 else if (wp_other != NULL)
734 used_max_fill_other = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000735 }
zeertzjq101d57b2022-07-31 18:34:32 +0100736 check_topfill(wp, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000737 }
zeertzjq101d57b2022-07-31 18:34:32 +0100738 }
Bram Moolenaar04626c22021-09-01 16:02:07 +0200739
Bram Moolenaar841c2252021-10-22 20:56:55 +0100740 if (wp_other != NULL && curwin->w_p_scb)
741 {
742 if (used_max_fill_curwin)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +0000743 // The current window was set to use the maximum number of filler
Bram Moolenaar841c2252021-10-22 20:56:55 +0100744 // lines, may need to reduce them.
745 diff_set_topline(wp_other, curwin);
746 else if (used_max_fill_other)
Dominique Pelleaf4a61a2021-12-27 17:21:41 +0000747 // The other window was set to use the maximum number of filler
Bram Moolenaar841c2252021-10-22 20:56:55 +0100748 // lines, may need to reduce them.
749 diff_set_topline(curwin, wp_other);
750 }
Bram Moolenaar071d4272004-06-13 20:20:40 +0000751}
752
Bram Moolenaare828b762018-09-10 17:51:58 +0200753 static void
754clear_diffin(diffin_T *din)
755{
756 if (din->din_fname == NULL)
Yegappan Lakshmanan960dcbd2023-03-07 17:45:11 +0000757 VIM_CLEAR(din->din_mmfile.ptr);
Bram Moolenaare828b762018-09-10 17:51:58 +0200758 else
759 mch_remove(din->din_fname);
760}
761
762 static void
763clear_diffout(diffout_T *dout)
764{
765 if (dout->dout_fname == NULL)
766 ga_clear_strings(&dout->dout_ga);
767 else
768 mch_remove(dout->dout_fname);
769}
770
Bram Moolenaar071d4272004-06-13 20:20:40 +0000771/*
Bram Moolenaare828b762018-09-10 17:51:58 +0200772 * Write buffer "buf" to a memory buffer.
773 * Return FAIL for failure.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000774 */
775 static int
Jonathon7c7a4e62025-01-12 09:58:00 +0100776diff_write_buffer(buf_T *buf, diffin_T *din, linenr_T start, linenr_T end)
Bram Moolenaare828b762018-09-10 17:51:58 +0200777{
778 linenr_T lnum;
779 char_u *s;
780 long len = 0;
781 char_u *ptr;
782
Jonathon7c7a4e62025-01-12 09:58:00 +0100783 if (end < 0)
784 end = buf->b_ml.ml_line_count;
785
Yukihiro Nakadairaf1694b42024-09-22 11:26:13 +0200786 if (buf->b_ml.ml_flags & ML_EMPTY)
787 {
788 din->din_mmfile.ptr = NULL;
789 din->din_mmfile.size = 0;
790 return OK;
791 }
792
Bram Moolenaare828b762018-09-10 17:51:58 +0200793 // xdiff requires one big block of memory with all the text.
Jonathon7c7a4e62025-01-12 09:58:00 +0100794 for (lnum = start; lnum <= end; ++lnum)
zeertzjq94b7c322024-03-12 21:50:32 +0100795 len += ml_get_buf_len(buf, lnum) + 1;
Bram Moolenaar18a4ba22019-05-24 19:39:03 +0200796 ptr = alloc(len);
Bram Moolenaare828b762018-09-10 17:51:58 +0200797 if (ptr == NULL)
798 {
799 // Allocating memory failed. This can happen, because we try to read
800 // the whole buffer text into memory. Set the failed flag, the diff
801 // will be retried with external diff. The flag is never reset.
802 buf->b_diff_failed = TRUE;
803 if (p_verbose > 0)
804 {
805 verbose_enter();
Bram Moolenaarf9e3e092019-01-13 23:38:42 +0100806 smsg(_("Not enough memory to use internal diff for buffer \"%s\""),
Bram Moolenaare828b762018-09-10 17:51:58 +0200807 buf->b_fname);
808 verbose_leave();
809 }
810 return FAIL;
811 }
812 din->din_mmfile.ptr = (char *)ptr;
813 din->din_mmfile.size = len;
814
815 len = 0;
Jonathon7c7a4e62025-01-12 09:58:00 +0100816 for (lnum = start; lnum <= end; ++lnum)
Bram Moolenaare828b762018-09-10 17:51:58 +0200817 {
818 for (s = ml_get_buf(buf, lnum, FALSE); *s != NUL; )
819 {
820 if (diff_flags & DIFF_ICASE)
821 {
822 int c;
Bram Moolenaare828b762018-09-10 17:51:58 +0200823 int orig_len;
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100824 int c_len = 1;
Bram Moolenaare828b762018-09-10 17:51:58 +0200825 char_u cbuf[MB_MAXBYTES + 1];
826
Bram Moolenaar06f60952021-12-28 18:30:05 +0000827 if (*s == NL)
828 c = NUL;
829 else
830 {
831 // xdiff doesn't support ignoring case, fold-case the text.
832 c = PTR2CHAR(s);
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100833 c_len = MB_CHAR2LEN(c);
Bram Moolenaar06f60952021-12-28 18:30:05 +0000834 c = MB_CASEFOLD(c);
835 }
Bram Moolenaar1614a142019-10-06 22:00:13 +0200836 orig_len = mb_ptr2len(s);
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100837 if (mb_char2bytes(c, cbuf) != c_len)
838 // TODO: handle byte length difference.
839 // One example is Å (3 bytes) and å (2 bytes).
Bram Moolenaare828b762018-09-10 17:51:58 +0200840 mch_memmove(ptr + len, s, orig_len);
841 else
Yee Cheng Chin9943d472025-03-26 19:41:02 +0100842 {
843 mch_memmove(ptr + len, cbuf, c_len);
844 if (orig_len > c_len)
845 {
846 // Copy remaining composing characters
847 mch_memmove(ptr + len + c_len, s + c_len,
848 orig_len - c_len);
849 }
850 }
Bram Moolenaare828b762018-09-10 17:51:58 +0200851
852 s += orig_len;
853 len += orig_len;
Bram Moolenaare828b762018-09-10 17:51:58 +0200854 }
855 else
Bram Moolenaar06f60952021-12-28 18:30:05 +0000856 {
857 ptr[len++] = *s == NL ? NUL : *s;
858 s++;
859 }
Bram Moolenaare828b762018-09-10 17:51:58 +0200860 }
861 ptr[len++] = NL;
862 }
863 return OK;
864}
865
866/*
867 * Write buffer "buf" to file or memory buffer.
868 * Return FAIL for failure.
869 */
870 static int
871diff_write(buf_T *buf, diffin_T *din)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000872{
873 int r;
874 char_u *save_ff;
Bram Moolenaare1004402020-10-24 20:49:43 +0200875 int save_cmod_flags;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000876
Bram Moolenaare828b762018-09-10 17:51:58 +0200877 if (din->din_fname == NULL)
Jonathon7c7a4e62025-01-12 09:58:00 +0100878 return diff_write_buffer(buf, din, 1, -1);
Bram Moolenaare828b762018-09-10 17:51:58 +0200879
880 // Always use 'fileformat' set to "unix".
Bram Moolenaar071d4272004-06-13 20:20:40 +0000881 save_ff = buf->b_p_ff;
882 buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
Bram Moolenaare1004402020-10-24 20:49:43 +0200883 save_cmod_flags = cmdmod.cmod_flags;
Bram Moolenaarf4a1d1c2019-11-16 13:50:25 +0100884 // Writing the buffer is an implementation detail of performing the diff,
885 // so it shouldn't update the '[ and '] marks.
Bram Moolenaare1004402020-10-24 20:49:43 +0200886 cmdmod.cmod_flags |= CMOD_LOCKMARKS;
Bram Moolenaare828b762018-09-10 17:51:58 +0200887 r = buf_write(buf, din->din_fname, NULL,
888 (linenr_T)1, buf->b_ml.ml_line_count,
889 NULL, FALSE, FALSE, FALSE, TRUE);
Bram Moolenaare1004402020-10-24 20:49:43 +0200890 cmdmod.cmod_flags = save_cmod_flags;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000891 free_string_option(buf->b_p_ff);
892 buf->b_p_ff = save_ff;
893 return r;
894}
895
896/*
Bram Moolenaare828b762018-09-10 17:51:58 +0200897 * Update the diffs for all buffers involved.
898 */
899 static void
900diff_try_update(
901 diffio_T *dio,
902 int idx_orig,
903 exarg_T *eap) // "eap" can be NULL
904{
905 buf_T *buf;
906 int idx_new;
907
908 if (dio->dio_internal)
909 {
910 ga_init2(&dio->dio_diff.dout_ga, sizeof(char *), 1000);
911 }
912 else
913 {
914 // We need three temp file names.
915 dio->dio_orig.din_fname = vim_tempname('o', TRUE);
916 dio->dio_new.din_fname = vim_tempname('n', TRUE);
917 dio->dio_diff.dout_fname = vim_tempname('d', TRUE);
918 if (dio->dio_orig.din_fname == NULL
919 || dio->dio_new.din_fname == NULL
920 || dio->dio_diff.dout_fname == NULL)
921 goto theend;
922 }
923
924 // Check external diff is actually working.
925 if (!dio->dio_internal && check_external_diff(dio) == FAIL)
926 goto theend;
927
928 // :diffupdate!
929 if (eap != NULL && eap->forceit)
930 for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
931 {
932 buf = curtab->tp_diffbuf[idx_new];
933 if (buf_valid(buf))
934 buf_check_timestamp(buf, FALSE);
935 }
936
937 // Write the first buffer to a tempfile or mmfile_t.
938 buf = curtab->tp_diffbuf[idx_orig];
939 if (diff_write(buf, &dio->dio_orig) == FAIL)
940 goto theend;
941
942 // Make a difference between the first buffer and every other.
943 for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
944 {
945 buf = curtab->tp_diffbuf[idx_new];
946 if (buf == NULL || buf->b_ml.ml_mfp == NULL)
947 continue; // skip buffer that isn't loaded
948
949 // Write the other buffer and diff with the first one.
950 if (diff_write(buf, &dio->dio_new) == FAIL)
951 continue;
952 if (diff_file(dio) == FAIL)
953 continue;
954
955 // Read the diff output and add each entry to the diff list.
Lewis Russelld9da86e2021-12-28 13:54:41 +0000956 diff_read(idx_orig, idx_new, dio);
Bram Moolenaare828b762018-09-10 17:51:58 +0200957
958 clear_diffin(&dio->dio_new);
959 clear_diffout(&dio->dio_diff);
960 }
961 clear_diffin(&dio->dio_orig);
962
963theend:
964 vim_free(dio->dio_orig.din_fname);
965 vim_free(dio->dio_new.din_fname);
966 vim_free(dio->dio_diff.dout_fname);
967}
968
969/*
970 * Return TRUE if the options are set to use the internal diff library.
971 * Note that if the internal diff failed for one of the buffers, the external
972 * diff will be used anyway.
973 */
Bram Moolenaare3521d92018-09-16 14:10:31 +0200974 int
Bram Moolenaare828b762018-09-10 17:51:58 +0200975diff_internal(void)
976{
Bram Moolenaar975880b2019-03-03 14:42:11 +0100977 return (diff_flags & DIFF_INTERNAL) != 0
978#ifdef FEAT_EVAL
979 && *p_dex == NUL
980#endif
981 ;
Bram Moolenaare828b762018-09-10 17:51:58 +0200982}
983
984/*
985 * Return TRUE if the internal diff failed for one of the diff buffers.
986 */
987 static int
988diff_internal_failed(void)
989{
990 int idx;
991
992 // Only need to do something when there is another buffer.
993 for (idx = 0; idx < DB_COUNT; ++idx)
994 if (curtab->tp_diffbuf[idx] != NULL
995 && curtab->tp_diffbuf[idx]->b_diff_failed)
996 return TRUE;
997 return FALSE;
998}
999
1000/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001001 * Completely update the diffs for the buffers involved.
Bram Moolenaare3521d92018-09-16 14:10:31 +02001002 * When using the external "diff" command the buffers are written to a file,
1003 * also for unmodified buffers (the file could have been produced by
1004 * autocommands, e.g. the netrw plugin).
Bram Moolenaar071d4272004-06-13 20:20:40 +00001005 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001006 void
Bram Moolenaare828b762018-09-10 17:51:58 +02001007ex_diffupdate(exarg_T *eap) // "eap" can be NULL
Bram Moolenaar071d4272004-06-13 20:20:40 +00001008{
Bram Moolenaar071d4272004-06-13 20:20:40 +00001009 int idx_orig;
1010 int idx_new;
Bram Moolenaare828b762018-09-10 17:51:58 +02001011 diffio_T diffio;
Bram Moolenaar198fa062018-09-18 21:20:26 +02001012 int had_diffs = curtab->tp_first_diff != NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001013
Bram Moolenaard2b58c02018-09-16 18:10:48 +02001014 if (diff_busy)
1015 {
1016 diff_need_update = TRUE;
1017 return;
1018 }
1019
Bram Moolenaare828b762018-09-10 17:51:58 +02001020 // Delete all diffblocks.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001021 diff_clear(curtab);
1022 curtab->tp_diff_invalid = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001023
Bram Moolenaare828b762018-09-10 17:51:58 +02001024 // Use the first buffer as the original text.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001025 for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001026 if (curtab->tp_diffbuf[idx_orig] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001027 break;
1028 if (idx_orig == DB_COUNT)
Bram Moolenaar198fa062018-09-18 21:20:26 +02001029 goto theend;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001030
Bram Moolenaare828b762018-09-10 17:51:58 +02001031 // Only need to do something when there is another buffer.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001032 for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001033 if (curtab->tp_diffbuf[idx_new] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001034 break;
1035 if (idx_new == DB_COUNT)
Bram Moolenaar198fa062018-09-18 21:20:26 +02001036 goto theend;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001037
Bram Moolenaare828b762018-09-10 17:51:58 +02001038 // Only use the internal method if it did not fail for one of the buffers.
Bram Moolenaara80faa82020-04-12 19:37:17 +02001039 CLEAR_FIELD(diffio);
Bram Moolenaare828b762018-09-10 17:51:58 +02001040 diffio.dio_internal = diff_internal() && !diff_internal_failed();
Bram Moolenaar071d4272004-06-13 20:20:40 +00001041
Bram Moolenaare828b762018-09-10 17:51:58 +02001042 diff_try_update(&diffio, idx_orig, eap);
1043 if (diffio.dio_internal && diff_internal_failed())
1044 {
1045 // Internal diff failed, use external diff instead.
Bram Moolenaara80faa82020-04-12 19:37:17 +02001046 CLEAR_FIELD(diffio);
Bram Moolenaare828b762018-09-10 17:51:58 +02001047 diff_try_update(&diffio, idx_orig, eap);
1048 }
1049
1050 // force updating cursor position on screen
1051 curwin->w_valid_cursor.lnum = 0;
1052
Bram Moolenaar198fa062018-09-18 21:20:26 +02001053theend:
1054 // A redraw is needed if there were diffs and they were cleared, or there
1055 // are diffs now, which means they got updated.
1056 if (had_diffs || curtab->tp_first_diff != NULL)
1057 {
1058 diff_redraw(TRUE);
1059 apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, FALSE, curbuf);
1060 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001061}
1062
1063/*
1064 * Do a quick test if "diff" really works. Otherwise it looks like there
1065 * are no differences. Can't use the return value, it's non-zero when
1066 * there are differences.
1067 */
1068 static int
1069check_external_diff(diffio_T *diffio)
1070{
1071 FILE *fd;
1072 int ok;
1073 int io_error = FALSE;
1074
1075 // May try twice, first with "-a" and then without.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001076 for (;;)
1077 {
1078 ok = FALSE;
Bram Moolenaare828b762018-09-10 17:51:58 +02001079 fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w");
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001080 if (fd == NULL)
1081 io_error = TRUE;
1082 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001083 {
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001084 if (fwrite("line1\n", (size_t)6, (size_t)1, fd) != 1)
1085 io_error = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001086 fclose(fd);
Bram Moolenaare828b762018-09-10 17:51:58 +02001087 fd = mch_fopen((char *)diffio->dio_new.din_fname, "w");
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001088 if (fd == NULL)
1089 io_error = TRUE;
1090 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001091 {
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001092 if (fwrite("line2\n", (size_t)6, (size_t)1, fd) != 1)
1093 io_error = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001094 fclose(fd);
Bram Moolenaare828b762018-09-10 17:51:58 +02001095 fd = NULL;
1096 if (diff_file(diffio) == OK)
1097 fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r");
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001098 if (fd == NULL)
1099 io_error = TRUE;
1100 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001101 {
1102 char_u linebuf[LBUFLEN];
1103
1104 for (;;)
1105 {
glacambread5c1782021-05-24 14:20:53 +02001106 // For normal diff there must be a line that contains
1107 // "1c1". For unified diff "@@ -1 +1 @@".
Bram Moolenaar00590742019-02-15 21:06:09 +01001108 if (vim_fgets(linebuf, LBUFLEN, fd))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001109 break;
glacambread5c1782021-05-24 14:20:53 +02001110 if (STRNCMP(linebuf, "1c1", 3) == 0
1111 || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001112 ok = TRUE;
1113 }
1114 fclose(fd);
1115 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001116 mch_remove(diffio->dio_diff.dout_fname);
1117 mch_remove(diffio->dio_new.din_fname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001118 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001119 mch_remove(diffio->dio_orig.din_fname);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001120 }
1121
1122#ifdef FEAT_EVAL
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001123 // When using 'diffexpr' break here.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001124 if (*p_dex != NUL)
1125 break;
1126#endif
1127
Bram Moolenaar48e330a2016-02-23 14:53:34 +01001128#if defined(MSWIN)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001129 // If the "-a" argument works, also check if "--binary" works.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001130 if (ok && diff_a_works == MAYBE && diff_bin_works == MAYBE)
1131 {
1132 diff_a_works = TRUE;
1133 diff_bin_works = TRUE;
1134 continue;
1135 }
1136 if (!ok && diff_a_works == TRUE && diff_bin_works == TRUE)
1137 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001138 // Tried --binary, but it failed. "-a" works though.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001139 diff_bin_works = FALSE;
1140 ok = TRUE;
1141 }
1142#endif
1143
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001144 // If we checked if "-a" works already, break here.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001145 if (diff_a_works != MAYBE)
1146 break;
1147 diff_a_works = ok;
1148
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001149 // If "-a" works break here, otherwise retry without "-a".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001150 if (ok)
1151 break;
1152 }
1153 if (!ok)
1154 {
Bram Moolenaarfe86f2d2008-11-28 20:29:07 +00001155 if (io_error)
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001156 emsg(_(e_cannot_read_or_write_temp_files));
Bram Moolenaare1242042021-12-16 20:56:57 +00001157 emsg(_(e_cannot_create_diffs));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001158 diff_a_works = MAYBE;
Bram Moolenaar48e330a2016-02-23 14:53:34 +01001159#if defined(MSWIN)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001160 diff_bin_works = MAYBE;
1161#endif
Bram Moolenaare828b762018-09-10 17:51:58 +02001162 return FAIL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001163 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001164 return OK;
1165}
Bram Moolenaar071d4272004-06-13 20:20:40 +00001166
Bram Moolenaare828b762018-09-10 17:51:58 +02001167/*
1168 * Invoke the xdiff function.
1169 */
1170 static int
1171diff_file_internal(diffio_T *diffio)
1172{
1173 xpparam_t param;
1174 xdemitconf_t emit_cfg;
1175 xdemitcb_t emit_cb;
Bram Moolenaarbd1d5602012-05-18 18:47:17 +02001176
Bram Moolenaara80faa82020-04-12 19:37:17 +02001177 CLEAR_FIELD(param);
1178 CLEAR_FIELD(emit_cfg);
1179 CLEAR_FIELD(emit_cb);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001180
Bram Moolenaare828b762018-09-10 17:51:58 +02001181 param.flags = diff_algorithm;
1182
1183 if (diff_flags & DIFF_IWHITE)
1184 param.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
Bram Moolenaar785fc652018-09-15 19:17:38 +02001185 if (diff_flags & DIFF_IWHITEALL)
1186 param.flags |= XDF_IGNORE_WHITESPACE;
1187 if (diff_flags & DIFF_IWHITEEOL)
1188 param.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
1189 if (diff_flags & DIFF_IBLANK)
1190 param.flags |= XDF_IGNORE_BLANK_LINES;
Bram Moolenaare828b762018-09-10 17:51:58 +02001191
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01001192 emit_cfg.ctxlen = diffio->dio_ctxlen;
Bram Moolenaare828b762018-09-10 17:51:58 +02001193 emit_cb.priv = &diffio->dio_diff;
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01001194 if (diffio->dio_outfmt == DIO_OUTPUT_INDICES)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01001195 emit_cfg.hunk_func = xdiff_out_indices;
1196 else
1197 emit_cb.out_line = xdiff_out_unified;
Bram Moolenaare828b762018-09-10 17:51:58 +02001198 if (xdl_diff(&diffio->dio_orig.din_mmfile,
1199 &diffio->dio_new.din_mmfile,
1200 &param, &emit_cfg, &emit_cb) < 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001201 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00001202 emsg(_(e_problem_creating_internal_diff));
Bram Moolenaare828b762018-09-10 17:51:58 +02001203 return FAIL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001204 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001205 return OK;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001206}
1207
1208/*
1209 * Make a diff between files "tmp_orig" and "tmp_new", results in "tmp_diff".
Bram Moolenaare828b762018-09-10 17:51:58 +02001210 * return OK or FAIL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001211 */
Bram Moolenaare828b762018-09-10 17:51:58 +02001212 static int
1213diff_file(diffio_T *dio)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001214{
1215 char_u *cmd;
Bram Moolenaar5fd0ca72009-05-13 16:56:33 +00001216 size_t len;
Bram Moolenaare828b762018-09-10 17:51:58 +02001217 char_u *tmp_orig = dio->dio_orig.din_fname;
1218 char_u *tmp_new = dio->dio_new.din_fname;
1219 char_u *tmp_diff = dio->dio_diff.dout_fname;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001220
1221#ifdef FEAT_EVAL
1222 if (*p_dex != NUL)
Bram Moolenaare828b762018-09-10 17:51:58 +02001223 {
1224 // Use 'diffexpr' to generate the diff file.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001225 eval_diff(tmp_orig, tmp_new, tmp_diff);
Bram Moolenaare828b762018-09-10 17:51:58 +02001226 return OK;
1227 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001228 else
1229#endif
Bram Moolenaare828b762018-09-10 17:51:58 +02001230 // Use xdiff for generating the diff.
1231 if (dio->dio_internal)
Bram Moolenaare828b762018-09-10 17:51:58 +02001232 return diff_file_internal(dio);
Bram Moolenaar95fb60a2005-03-08 22:29:20 +00001233
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001234 len = STRLEN(tmp_orig) + STRLEN(tmp_new)
1235 + STRLEN(tmp_diff) + STRLEN(p_srr) + 27;
1236 cmd = alloc(len);
1237 if (cmd == NULL)
1238 return FAIL;
Bram Moolenaare828b762018-09-10 17:51:58 +02001239
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001240 // We don't want $DIFF_OPTIONS to get in the way.
1241 if (getenv("DIFF_OPTIONS"))
1242 vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)"");
1243
1244 // Build the diff command and execute it. Always use -a, binary
1245 // differences are of no use. Ignore errors, diff returns
1246 // non-zero when differences have been found.
1247 vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s%s%s%s %s",
1248 diff_a_works == FALSE ? "" : "-a ",
Bram Moolenaar48e330a2016-02-23 14:53:34 +01001249#if defined(MSWIN)
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001250 diff_bin_works == TRUE ? "--binary " : "",
Bram Moolenaar071d4272004-06-13 20:20:40 +00001251#else
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001252 "",
Bram Moolenaar071d4272004-06-13 20:20:40 +00001253#endif
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001254 (diff_flags & DIFF_IWHITE) ? "-b " : "",
1255 (diff_flags & DIFF_IWHITEALL) ? "-w " : "",
1256 (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "",
1257 (diff_flags & DIFF_IBLANK) ? "-B " : "",
1258 (diff_flags & DIFF_ICASE) ? "-i " : "",
1259 tmp_orig, tmp_new);
1260 append_redir(cmd, (int)len, p_srr, tmp_diff);
1261 block_autocmds(); // avoid ShellCmdPost stuff
1262 (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT);
1263 unblock_autocmds();
1264 vim_free(cmd);
1265 return OK;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001266}
1267
1268/*
1269 * Create a new version of a file from the current buffer and a diff file.
1270 * The buffer is written to a file, also for unmodified buffers (the file
1271 * could have been produced by autocommands, e.g. the netrw plugin).
1272 */
1273 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001274ex_diffpatch(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001275{
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001276 char_u *tmp_orig; // name of original temp file
1277 char_u *tmp_new; // name of patched temp file
Bram Moolenaar071d4272004-06-13 20:20:40 +00001278 char_u *buf = NULL;
Bram Moolenaar5fd0ca72009-05-13 16:56:33 +00001279 size_t buflen;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001280 win_T *old_curwin = curwin;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001281 char_u *newname = NULL; // name of patched file buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00001282#ifdef UNIX
1283 char_u dirbuf[MAXPATHL];
1284 char_u *fullname = NULL;
1285#endif
1286#ifdef FEAT_BROWSE
1287 char_u *browseFile = NULL;
Bram Moolenaare1004402020-10-24 20:49:43 +02001288 int save_cmod_flags = cmdmod.cmod_flags;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001289#endif
Bram Moolenaar8767f522016-07-01 17:17:39 +02001290 stat_T st;
Bram Moolenaara95ab322017-03-11 19:21:53 +01001291 char_u *esc_name = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001292
1293#ifdef FEAT_BROWSE
Bram Moolenaare1004402020-10-24 20:49:43 +02001294 if (cmdmod.cmod_flags & CMOD_BROWSE)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001295 {
Bram Moolenaar7171abe2004-10-11 10:06:20 +00001296 browseFile = do_browse(0, (char_u *)_("Patch file"),
Bram Moolenaarc36651b2018-04-29 12:22:56 +02001297 eap->arg, NULL, NULL,
1298 (char_u *)_(BROWSE_FILTER_ALL_FILES), NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001299 if (browseFile == NULL)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001300 return; // operation cancelled
Bram Moolenaar071d4272004-06-13 20:20:40 +00001301 eap->arg = browseFile;
Bram Moolenaare1004402020-10-24 20:49:43 +02001302 cmdmod.cmod_flags &= ~CMOD_BROWSE; // don't let do_ecmd() browse again
Bram Moolenaar071d4272004-06-13 20:20:40 +00001303 }
1304#endif
1305
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001306 // We need two temp file names.
Bram Moolenaare5c421c2015-03-31 13:33:08 +02001307 tmp_orig = vim_tempname('o', FALSE);
1308 tmp_new = vim_tempname('n', FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001309 if (tmp_orig == NULL || tmp_new == NULL)
1310 goto theend;
1311
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001312 // Write the current buffer to "tmp_orig".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001313 if (buf_write(curbuf, tmp_orig, NULL,
1314 (linenr_T)1, curbuf->b_ml.ml_line_count,
1315 NULL, FALSE, FALSE, FALSE, TRUE) == FAIL)
1316 goto theend;
1317
1318#ifdef UNIX
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001319 // Get the absolute path of the patchfile, changing directory below.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001320 fullname = FullName_save(eap->arg, FALSE);
1321#endif
Bram Moolenaara95ab322017-03-11 19:21:53 +01001322 esc_name = vim_strsave_shellescape(
Bram Moolenaar071d4272004-06-13 20:20:40 +00001323# ifdef UNIX
Bram Moolenaara95ab322017-03-11 19:21:53 +01001324 fullname != NULL ? fullname :
Bram Moolenaar071d4272004-06-13 20:20:40 +00001325# endif
Bram Moolenaara95ab322017-03-11 19:21:53 +01001326 eap->arg, TRUE, TRUE);
1327 if (esc_name == NULL)
1328 goto theend;
1329 buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16;
Bram Moolenaar964b3742019-05-24 18:54:09 +02001330 buf = alloc(buflen);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001331 if (buf == NULL)
1332 goto theend;
1333
1334#ifdef UNIX
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001335 // Temporarily chdir to /tmp, to avoid patching files in the current
1336 // directory when the patch file contains more than one patch. When we
1337 // have our own temp dir use that instead, it will be cleaned up when we
1338 // exit (any .rej files created). Don't change directory if we can't
1339 // return to the current.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001340 if (mch_dirname(dirbuf, MAXPATHL) != OK || mch_chdir((char *)dirbuf) != 0)
1341 dirbuf[0] = NUL;
1342 else
1343 {
1344# ifdef TEMPDIRNAMES
1345 if (vim_tempdir != NULL)
Bram Moolenaar42335f52018-09-13 15:33:43 +02001346 vim_ignored = mch_chdir((char *)vim_tempdir);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001347 else
1348# endif
Bram Moolenaar42335f52018-09-13 15:33:43 +02001349 vim_ignored = mch_chdir("/tmp");
Bram Moolenaar071d4272004-06-13 20:20:40 +00001350 shorten_fnames(TRUE);
1351 }
1352#endif
1353
1354#ifdef FEAT_EVAL
1355 if (*p_pex != NUL)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001356 // Use 'patchexpr' to generate the new file.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001357 eval_patch(tmp_orig,
1358# ifdef UNIX
1359 fullname != NULL ? fullname :
1360# endif
1361 eap->arg, tmp_new);
1362 else
1363#endif
1364 {
Bram Moolenaar23a971d2023-04-04 22:04:53 +01001365 if (check_restricted())
1366 goto theend;
1367
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001368 // Build the patch command and execute it. Ignore errors. Switch to
1369 // cooked mode to allow the user to respond to prompts.
Bram Moolenaara95ab322017-03-11 19:21:53 +01001370 vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s",
1371 tmp_new, tmp_orig, esc_name);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001372 block_autocmds(); // Avoid ShellCmdPost stuff
Bram Moolenaar071d4272004-06-13 20:20:40 +00001373 (void)call_shell(buf, SHELL_FILTER | SHELL_COOKED);
Bram Moolenaar78ab3312007-09-29 12:16:41 +00001374 unblock_autocmds();
Bram Moolenaar071d4272004-06-13 20:20:40 +00001375 }
1376
1377#ifdef UNIX
1378 if (dirbuf[0] != NUL)
1379 {
1380 if (mch_chdir((char *)dirbuf) != 0)
Bram Moolenaar460ae5d2022-01-01 14:19:49 +00001381 emsg(_(e_cannot_go_back_to_previous_directory));
Bram Moolenaar071d4272004-06-13 20:20:40 +00001382 shorten_fnames(TRUE);
1383 }
1384#endif
1385
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001386 // patch probably has written over the screen
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001387 redraw_later(UPD_CLEAR);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001388
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001389 // Delete any .orig or .rej file created.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001390 STRCPY(buf, tmp_new);
1391 STRCAT(buf, ".orig");
1392 mch_remove(buf);
1393 STRCPY(buf, tmp_new);
1394 STRCAT(buf, ".rej");
1395 mch_remove(buf);
1396
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001397 // Only continue if the output file was created.
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001398 if (mch_stat((char *)tmp_new, &st) < 0 || st.st_size == 0)
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001399 emsg(_(e_cannot_read_patch_output));
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001400 else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001401 {
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001402 if (curbuf->b_fname != NULL)
1403 {
1404 newname = vim_strnsave(curbuf->b_fname,
Bram Moolenaardf44a272020-06-07 20:49:05 +02001405 STRLEN(curbuf->b_fname) + 4);
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001406 if (newname != NULL)
1407 STRCAT(newname, ".new");
1408 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001409
1410#ifdef FEAT_GUI
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001411 need_mouse_correct = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001412#endif
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001413 // don't use a new tab page, each tab page has its own diffs
Bram Moolenaare1004402020-10-24 20:49:43 +02001414 cmdmod.cmod_tab = 0;
Bram Moolenaar80a94a52006-02-23 21:26:58 +00001415
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001416 if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001417 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001418 // Pretend it was a ":split fname" command
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001419 eap->cmdidx = CMD_split;
1420 eap->arg = tmp_new;
1421 do_exedit(eap, old_curwin);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001422
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001423 // check that split worked and editing tmp_new
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001424 if (curwin != old_curwin && win_valid(old_curwin))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001425 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001426 // Set 'diff', 'scrollbind' on and 'wrap' off.
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001427 diff_win_options(curwin, TRUE);
1428 diff_win_options(old_curwin, TRUE);
1429
1430 if (newname != NULL)
1431 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001432 // do a ":file filename.new" on the patched buffer
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001433 eap->arg = newname;
1434 ex_file(eap);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001435
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001436 // Do filetype detection with the new name.
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001437 if (au_has_group((char_u *)"filetypedetect"))
Bram Moolenaar23a971d2023-04-04 22:04:53 +01001438 do_cmdline_cmd(
1439 (char_u *)":doau filetypedetect BufRead");
Bram Moolenaar6ec0a6c2009-07-22 14:23:13 +00001440 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001441 }
1442 }
1443 }
1444
1445theend:
1446 if (tmp_orig != NULL)
1447 mch_remove(tmp_orig);
1448 vim_free(tmp_orig);
1449 if (tmp_new != NULL)
1450 mch_remove(tmp_new);
1451 vim_free(tmp_new);
1452 vim_free(newname);
1453 vim_free(buf);
1454#ifdef UNIX
1455 vim_free(fullname);
1456#endif
Bram Moolenaara95ab322017-03-11 19:21:53 +01001457 vim_free(esc_name);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001458#ifdef FEAT_BROWSE
1459 vim_free(browseFile);
Bram Moolenaare1004402020-10-24 20:49:43 +02001460 cmdmod.cmod_flags = save_cmod_flags;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001461#endif
1462}
1463
1464/*
1465 * Split the window and edit another file, setting options to show the diffs.
1466 */
1467 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001468ex_diffsplit(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001469{
1470 win_T *old_curwin = curwin;
Bram Moolenaar7c0a2f32016-07-10 22:11:16 +02001471 bufref_T old_curbuf;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001472
Bram Moolenaar7c0a2f32016-07-10 22:11:16 +02001473 set_bufref(&old_curbuf, curbuf);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001474#ifdef FEAT_GUI
1475 need_mouse_correct = TRUE;
1476#endif
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001477 // Need to compute w_fraction when no redraw happened yet.
Bram Moolenaar46328f92016-08-28 15:39:57 +02001478 validate_cursor();
1479 set_fraction(curwin);
1480
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001481 // don't use a new tab page, each tab page has its own diffs
Bram Moolenaare1004402020-10-24 20:49:43 +02001482 cmdmod.cmod_tab = 0;
Bram Moolenaar80a94a52006-02-23 21:26:58 +00001483
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001484 if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) == FAIL)
1485 return;
1486
1487 // Pretend it was a ":split fname" command
1488 eap->cmdidx = CMD_split;
1489 curwin->w_p_diff = TRUE;
1490 do_exedit(eap, old_curwin);
1491
1492 if (curwin == old_curwin) // split didn't work
1493 return;
1494
1495 // Set 'diff', 'scrollbind' on and 'wrap' off.
1496 diff_win_options(curwin, TRUE);
1497 if (win_valid(old_curwin))
Bram Moolenaar071d4272004-06-13 20:20:40 +00001498 {
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001499 diff_win_options(old_curwin, TRUE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001500
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001501 if (bufref_valid(&old_curbuf))
1502 // Move the cursor position to that of the old window.
1503 curwin->w_cursor.lnum = diff_get_corresponding_line(
1504 old_curbuf.br_buf, old_curwin->w_cursor.lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001505 }
Yegappan Lakshmanan465de3a2022-12-26 12:50:04 +00001506 // Now that lines are folded scroll to show the cursor at the same
1507 // relative position.
1508 scroll_to_fraction(curwin, curwin->w_height);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001509}
1510
1511/*
Bram Moolenaar84a05ac2013-05-06 04:24:17 +02001512 * Set options to show diffs for the current window.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001513 */
Bram Moolenaar071d4272004-06-13 20:20:40 +00001514 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001515ex_diffthis(exarg_T *eap UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001516{
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001517 // Set 'diff', 'scrollbind' on and 'wrap' off.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001518 diff_win_options(curwin, TRUE);
1519}
1520
Bram Moolenaar04f62f82017-07-19 18:18:39 +02001521 static void
1522set_diff_option(win_T *wp, int value)
1523{
1524 win_T *old_curwin = curwin;
1525
1526 curwin = wp;
1527 curbuf = curwin->w_buffer;
1528 ++curbuf_lock;
Bram Moolenaar31e5c602022-04-15 13:53:33 +01001529 set_option_value_give_err((char_u *)"diff", (long)value, NULL, OPT_LOCAL);
Bram Moolenaar04f62f82017-07-19 18:18:39 +02001530 --curbuf_lock;
1531 curwin = old_curwin;
1532 curbuf = curwin->w_buffer;
1533}
1534
Bram Moolenaar071d4272004-06-13 20:20:40 +00001535/*
1536 * Set options in window "wp" for diff mode.
1537 */
1538 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001539diff_win_options(
1540 win_T *wp,
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001541 int addbuf) // Add buffer to diff.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001542{
Bram Moolenaarf4d7f942010-02-24 14:34:19 +01001543# ifdef FEAT_FOLDING
1544 win_T *old_curwin = curwin;
1545
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001546 // close the manually opened folds
Bram Moolenaarf4d7f942010-02-24 14:34:19 +01001547 curwin = wp;
1548 newFoldLevel();
1549 curwin = old_curwin;
1550# endif
1551
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001552 // Use 'scrollbind' and 'cursorbind' when available
Bram Moolenaar43929962015-07-03 15:06:56 +02001553 if (!wp->w_p_diff)
Bram Moolenaara87aa802013-07-03 15:47:03 +02001554 wp->w_p_scb_save = wp->w_p_scb;
Bram Moolenaar3368ea22010-09-21 16:56:35 +02001555 wp->w_p_scb = TRUE;
Bram Moolenaar43929962015-07-03 15:06:56 +02001556 if (!wp->w_p_diff)
Bram Moolenaara87aa802013-07-03 15:47:03 +02001557 wp->w_p_crb_save = wp->w_p_crb;
Bram Moolenaar860cae12010-06-05 23:22:07 +02001558 wp->w_p_crb = TRUE;
Bram Moolenaar4223d432021-02-10 13:18:17 +01001559 if (!(diff_flags & DIFF_FOLLOWWRAP))
1560 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00001561 if (!wp->w_p_diff)
Bram Moolenaar4223d432021-02-10 13:18:17 +01001562 wp->w_p_wrap_save = wp->w_p_wrap;
Lewis Russelld9da86e2021-12-28 13:54:41 +00001563 wp->w_p_wrap = FALSE;
zeertzjq9e7f1fc2024-03-16 09:40:22 +01001564 wp->w_skipcol = 0;
Bram Moolenaar4223d432021-02-10 13:18:17 +01001565 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001566# ifdef FEAT_FOLDING
Bram Moolenaar43929962015-07-03 15:06:56 +02001567 if (!wp->w_p_diff)
1568 {
1569 if (wp->w_p_diff_saved)
1570 free_string_option(wp->w_p_fdm_save);
Bram Moolenaara87aa802013-07-03 15:47:03 +02001571 wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm);
Bram Moolenaar43929962015-07-03 15:06:56 +02001572 }
Bram Moolenaar20c023a2019-05-26 21:03:24 +02001573 set_string_option_direct_in_win(wp, (char_u *)"fdm", -1, (char_u *)"diff",
Bram Moolenaar5e3cb7e2006-02-27 23:58:35 +00001574 OPT_LOCAL|OPT_FREE, 0);
Bram Moolenaar43929962015-07-03 15:06:56 +02001575 if (!wp->w_p_diff)
Bram Moolenaara87aa802013-07-03 15:47:03 +02001576 {
1577 wp->w_p_fdc_save = wp->w_p_fdc;
1578 wp->w_p_fen_save = wp->w_p_fen;
1579 wp->w_p_fdl_save = wp->w_p_fdl;
1580 }
Bram Moolenaarf4d7f942010-02-24 14:34:19 +01001581 wp->w_p_fdc = diff_foldcolumn;
1582 wp->w_p_fen = TRUE;
1583 wp->w_p_fdl = 0;
1584 foldUpdateAll(wp);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001585 // make sure topline is not halfway a fold
Bram Moolenaarf4d7f942010-02-24 14:34:19 +01001586 changed_window_setting_win(wp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001587# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001588 if (vim_strchr(p_sbo, 'h') == NULL)
1589 do_cmdline_cmd((char_u *)"set sbo+=hor");
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001590 // Save the current values, to be restored in ex_diffoff().
Bram Moolenaara87aa802013-07-03 15:47:03 +02001591 wp->w_p_diff_saved = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001592
Bram Moolenaar04f62f82017-07-19 18:18:39 +02001593 set_diff_option(wp, TRUE);
Bram Moolenaar43929962015-07-03 15:06:56 +02001594
Bram Moolenaar071d4272004-06-13 20:20:40 +00001595 if (addbuf)
1596 diff_buf_add(wp->w_buffer);
Bram Moolenaara4d158b2022-08-14 14:17:45 +01001597 redraw_win_later(wp, UPD_NOT_VALID);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001598}
1599
1600/*
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001601 * Set options not to show diffs. For the current window or all windows.
Bram Moolenaarf740b292006-02-16 22:11:02 +00001602 * Only in the current tab page.
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001603 */
1604 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001605ex_diffoff(exarg_T *eap)
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001606{
1607 win_T *wp;
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001608 int diffwin = FALSE;
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001609
Bram Moolenaar29323592016-07-24 22:04:11 +02001610 FOR_ALL_WINDOWS(wp)
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001611 {
Bram Moolenaar00462ff2013-09-20 20:13:53 +02001612 if (eap->forceit ? wp->w_p_diff : wp == curwin)
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001613 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001614 // Set 'diff' off. If option values were saved in
1615 // diff_win_options(), restore the ones whose settings seem to have
1616 // been left over from diff mode.
Bram Moolenaar04f62f82017-07-19 18:18:39 +02001617 set_diff_option(wp, FALSE);
Bram Moolenaara87aa802013-07-03 15:47:03 +02001618
Bram Moolenaara87aa802013-07-03 15:47:03 +02001619 if (wp->w_p_diff_saved)
1620 {
Bram Moolenaar33ca6bf2013-07-17 13:43:39 +02001621
Bram Moolenaar43929962015-07-03 15:06:56 +02001622 if (wp->w_p_scb)
1623 wp->w_p_scb = wp->w_p_scb_save;
Bram Moolenaar43929962015-07-03 15:06:56 +02001624 if (wp->w_p_crb)
1625 wp->w_p_crb = wp->w_p_crb_save;
Bram Moolenaar4223d432021-02-10 13:18:17 +01001626 if (!(diff_flags & DIFF_FOLLOWWRAP))
1627 {
zeertzjq9e7f1fc2024-03-16 09:40:22 +01001628 if (!wp->w_p_wrap && wp->w_p_wrap_save)
1629 {
1630 wp->w_p_wrap = TRUE;
1631 wp->w_leftcol = 0;
1632 }
Bram Moolenaar4223d432021-02-10 13:18:17 +01001633 }
Bram Moolenaar43929962015-07-03 15:06:56 +02001634#ifdef FEAT_FOLDING
1635 free_string_option(wp->w_p_fdm);
Bram Moolenaar79a213d2017-05-16 13:15:18 +02001636 wp->w_p_fdm = vim_strsave(
1637 *wp->w_p_fdm_save ? wp->w_p_fdm_save : (char_u*)"manual");
Bram Moolenaar43929962015-07-03 15:06:56 +02001638
1639 if (wp->w_p_fdc == diff_foldcolumn)
1640 wp->w_p_fdc = wp->w_p_fdc_save;
1641 if (wp->w_p_fdl == 0)
1642 wp->w_p_fdl = wp->w_p_fdl_save;
1643
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001644 // Only restore 'foldenable' when 'foldmethod' is not
1645 // "manual", otherwise we continue to show the diff folds.
Bram Moolenaar43929962015-07-03 15:06:56 +02001646 if (wp->w_p_fen)
1647 wp->w_p_fen = foldmethodIsManual(wp) ? FALSE
1648 : wp->w_p_fen_save;
1649
1650 foldUpdateAll(wp);
Bram Moolenaar43929962015-07-03 15:06:56 +02001651#endif
Bram Moolenaar33ca6bf2013-07-17 13:43:39 +02001652 }
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001653 // remove filler lines
Bram Moolenaare67d5462016-08-27 22:40:42 +02001654 wp->w_topfill = 0;
1655
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001656 // make sure topline is not halfway a fold and cursor is
1657 // invalidated
Bram Moolenaare67d5462016-08-27 22:40:42 +02001658 changed_window_setting_win(wp);
Bram Moolenaar33ca6bf2013-07-17 13:43:39 +02001659
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001660 // Note: 'sbo' is not restored, it's a global option.
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001661 diff_buf_adjust(wp);
1662 }
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001663 diffwin |= wp->w_p_diff;
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001664 }
1665
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001666 // Also remove hidden buffers from the list.
Bram Moolenaar25ea0542017-02-03 23:16:28 +01001667 if (eap->forceit)
1668 diff_buf_clear();
1669
Bram Moolenaarc8234772019-11-10 21:00:27 +01001670 if (!diffwin)
1671 {
1672 diff_need_update = FALSE;
1673 curtab->tp_diff_invalid = FALSE;
1674 curtab->tp_diff_update = FALSE;
1675 diff_clear(curtab);
1676 }
1677
Dominique Pelleaf4a61a2021-12-27 17:21:41 +00001678 // Remove "hor" from 'scrollopt' if there are no diff windows left.
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001679 if (!diffwin && vim_strchr(p_sbo, 'h') != NULL)
1680 do_cmdline_cmd((char_u *)"set sbo-=hor");
Bram Moolenaar2df6dcc2004-07-12 15:53:54 +00001681}
1682
1683/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00001684 * Read the diff output and add each entry to the diff list.
1685 */
1686 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001687diff_read(
Bram Moolenaare828b762018-09-10 17:51:58 +02001688 int idx_orig, // idx of original file
1689 int idx_new, // idx of new file
Lewis Russelld9da86e2021-12-28 13:54:41 +00001690 diffio_T *dio) // diff output
Bram Moolenaar071d4272004-06-13 20:20:40 +00001691{
Bram Moolenaare828b762018-09-10 17:51:58 +02001692 FILE *fd = NULL;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001693 int line_hunk_idx = 0; // line or hunk index
Bram Moolenaar071d4272004-06-13 20:20:40 +00001694 diff_T *dprev = NULL;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001695 diff_T *dp = curtab->tp_first_diff;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001696 diff_T *dn, *dpl;
Lewis Russelld9da86e2021-12-28 13:54:41 +00001697 diffout_T *dout = &dio->dio_diff;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001698 char_u linebuf[LBUFLEN]; // only need to hold the diff line
Bram Moolenaare828b762018-09-10 17:51:58 +02001699 char_u *line;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001700 long off;
1701 int i;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01001702 int notset = TRUE; // block "*dp" not set yet
Bram Moolenaar49166972021-12-30 10:51:45 +00001703 diffhunk_T *hunk = NULL; // init to avoid gcc warning
Lewis Russelld9da86e2021-12-28 13:54:41 +00001704
Bram Moolenaare828b762018-09-10 17:51:58 +02001705 enum {
1706 DIFF_ED,
1707 DIFF_UNIFIED,
1708 DIFF_NONE
1709 } diffstyle = DIFF_NONE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001710
Bram Moolenaare828b762018-09-10 17:51:58 +02001711 if (dout->dout_fname == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001712 {
Bram Moolenaare828b762018-09-10 17:51:58 +02001713 diffstyle = DIFF_UNIFIED;
1714 }
1715 else
1716 {
1717 fd = mch_fopen((char *)dout->dout_fname, "r");
1718 if (fd == NULL)
1719 {
Bram Moolenaare1242042021-12-16 20:56:57 +00001720 emsg(_(e_cannot_read_diff_output));
Bram Moolenaare828b762018-09-10 17:51:58 +02001721 return;
1722 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001723 }
1724
Lewis Russelld9da86e2021-12-28 13:54:41 +00001725 if (!dio->dio_internal)
1726 {
1727 hunk = ALLOC_ONE(diffhunk_T);
1728 if (hunk == NULL)
Bram Moolenaar5d46dcf2022-03-25 14:46:47 +00001729 {
1730 if (fd != NULL)
1731 fclose(fd);
Lewis Russelld9da86e2021-12-28 13:54:41 +00001732 return;
Bram Moolenaar5d46dcf2022-03-25 14:46:47 +00001733 }
Lewis Russelld9da86e2021-12-28 13:54:41 +00001734 }
1735
Bram Moolenaar071d4272004-06-13 20:20:40 +00001736 for (;;)
1737 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00001738 if (dio->dio_internal)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001739 {
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001740 if (line_hunk_idx >= dout->dout_ga.ga_len)
1741 break; // did last hunk
1742 hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_hunk_idx++];
Bram Moolenaar071d4272004-06-13 20:20:40 +00001743 }
1744 else
1745 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00001746 if (fd == NULL)
1747 {
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001748 if (line_hunk_idx >= dout->dout_ga.ga_len)
Lewis Russelld9da86e2021-12-28 13:54:41 +00001749 break; // did last line
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001750 line = ((char_u **)dout->dout_ga.ga_data)[line_hunk_idx++];
Lewis Russelld9da86e2021-12-28 13:54:41 +00001751 }
Bram Moolenaar3b8defd2018-09-13 13:03:11 +02001752 else
Lewis Russelld9da86e2021-12-28 13:54:41 +00001753 {
1754 if (vim_fgets(linebuf, LBUFLEN, fd))
1755 break; // end of file
1756 line = linebuf;
1757 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001758
Lewis Russelld9da86e2021-12-28 13:54:41 +00001759 if (diffstyle == DIFF_NONE)
1760 {
1761 // Determine diff style.
1762 // ed like diff looks like this:
1763 // {first}[,{last}]c{first}[,{last}]
1764 // {first}a{first}[,{last}]
1765 // {first}[,{last}]d{first}
1766 //
1767 // unified diff looks like this:
1768 // --- file1 2018-03-20 13:23:35.783153140 +0100
1769 // +++ file2 2018-03-20 13:23:41.183156066 +0100
1770 // @@ -1,3 +1,5 @@
Keith Thompson184f71c2024-01-04 21:19:04 +01001771 if (SAFE_isdigit(*line))
Lewis Russelld9da86e2021-12-28 13:54:41 +00001772 diffstyle = DIFF_ED;
1773 else if ((STRNCMP(line, "@@ ", 3) == 0))
1774 diffstyle = DIFF_UNIFIED;
1775 else if ((STRNCMP(line, "--- ", 4) == 0)
1776 && (vim_fgets(linebuf, LBUFLEN, fd) == 0)
1777 && (STRNCMP(line, "+++ ", 4) == 0)
1778 && (vim_fgets(linebuf, LBUFLEN, fd) == 0)
1779 && (STRNCMP(line, "@@ ", 3) == 0))
1780 diffstyle = DIFF_UNIFIED;
1781 else
1782 // Format not recognized yet, skip over this line. Cygwin
1783 // diff may put a warning at the start of the file.
1784 continue;
1785 }
1786
1787 if (diffstyle == DIFF_ED)
1788 {
Keith Thompson184f71c2024-01-04 21:19:04 +01001789 if (!SAFE_isdigit(*line))
Lewis Russelld9da86e2021-12-28 13:54:41 +00001790 continue; // not the start of a diff block
1791 if (parse_diff_ed(line, hunk) == FAIL)
1792 continue;
1793 }
1794 else if (diffstyle == DIFF_UNIFIED)
1795 {
1796 if (STRNCMP(line, "@@ ", 3) != 0)
1797 continue; // not the start of a diff block
1798 if (parse_diff_unified(line, hunk) == FAIL)
1799 continue;
1800 }
1801 else
1802 {
Bram Moolenaard82a47d2022-01-05 20:24:39 +00001803 emsg(_(e_invalid_diff_format));
Lewis Russelld9da86e2021-12-28 13:54:41 +00001804 break;
1805 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001806 }
1807
1808 // Go over blocks before the change, for which orig and new are equal.
1809 // Copy blocks from orig to new.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001810 while (dp != NULL
Lewis Russelld9da86e2021-12-28 13:54:41 +00001811 && hunk->lnum_orig > dp->df_lnum[idx_orig]
1812 + dp->df_count[idx_orig])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001813 {
1814 if (notset)
1815 diff_copy_entry(dprev, dp, idx_orig, idx_new);
1816 dprev = dp;
1817 dp = dp->df_next;
1818 notset = TRUE;
1819 }
1820
1821 if (dp != NULL
Lewis Russelld9da86e2021-12-28 13:54:41 +00001822 && hunk->lnum_orig <= dp->df_lnum[idx_orig]
1823 + dp->df_count[idx_orig]
1824 && hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001825 {
Bram Moolenaare828b762018-09-10 17:51:58 +02001826 // New block overlaps with existing block(s).
1827 // First find last block that overlaps.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001828 for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next)
Lewis Russelld9da86e2021-12-28 13:54:41 +00001829 if (hunk->lnum_orig + hunk->count_orig
1830 < dpl->df_next->df_lnum[idx_orig])
Bram Moolenaar071d4272004-06-13 20:20:40 +00001831 break;
1832
Bram Moolenaare828b762018-09-10 17:51:58 +02001833 // If the newly found block starts before the old one, set the
1834 // start back a number of lines.
Lewis Russelld9da86e2021-12-28 13:54:41 +00001835 off = dp->df_lnum[idx_orig] - hunk->lnum_orig;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001836 if (off > 0)
1837 {
1838 for (i = idx_orig; i < idx_new; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001839 if (curtab->tp_diffbuf[i] != NULL)
Yukihiro Nakadaira06fe70c2024-09-26 16:19:42 +02001840 {
Bram Moolenaar071d4272004-06-13 20:20:40 +00001841 dp->df_lnum[i] -= off;
Yukihiro Nakadaira06fe70c2024-09-26 16:19:42 +02001842 dp->df_count[i] += off;
1843 }
Lewis Russelld9da86e2021-12-28 13:54:41 +00001844 dp->df_lnum[idx_new] = hunk->lnum_new;
1845 dp->df_count[idx_new] = hunk->count_new;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001846 }
1847 else if (notset)
1848 {
Bram Moolenaare828b762018-09-10 17:51:58 +02001849 // new block inside existing one, adjust new block
Lewis Russelld9da86e2021-12-28 13:54:41 +00001850 dp->df_lnum[idx_new] = hunk->lnum_new + off;
1851 dp->df_count[idx_new] = hunk->count_new - off;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001852 }
1853 else
Yukihiro Nakadaira01f65092025-01-15 18:36:43 +01001854 {
Bram Moolenaare828b762018-09-10 17:51:58 +02001855 // second overlap of new block with existing block
Yee Cheng Chinbc08ceb2025-03-02 22:05:37 +01001856
1857 // if this hunk has different orig/new counts, adjust
1858 // the diff block size first. When we handled the first hunk we
1859 // would have expanded it to fit, without knowing that this
1860 // hunk exists
1861 int orig_size_in_dp = MIN(hunk->count_orig,
1862 dp->df_lnum[idx_orig] +
1863 dp->df_count[idx_orig] - hunk->lnum_orig);
1864 int size_diff = hunk->count_new - orig_size_in_dp;
1865 dp->df_count[idx_new] += size_diff;
1866
1867 // grow existing block to include the overlap completely
1868 off = hunk->lnum_new + hunk->count_new
1869 - (dp->df_lnum[idx_new] + dp->df_count[idx_new]);
1870 if (off > 0)
1871 dp->df_count[idx_new] += off;
Yukihiro Nakadaira01f65092025-01-15 18:36:43 +01001872 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001873
Bram Moolenaare828b762018-09-10 17:51:58 +02001874 // Adjust the size of the block to include all the lines to the
1875 // end of the existing block or the new diff, whatever ends last.
Lewis Russelld9da86e2021-12-28 13:54:41 +00001876 off = (hunk->lnum_orig + hunk->count_orig)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001877 - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
1878 if (off < 0)
1879 {
Yee Cheng Chinbc08ceb2025-03-02 22:05:37 +01001880 // new change ends in existing block, adjust the end. We only
1881 // need to do this once per block or we will over-adjust.
1882 if (notset || dp != dpl)
1883 {
1884 // adjusting by 'off' here is only correct if
1885 // there is not another hunk in this block. we
1886 // adjust for this when we encounter a second
1887 // overlap later.
1888 dp->df_count[idx_new] += -off;
1889 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001890 off = 0;
1891 }
Bram Moolenaard4b96bc2007-10-19 15:33:39 +00001892 for (i = idx_orig; i < idx_new; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001893 if (curtab->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001894 dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i]
1895 - dp->df_lnum[i] + off;
1896
Bram Moolenaare828b762018-09-10 17:51:58 +02001897 // Delete the diff blocks that have been merged into one.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001898 dn = dp->df_next;
1899 dp->df_next = dpl->df_next;
1900 while (dn != dp->df_next)
1901 {
1902 dpl = dn->df_next;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001903 clear_diffblock(dn);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001904 dn = dpl;
1905 }
1906 }
1907 else
1908 {
Bram Moolenaare828b762018-09-10 17:51:58 +02001909 // Allocate a new diffblock.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001910 dp = diff_alloc_new(curtab, dprev, dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001911 if (dp == NULL)
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001912 goto done;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001913
Lewis Russelld9da86e2021-12-28 13:54:41 +00001914 dp->df_lnum[idx_orig] = hunk->lnum_orig;
1915 dp->df_count[idx_orig] = hunk->count_orig;
1916 dp->df_lnum[idx_new] = hunk->lnum_new;
1917 dp->df_count[idx_new] = hunk->count_new;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001918
Bram Moolenaare828b762018-09-10 17:51:58 +02001919 // Set values for other buffers, these must be equal to the
1920 // original buffer, otherwise there would have been a change
1921 // already.
Bram Moolenaar071d4272004-06-13 20:20:40 +00001922 for (i = idx_orig + 1; i < idx_new; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001923 if (curtab->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001924 diff_copy_entry(dprev, dp, idx_orig, i);
1925 }
Bram Moolenaare828b762018-09-10 17:51:58 +02001926 notset = FALSE; // "*dp" has been set
Bram Moolenaar071d4272004-06-13 20:20:40 +00001927 }
1928
Bram Moolenaare828b762018-09-10 17:51:58 +02001929 // for remaining diff blocks orig and new are equal
Bram Moolenaar071d4272004-06-13 20:20:40 +00001930 while (dp != NULL)
1931 {
1932 if (notset)
1933 diff_copy_entry(dprev, dp, idx_orig, idx_new);
1934 dprev = dp;
1935 dp = dp->df_next;
1936 notset = TRUE;
1937 }
1938
Bram Moolenaareb3593b2006-04-22 22:33:57 +00001939done:
Lewis Russelld9da86e2021-12-28 13:54:41 +00001940 if (!dio->dio_internal)
1941 vim_free(hunk);
1942
Bram Moolenaare828b762018-09-10 17:51:58 +02001943 if (fd != NULL)
1944 fclose(fd);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001945}
1946
1947/*
1948 * Copy an entry at "dp" from "idx_orig" to "idx_new".
1949 */
1950 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001951diff_copy_entry(
1952 diff_T *dprev,
1953 diff_T *dp,
1954 int idx_orig,
1955 int idx_new)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001956{
1957 long off;
1958
1959 if (dprev == NULL)
1960 off = 0;
1961 else
1962 off = (dprev->df_lnum[idx_orig] + dprev->df_count[idx_orig])
1963 - (dprev->df_lnum[idx_new] + dprev->df_count[idx_new]);
1964 dp->df_lnum[idx_new] = dp->df_lnum[idx_orig] - off;
1965 dp->df_count[idx_new] = dp->df_count[idx_orig];
1966}
1967
1968/*
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001969 * Clear the list of diffblocks for tab page "tp".
Bram Moolenaar071d4272004-06-13 20:20:40 +00001970 */
Bram Moolenaarea408852005-06-25 22:49:46 +00001971 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01001972diff_clear(tabpage_T *tp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001973{
1974 diff_T *p, *next_p;
1975
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001976 for (p = tp->tp_first_diff; p != NULL; p = next_p)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001977 {
1978 next_p = p->df_next;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01001979 clear_diffblock(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001980 }
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00001981 tp->tp_first_diff = NULL;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001982}
1983
1984/*
Jonathon7c7a4e62025-01-12 09:58:00 +01001985 * return true if the options are set to use diff linematch
1986 */
1987 static int
1988diff_linematch(diff_T *dp)
1989{
1990 if (!(diff_flags & DIFF_LINEMATCH))
1991 return 0;
1992
1993 // are there more than three diff buffers?
1994 int tsize = 0;
1995 for (int i = 0; i < DB_COUNT; i++)
1996 {
1997 if (curtab->tp_diffbuf[i] != NULL)
1998 {
1999 // for the rare case (bug?) that the count of a diff block is
2000 // negative, do not run the algorithm because this will try to
2001 // allocate a negative amount of space and crash
2002 if (dp->df_count[i] < 0)
2003 return FALSE;
2004 tsize += dp->df_count[i];
2005 }
2006 }
2007
2008 // avoid allocating a huge array because it will lag
2009 return tsize <= linematch_lines;
2010}
2011
2012 static int
2013get_max_diff_length(const diff_T *dp)
2014{
2015 int maxlength = 0;
2016
2017 for (int k = 0; k < DB_COUNT; k++)
2018 {
2019 if (curtab->tp_diffbuf[k] != NULL)
2020 {
2021 if (dp->df_count[k] > maxlength)
2022 maxlength = dp->df_count[k];
2023 }
2024 }
2025 return maxlength;
2026}
2027
2028 static void
2029find_top_diff_block(
2030 diff_T **thistopdiff,
2031 diff_T **nextblockblock,
2032 int fromidx,
2033 int topline)
2034{
2035 diff_T *topdiff = NULL;
2036 diff_T *localtopdiff = NULL;
2037 int topdiffchange = 0;
2038
2039 for (topdiff = curtab->tp_first_diff; topdiff != NULL;
2040 topdiff = topdiff->df_next)
2041 {
2042 // set the top of the current overlapping diff block set as we
2043 // iterate through all of the sets of overlapping diff blocks
2044 if (!localtopdiff || topdiffchange)
2045 {
2046 localtopdiff = topdiff;
2047 topdiffchange = 0;
2048 }
2049
2050 // check if the fromwin topline is matched by the current diff. if so,
2051 // set it to the top of the diff block
2052 if (topline >= topdiff->df_lnum[fromidx] && topline <=
2053 (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx]))
2054 {
2055 // this line is inside the current diff block, so we will save the
2056 // top block of the set of blocks to refer to later
2057 if ((*thistopdiff) == NULL)
2058 (*thistopdiff) = localtopdiff;
2059 }
2060
2061 // check if the next set of overlapping diff blocks is next
2062 if (!(topdiff->df_next && (topdiff->df_next->df_lnum[fromidx] ==
2063 (topdiff->df_lnum[fromidx] +
2064 topdiff->df_count[fromidx]))))
2065 {
2066 // mark that the next diff block is belongs to a different set of
2067 // overlapping diff blocks
2068 topdiffchange = 1;
2069
2070 // if we already have found that the line number is inside a diff
2071 // block, set the marker of the next block and finish the iteration
2072 if (*thistopdiff)
2073 {
2074 (*nextblockblock) = topdiff->df_next;
2075 break;
2076 }
2077 }
2078 }
2079}
2080
2081 static void
2082count_filler_lines_and_topline(
2083 int *curlinenum_to,
2084 int *linesfiller,
2085 const diff_T *thistopdiff,
2086 const int toidx,
2087 int virtual_lines_passed)
2088{
2089 const diff_T *curdif = thistopdiff;
2090 int ch_virtual_lines = 0;
2091 int isfiller = FALSE;
2092
2093 while (virtual_lines_passed > 0)
2094 {
2095 if (ch_virtual_lines)
2096 {
2097 virtual_lines_passed--;
2098 ch_virtual_lines--;
2099 if (!isfiller)
2100 (*curlinenum_to)++;
2101 else
2102 (*linesfiller)++;
2103 }
2104 else
2105 {
2106 (*linesfiller) = 0;
Christian Brabandta9f77be2025-01-16 19:06:57 +01002107 if (curdif)
2108 {
2109 ch_virtual_lines = get_max_diff_length(curdif);
2110 isfiller = (curdif->df_count[toidx] ? FALSE : TRUE);
2111 }
Jonathon7c7a4e62025-01-12 09:58:00 +01002112 if (isfiller)
2113 {
2114 while (curdif && curdif->df_next &&
2115 curdif->df_lnum[toidx] ==
2116 curdif->df_next->df_lnum[toidx] &&
2117 curdif->df_next->df_count[toidx] == 0)
2118 {
2119 curdif = curdif->df_next;
2120 ch_virtual_lines += get_max_diff_length(curdif);
2121 }
2122 }
2123 if (curdif)
2124 curdif = curdif->df_next;
2125 }
2126 }
2127}
2128
2129 static void
2130calculate_topfill_and_topline(
2131 const int fromidx,
2132 const int toidx,
2133 const int from_topline,
2134 const int from_topfill,
2135 int *topfill,
2136 linenr_T *topline)
2137{
2138 // 1. find the position from the top of the diff block, and the start
2139 // of the next diff block
2140 diff_T *thistopdiff = NULL;
2141 diff_T *nextblockblock = NULL;
2142 int virtual_lines_passed = 0;
2143
2144 find_top_diff_block(&thistopdiff, &nextblockblock, fromidx, from_topline);
2145
2146 // count the virtual lines that have been passed
2147 diff_T *curdif = thistopdiff;
2148 while (curdif && (curdif->df_lnum[fromidx] + curdif->df_count[fromidx])
2149 <= from_topline)
2150 {
2151 virtual_lines_passed += get_max_diff_length(curdif);
2152
2153 curdif = curdif->df_next;
2154 }
2155
2156 if (curdif != nextblockblock)
2157 virtual_lines_passed += from_topline - curdif->df_lnum[fromidx];
2158 virtual_lines_passed -= from_topfill;
2159
2160 // count the same amount of virtual lines in the toidx buffer
2161 int curlinenum_to = thistopdiff->df_lnum[toidx];
2162 int linesfiller = 0;
2163
2164 count_filler_lines_and_topline(&curlinenum_to, &linesfiller, thistopdiff,
2165 toidx, virtual_lines_passed);
2166
2167 // count the number of filler lines that would normally be above this line
2168 int maxfiller = 0;
2169 for (diff_T *dpfillertest = thistopdiff; dpfillertest != NULL;
2170 dpfillertest = dpfillertest->df_next)
2171 {
2172 if (dpfillertest->df_lnum[toidx] == curlinenum_to)
2173 {
2174 while (dpfillertest && dpfillertest->df_lnum[toidx] ==
2175 curlinenum_to)
2176 {
2177 maxfiller += dpfillertest->df_count[toidx] ? 0 :
2178 get_max_diff_length(dpfillertest);
2179 dpfillertest = dpfillertest->df_next;
2180 }
2181 break;
2182 }
2183 }
2184 (*topfill) = maxfiller - linesfiller;
2185 (*topline) = curlinenum_to;
2186}
2187
2188 static int
2189linematched_filler_lines(diff_T *dp, int idx, linenr_T lnum, int *linestatus)
2190{
2191 int filler_lines_d1 = 0;
2192
2193 while (dp && dp->df_next &&
2194 lnum == (dp->df_lnum[idx] + dp->df_count[idx]) &&
2195 dp->df_next->df_lnum[idx] == lnum)
2196 {
2197 if (dp->df_count[idx] == 0)
2198 filler_lines_d1 += get_max_diff_length(dp);
2199 dp = dp->df_next;
2200 }
2201
2202 if (dp->df_count[idx] == 0)
2203 filler_lines_d1 += get_max_diff_length(dp);
2204
2205 if (lnum < dp->df_lnum[idx] + dp->df_count[idx])
2206 {
2207 int j = 0;
2208
2209 for (int i = 0; i < DB_COUNT; i++)
2210 {
2211 if (curtab->tp_diffbuf[i] != NULL)
2212 {
2213 if (dp->df_count[i])
2214 j++;
2215 }
2216 // is this an added line or a changed line?
2217 if (linestatus)
2218 (*linestatus) = (j == 1) ? -2 : -1;
2219 }
2220 }
2221
2222 return filler_lines_d1;
2223}
2224
2225// Apply results from the linematch algorithm and apply to 'dp' by splitting it
2226// into multiple adjacent diff blocks.
2227 static void
2228apply_linematch_results(
2229 diff_T *dp,
2230 size_t decisions_length,
2231 const int *decisions)
2232{
2233 // get the start line number here in each diff buffer, and then increment
2234 int line_numbers[DB_COUNT];
2235 int outputmap[DB_COUNT];
2236 size_t ndiffs = 0;
2237
2238 for (int i = 0; i < DB_COUNT; i++)
2239 {
2240 if (curtab->tp_diffbuf[i] != NULL)
2241 {
2242 line_numbers[i] = dp->df_lnum[i];
2243 dp->df_count[i] = 0;
2244
2245 // Keep track of the index of the diff buffer we are using here.
2246 // We will use this to write the output of the algorithm to
2247 // diff_T structs at the correct indexes
2248 outputmap[ndiffs] = i;
2249 ndiffs++;
2250 }
2251 }
2252
2253 // write the diffs starting with the current diff block
2254 diff_T *dp_s = dp;
2255 for (size_t i = 0; i < decisions_length; i++)
2256 {
2257 // Don't allocate on first iter since we can reuse the initial
2258 // diffblock
2259 if (i != 0 && (decisions[i - 1] != decisions[i]))
2260 {
2261 // create new sub diff blocks to segment the original diff block
2262 // which we further divided by running the linematch algorithm
2263 dp_s = diff_alloc_new(curtab, dp_s, dp_s->df_next);
2264 dp_s->is_linematched = TRUE;
2265 for (int j = 0; j < DB_COUNT; j++)
2266 {
2267 if (curtab->tp_diffbuf[j] != NULL)
2268 {
2269 dp_s->df_lnum[j] = line_numbers[j];
2270 dp_s->df_count[j] = 0;
2271 }
2272 }
2273 }
2274 for (size_t j = 0; j < ndiffs; j++)
2275 {
2276 if (decisions[i] & (1 << j))
2277 {
2278 // will need to use the map here
2279 dp_s->df_count[outputmap[j]]++;
2280 line_numbers[outputmap[j]]++;
2281 }
2282 }
2283 }
2284 dp->is_linematched = TRUE;
2285}
2286
2287 static void
2288run_linematch_algorithm(diff_T *dp)
2289{
2290 // define buffers for diff algorithm
2291 diffin_T diffbufs_mm[DB_COUNT];
2292 const mmfile_t *diffbufs[DB_COUNT];
2293 int diff_length[DB_COUNT];
2294 size_t ndiffs = 0;
2295
2296 for (int i = 0; i < DB_COUNT; i++)
2297 {
2298 if (curtab->tp_diffbuf[i] != NULL)
2299 {
2300 // write the contents of the entire buffer to
2301 // diffbufs_mm[diffbuffers_count]
2302 if (dp->df_count[i] > 0)
2303 {
2304 diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs],
2305 dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1);
2306 }
2307 else
2308 {
2309 diffbufs_mm[ndiffs].din_mmfile.size = 0;
2310 diffbufs_mm[ndiffs].din_mmfile.ptr = NULL;
2311 }
2312
2313 diffbufs[ndiffs] = &diffbufs_mm[ndiffs].din_mmfile;
2314
2315 // keep track of the length of this diff block to pass it to the
2316 // linematch algorithm
2317 diff_length[ndiffs] = dp->df_count[i];
2318
2319 // increment the amount of diff buffers we are passing to the
2320 // algorithm
2321 ndiffs++;
2322 }
2323 }
2324
2325 // we will get the output of the linematch algorithm in the format of an
2326 // array of integers (*decisions) and the length of that array
2327 // (decisions_length)
2328 int *decisions = NULL;
2329 const int iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0 ? 1 : 0;
2330 size_t decisions_length =
2331 linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite);
2332
2333 for (size_t i = 0; i < ndiffs; i++)
2334 free(diffbufs_mm[i].din_mmfile.ptr); // TODO should this be vim_free ?
2335
2336 apply_linematch_results(dp, decisions_length, decisions);
2337
2338 free(decisions);
2339}
2340
2341/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002342 * Check diff status for line "lnum" in buffer "buf":
2343 * Returns 0 for nothing special
2344 * Returns -1 for a line that should be highlighted as changed.
2345 * Returns -2 for a line that should be highlighted as added/deleted.
2346 * Returns > 0 for inserting that many filler lines above it (never happens
2347 * when 'diffopt' doesn't contain "filler").
2348 * This should only be used for windows where 'diff' is set.
Jonathon7c7a4e62025-01-12 09:58:00 +01002349 * When diffopt contains linematch, a changed/added/deleted line
2350 * may also have filler lines above it. In such a case, the possibilities
2351 * are no longer mutually exclusive. The number of filler lines is
2352 * returned from diff_check, and the integer 'linestatus' passed by
2353 * pointer is set to -1 to indicate a changed line, and -2 to indicate an
2354 * added line
Bram Moolenaar071d4272004-06-13 20:20:40 +00002355 */
2356 int
Jonathon7c7a4e62025-01-12 09:58:00 +01002357diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002358{
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002359 int idx; // index in tp_diffbuf[] for this buffer
Bram Moolenaar071d4272004-06-13 20:20:40 +00002360 diff_T *dp;
2361 int maxcount;
2362 int i;
2363 buf_T *buf = wp->w_buffer;
2364 int cmp;
2365
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002366 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002367 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00002368
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002369 if (curtab->tp_first_diff == NULL || !wp->w_p_diff) // no diffs at all
Bram Moolenaar071d4272004-06-13 20:20:40 +00002370 return 0;
2371
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002372 // safety check: "lnum" must be a buffer line
Bram Moolenaar071d4272004-06-13 20:20:40 +00002373 if (lnum < 1 || lnum > buf->b_ml.ml_line_count + 1)
2374 return 0;
2375
2376 idx = diff_buf_idx(buf);
2377 if (idx == DB_COUNT)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002378 return 0; // no diffs for buffer "buf"
Bram Moolenaar071d4272004-06-13 20:20:40 +00002379
2380#ifdef FEAT_FOLDING
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002381 // A closed fold never has filler lines.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002382 if (hasFoldingWin(wp, lnum, NULL, NULL, TRUE, NULL))
2383 return 0;
2384#endif
2385
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002386 // search for a change that includes "lnum" in the list of diffblocks.
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002387 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002388 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
2389 break;
2390 if (dp == NULL || lnum < dp->df_lnum[idx])
2391 return 0;
2392
Jonathon7c7a4e62025-01-12 09:58:00 +01002393 // Don't run linematch when lnum is offscreen. Useful for scrollbind
2394 // calculations which need to count all the filler lines above the screen.
2395 if (lnum >= wp->w_topline && lnum < wp->w_botline
Jonathonca307ef2025-01-17 13:37:35 +01002396 && !dp->is_linematched && diff_linematch(dp)
2397 && diff_check_sanity(curtab, dp))
Jonathon7c7a4e62025-01-12 09:58:00 +01002398 run_linematch_algorithm(dp);
2399
2400 if (dp->is_linematched)
2401 return linematched_filler_lines(dp, idx, lnum, linestatus);
2402
Bram Moolenaar071d4272004-06-13 20:20:40 +00002403 if (lnum < dp->df_lnum[idx] + dp->df_count[idx])
2404 {
2405 int zero = FALSE;
2406
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002407 // Changed or inserted line. If the other buffers have a count of
2408 // zero, the lines were inserted. If the other buffers have the same
2409 // count, check if the lines are identical.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002410 cmp = FALSE;
2411 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002412 if (i != idx && curtab->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002413 {
2414 if (dp->df_count[i] == 0)
2415 zero = TRUE;
2416 else
2417 {
2418 if (dp->df_count[i] != dp->df_count[idx])
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002419 return -1; // nr of lines changed.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002420 cmp = TRUE;
2421 }
2422 }
2423 if (cmp)
2424 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002425 // Compare all lines. If they are equal the lines were inserted
2426 // in some buffers, deleted in others, but not changed.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002427 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar7c0a2f32016-07-10 22:11:16 +02002428 if (i != idx && curtab->tp_diffbuf[i] != NULL
2429 && dp->df_count[i] != 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002430 if (!diff_equal_entry(dp, idx, i))
2431 return -1;
2432 }
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002433 // If there is no buffer with zero lines then there is no difference
2434 // any longer. Happens when making a change (or undo) that removes
2435 // the difference. Can't remove the entry here, we might be halfway
2436 // updating the window. Just report the text as unchanged. Other
2437 // windows might still show the change though.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002438 if (zero == FALSE)
2439 return 0;
2440 return -2;
2441 }
2442
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002443 // If 'diffopt' doesn't contain "filler", return 0.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002444 if (!(diff_flags & DIFF_FILLER))
2445 return 0;
2446
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002447 // Insert filler lines above the line just below the change. Will return
2448 // 0 when this buf had the max count.
Jonathon7c7a4e62025-01-12 09:58:00 +01002449 maxcount = get_max_diff_length(dp);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002450 return maxcount - dp->df_count[idx];
2451}
2452
Jonathon7c7a4e62025-01-12 09:58:00 +01002453 int
2454diff_check(win_T *wp, linenr_T lnum)
2455{
2456 return diff_check_with_linestatus(wp, lnum, NULL);
2457}
2458
Bram Moolenaar071d4272004-06-13 20:20:40 +00002459/*
2460 * Compare two entries in diff "*dp" and return TRUE if they are equal.
2461 */
2462 static int
Bram Moolenaar7454a062016-01-30 15:14:10 +01002463diff_equal_entry(diff_T *dp, int idx1, int idx2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002464{
2465 int i;
2466 char_u *line;
2467 int cmp;
2468
2469 if (dp->df_count[idx1] != dp->df_count[idx2])
2470 return FALSE;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002471 if (diff_check_sanity(curtab, dp) == FAIL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002472 return FALSE;
2473 for (i = 0; i < dp->df_count[idx1]; ++i)
2474 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002475 line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1],
Bram Moolenaar071d4272004-06-13 20:20:40 +00002476 dp->df_lnum[idx1] + i, FALSE));
2477 if (line == NULL)
2478 return FALSE;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002479 cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2],
Bram Moolenaar071d4272004-06-13 20:20:40 +00002480 dp->df_lnum[idx2] + i, FALSE));
2481 vim_free(line);
2482 if (cmp != 0)
2483 return FALSE;
2484 }
2485 return TRUE;
2486}
2487
2488/*
Bram Moolenaarae96b8d2017-09-03 15:04:21 +02002489 * Compare the characters at "p1" and "p2". If they are equal (possibly
2490 * ignoring case) return TRUE and set "len" to the number of bytes.
2491 */
2492 static int
2493diff_equal_char(char_u *p1, char_u *p2, int *len)
2494{
Bram Moolenaarae96b8d2017-09-03 15:04:21 +02002495 int l = (*mb_ptr2len)(p1);
2496
2497 if (l != (*mb_ptr2len)(p2))
2498 return FALSE;
2499 if (l > 1)
2500 {
2501 if (STRNCMP(p1, p2, l) != 0
2502 && (!enc_utf8
2503 || !(diff_flags & DIFF_ICASE)
2504 || utf_fold(utf_ptr2char(p1))
2505 != utf_fold(utf_ptr2char(p2))))
2506 return FALSE;
2507 *len = l;
2508 }
2509 else
Bram Moolenaarae96b8d2017-09-03 15:04:21 +02002510 {
2511 if ((*p1 != *p2)
2512 && (!(diff_flags & DIFF_ICASE)
2513 || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2)))
2514 return FALSE;
2515 *len = 1;
2516 }
2517 return TRUE;
2518}
2519
2520/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00002521 * Compare strings "s1" and "s2" according to 'diffopt'.
2522 * Return non-zero when they are different.
2523 */
2524 static int
Bram Moolenaar7454a062016-01-30 15:14:10 +01002525diff_cmp(char_u *s1, char_u *s2)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002526{
2527 char_u *p1, *p2;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002528 int l;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002529
Bram Moolenaar785fc652018-09-15 19:17:38 +02002530 if ((diff_flags & DIFF_IBLANK)
2531 && (*skipwhite(s1) == NUL || *skipwhite(s2) == NUL))
2532 return 0;
2533
2534 if ((diff_flags & (DIFF_ICASE | ALL_WHITE_DIFF)) == 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002535 return STRCMP(s1, s2);
Bram Moolenaar785fc652018-09-15 19:17:38 +02002536 if ((diff_flags & DIFF_ICASE) && !(diff_flags & ALL_WHITE_DIFF))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002537 return MB_STRICMP(s1, s2);
2538
Bram Moolenaar071d4272004-06-13 20:20:40 +00002539 p1 = s1;
2540 p2 = s2;
Bram Moolenaar785fc652018-09-15 19:17:38 +02002541
2542 // Ignore white space changes and possibly ignore case.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002543 while (*p1 != NUL && *p2 != NUL)
2544 {
Bram Moolenaar785fc652018-09-15 19:17:38 +02002545 if (((diff_flags & DIFF_IWHITE)
2546 && VIM_ISWHITE(*p1) && VIM_ISWHITE(*p2))
2547 || ((diff_flags & DIFF_IWHITEALL)
2548 && (VIM_ISWHITE(*p1) || VIM_ISWHITE(*p2))))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002549 {
2550 p1 = skipwhite(p1);
2551 p2 = skipwhite(p2);
2552 }
2553 else
2554 {
Bram Moolenaarae96b8d2017-09-03 15:04:21 +02002555 if (!diff_equal_char(p1, p2, &l))
Bram Moolenaar071d4272004-06-13 20:20:40 +00002556 break;
Bram Moolenaarae96b8d2017-09-03 15:04:21 +02002557 p1 += l;
2558 p2 += l;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002559 }
2560 }
2561
Bram Moolenaar785fc652018-09-15 19:17:38 +02002562 // Ignore trailing white space.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002563 p1 = skipwhite(p1);
2564 p2 = skipwhite(p2);
2565 if (*p1 != NUL || *p2 != NUL)
2566 return 1;
2567 return 0;
2568}
2569
2570/*
2571 * Return the number of filler lines above "lnum".
2572 */
2573 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01002574diff_check_fill(win_T *wp, linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002575{
2576 int n;
2577
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002578 // be quick when there are no filler lines
Bram Moolenaar071d4272004-06-13 20:20:40 +00002579 if (!(diff_flags & DIFF_FILLER))
2580 return 0;
2581 n = diff_check(wp, lnum);
2582 if (n <= 0)
2583 return 0;
2584 return n;
2585}
2586
2587/*
2588 * Set the topline of "towin" to match the position in "fromwin", so that they
2589 * show the same diff'ed lines.
2590 */
2591 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01002592diff_set_topline(win_T *fromwin, win_T *towin)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002593{
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002594 buf_T *frombuf = fromwin->w_buffer;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002595 linenr_T lnum = fromwin->w_topline;
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002596 int fromidx;
2597 int toidx;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002598 diff_T *dp;
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002599 int max_count;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002600 int i;
2601
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002602 fromidx = diff_buf_idx(frombuf);
2603 if (fromidx == DB_COUNT)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002604 return; // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00002605
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002606 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002607 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00002608
2609 towin->w_topfill = 0;
2610
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002611 // search for a change that includes "lnum" in the list of diffblocks.
Bram Moolenaaraeea7212020-04-02 18:50:46 +02002612 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002613 if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx])
Bram Moolenaar071d4272004-06-13 20:20:40 +00002614 break;
2615 if (dp == NULL)
2616 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002617 // After last change, compute topline relative to end of file; no
2618 // filler lines.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002619 towin->w_topline = towin->w_buffer->b_ml.ml_line_count
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002620 - (frombuf->b_ml.ml_line_count - lnum);
Bram Moolenaar071d4272004-06-13 20:20:40 +00002621 }
2622 else
2623 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002624 // Find index for "towin".
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002625 toidx = diff_buf_idx(towin->w_buffer);
2626 if (toidx == DB_COUNT)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002627 return; // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00002628
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002629 towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]);
2630 if (lnum >= dp->df_lnum[fromidx])
Bram Moolenaar071d4272004-06-13 20:20:40 +00002631 {
Jonathon7c7a4e62025-01-12 09:58:00 +01002632 if (dp->is_linematched)
2633 {
2634 calculate_topfill_and_topline(fromidx, toidx,
2635 fromwin->w_topline,
2636 fromwin->w_topfill,
2637 &towin->w_topfill,
2638 &towin->w_topline);
2639 }
2640 else
2641 {
2642 // Inside a change: compute filler lines. With three or more
2643 // buffers we need to know the largest count.
2644 max_count = 0;
2645 for (i = 0; i < DB_COUNT; ++i)
2646 if (curtab->tp_diffbuf[i] != NULL
2647 && max_count < dp->df_count[i])
2648 max_count = dp->df_count[i];
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002649
Jonathon7c7a4e62025-01-12 09:58:00 +01002650 if (dp->df_count[toidx] == dp->df_count[fromidx])
Bram Moolenaar071d4272004-06-13 20:20:40 +00002651 {
Jonathon7c7a4e62025-01-12 09:58:00 +01002652 // same number of lines: use same filler count
2653 towin->w_topfill = fromwin->w_topfill;
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002654 }
Jonathon7c7a4e62025-01-12 09:58:00 +01002655 else if (dp->df_count[toidx] > dp->df_count[fromidx])
Bram Moolenaarbb8f88b2008-01-18 16:40:00 +00002656 {
2657 if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx])
Jonathon7c7a4e62025-01-12 09:58:00 +01002658 {
2659 // more lines in towin and fromwin doesn't show diff
2660 // lines, only filler lines
2661 if (max_count - fromwin->w_topfill >= dp->df_count[toidx])
2662 {
2663 // towin also only shows filler lines
2664 towin->w_topline = dp->df_lnum[toidx]
2665 + dp->df_count[toidx];
2666 towin->w_topfill = fromwin->w_topfill;
2667 }
2668 else
2669 // towin still has some diff lines to show
2670 towin->w_topline = dp->df_lnum[toidx]
2671 + max_count - fromwin->w_topfill;
2672 }
2673 }
2674 else if (towin->w_topline >= dp->df_lnum[toidx]
2675 + dp->df_count[toidx])
2676 {
2677 // less lines in towin and no diff lines to show: compute
2678 // filler lines
2679 towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx];
2680 if (diff_flags & DIFF_FILLER)
2681 {
2682 if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx])
2683 // fromwin is also out of diff lines
2684 towin->w_topfill = fromwin->w_topfill;
2685 else
2686 // fromwin has some diff lines
2687 towin->w_topfill = dp->df_lnum[fromidx] +
2688 max_count - lnum;
2689 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002690 }
2691 }
2692 }
2693 }
2694
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002695 // safety check (if diff info gets outdated strange things may happen)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002696 towin->w_botfill = FALSE;
2697 if (towin->w_topline > towin->w_buffer->b_ml.ml_line_count)
2698 {
2699 towin->w_topline = towin->w_buffer->b_ml.ml_line_count;
2700 towin->w_botfill = TRUE;
2701 }
2702 if (towin->w_topline < 1)
2703 {
2704 towin->w_topline = 1;
2705 towin->w_topfill = 0;
2706 }
2707
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002708 // When w_topline changes need to recompute w_botline and cursor position
Bram Moolenaar071d4272004-06-13 20:20:40 +00002709 invalidate_botline_win(towin);
2710 changed_line_abv_curs_win(towin);
2711
2712 check_topfill(towin, FALSE);
2713#ifdef FEAT_FOLDING
2714 (void)hasFoldingWin(towin, towin->w_topline, &towin->w_topline,
2715 NULL, TRUE, NULL);
2716#endif
2717}
2718
2719/*
2720 * This is called when 'diffopt' is changed.
2721 */
2722 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01002723diffopt_changed(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +00002724{
2725 char_u *p;
2726 int diff_context_new = 6;
Jonathon7c7a4e62025-01-12 09:58:00 +01002727 int linematch_lines_new = 0;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002728 int diff_flags_new = 0;
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002729 int diff_foldcolumn_new = 2;
Bram Moolenaare828b762018-09-10 17:51:58 +02002730 long diff_algorithm_new = 0;
Bram Moolenaarb6fc7282018-12-04 22:24:16 +01002731 long diff_indent_heuristic = 0;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002732 tabpage_T *tp;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002733
2734 p = p_dip;
2735 while (*p != NUL)
2736 {
Yee Cheng Chin900894b2023-09-29 20:42:32 +02002737 // Note: Keep this in sync with p_dip_values
Bram Moolenaar071d4272004-06-13 20:20:40 +00002738 if (STRNCMP(p, "filler", 6) == 0)
2739 {
2740 p += 6;
2741 diff_flags_new |= DIFF_FILLER;
2742 }
2743 else if (STRNCMP(p, "context:", 8) == 0 && VIM_ISDIGIT(p[8]))
2744 {
2745 p += 8;
2746 diff_context_new = getdigits(&p);
2747 }
Bram Moolenaar785fc652018-09-15 19:17:38 +02002748 else if (STRNCMP(p, "iblank", 6) == 0)
2749 {
2750 p += 6;
2751 diff_flags_new |= DIFF_IBLANK;
2752 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002753 else if (STRNCMP(p, "icase", 5) == 0)
2754 {
2755 p += 5;
2756 diff_flags_new |= DIFF_ICASE;
2757 }
Bram Moolenaar785fc652018-09-15 19:17:38 +02002758 else if (STRNCMP(p, "iwhiteall", 9) == 0)
2759 {
2760 p += 9;
2761 diff_flags_new |= DIFF_IWHITEALL;
2762 }
2763 else if (STRNCMP(p, "iwhiteeol", 9) == 0)
2764 {
2765 p += 9;
2766 diff_flags_new |= DIFF_IWHITEEOL;
2767 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00002768 else if (STRNCMP(p, "iwhite", 6) == 0)
2769 {
2770 p += 6;
2771 diff_flags_new |= DIFF_IWHITE;
2772 }
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002773 else if (STRNCMP(p, "horizontal", 10) == 0)
2774 {
2775 p += 10;
2776 diff_flags_new |= DIFF_HORIZONTAL;
2777 }
2778 else if (STRNCMP(p, "vertical", 8) == 0)
2779 {
2780 p += 8;
2781 diff_flags_new |= DIFF_VERTICAL;
2782 }
2783 else if (STRNCMP(p, "foldcolumn:", 11) == 0 && VIM_ISDIGIT(p[11]))
2784 {
2785 p += 11;
2786 diff_foldcolumn_new = getdigits(&p);
2787 }
Bram Moolenaar97ce4192017-12-01 20:35:58 +01002788 else if (STRNCMP(p, "hiddenoff", 9) == 0)
2789 {
2790 p += 9;
2791 diff_flags_new |= DIFF_HIDDEN_OFF;
2792 }
Bram Moolenaarc8234772019-11-10 21:00:27 +01002793 else if (STRNCMP(p, "closeoff", 8) == 0)
2794 {
2795 p += 8;
2796 diff_flags_new |= DIFF_CLOSE_OFF;
2797 }
Bram Moolenaar4223d432021-02-10 13:18:17 +01002798 else if (STRNCMP(p, "followwrap", 10) == 0)
2799 {
2800 p += 10;
2801 diff_flags_new |= DIFF_FOLLOWWRAP;
2802 }
Bram Moolenaare828b762018-09-10 17:51:58 +02002803 else if (STRNCMP(p, "indent-heuristic", 16) == 0)
2804 {
2805 p += 16;
Bram Moolenaarb6fc7282018-12-04 22:24:16 +01002806 diff_indent_heuristic = XDF_INDENT_HEURISTIC;
Bram Moolenaare828b762018-09-10 17:51:58 +02002807 }
2808 else if (STRNCMP(p, "internal", 8) == 0)
2809 {
2810 p += 8;
2811 diff_flags_new |= DIFF_INTERNAL;
2812 }
2813 else if (STRNCMP(p, "algorithm:", 10) == 0)
2814 {
Yee Cheng Chin900894b2023-09-29 20:42:32 +02002815 // Note: Keep this in sync with p_dip_algorithm_values.
Bram Moolenaare828b762018-09-10 17:51:58 +02002816 p += 10;
2817 if (STRNCMP(p, "myers", 5) == 0)
2818 {
2819 p += 5;
2820 diff_algorithm_new = 0;
2821 }
2822 else if (STRNCMP(p, "minimal", 7) == 0)
2823 {
2824 p += 7;
2825 diff_algorithm_new = XDF_NEED_MINIMAL;
2826 }
2827 else if (STRNCMP(p, "patience", 8) == 0)
2828 {
2829 p += 8;
2830 diff_algorithm_new = XDF_PATIENCE_DIFF;
2831 }
2832 else if (STRNCMP(p, "histogram", 9) == 0)
2833 {
2834 p += 9;
2835 diff_algorithm_new = XDF_HISTOGRAM_DIFF;
2836 }
Bram Moolenaard0721052018-11-05 21:21:33 +01002837 else
2838 return FAIL;
Bram Moolenaare828b762018-09-10 17:51:58 +02002839 }
Yee Cheng Chin9943d472025-03-26 19:41:02 +01002840 else if (STRNCMP(p, "inline:", 7) == 0)
2841 {
2842 // Note: Keep this in sync with p_dip_inline_values.
2843 p += 7;
2844 if (STRNCMP(p, "none", 4) == 0)
2845 {
2846 p += 4;
2847 diff_flags_new &= ~(ALL_INLINE);
2848 diff_flags_new |= DIFF_INLINE_NONE;
2849 }
2850 else if (STRNCMP(p, "simple", 6) == 0)
2851 {
2852 p += 6;
2853 diff_flags_new &= ~(ALL_INLINE);
2854 diff_flags_new |= DIFF_INLINE_SIMPLE;
2855 }
2856 else if (STRNCMP(p, "char", 4) == 0)
2857 {
2858 p += 4;
2859 diff_flags_new &= ~(ALL_INLINE);
2860 diff_flags_new |= DIFF_INLINE_CHAR;
2861 }
2862 else if (STRNCMP(p, "word", 4) == 0)
2863 {
2864 p += 4;
2865 diff_flags_new &= ~(ALL_INLINE);
2866 diff_flags_new |= DIFF_INLINE_WORD;
2867 }
2868 else
2869 return FAIL;
2870 }
zeertzjqccd7f452025-02-03 18:49:49 +01002871 else if (STRNCMP(p, "linematch:", 10) == 0 && VIM_ISDIGIT(p[10]))
Jonathon7c7a4e62025-01-12 09:58:00 +01002872 {
2873 p += 10;
2874 linematch_lines_new = getdigits(&p);
2875 diff_flags_new |= DIFF_LINEMATCH;
2876 }
Bram Moolenaare828b762018-09-10 17:51:58 +02002877
Bram Moolenaar071d4272004-06-13 20:20:40 +00002878 if (*p != ',' && *p != NUL)
2879 return FAIL;
2880 if (*p == ',')
2881 ++p;
2882 }
2883
Bram Moolenaarb6fc7282018-12-04 22:24:16 +01002884 diff_algorithm_new |= diff_indent_heuristic;
2885
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002886 // Can't have both "horizontal" and "vertical".
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002887 if ((diff_flags_new & DIFF_HORIZONTAL) && (diff_flags_new & DIFF_VERTICAL))
2888 return FAIL;
2889
Bram Moolenaar198fa062018-09-18 21:20:26 +02002890 // If flags were added or removed, or the algorithm was changed, need to
2891 // update the diff.
Bram Moolenaare828b762018-09-10 17:51:58 +02002892 if (diff_flags != diff_flags_new || diff_algorithm != diff_algorithm_new)
Bram Moolenaar29323592016-07-24 22:04:11 +02002893 FOR_ALL_TABPAGES(tp)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00002894 tp->tp_diff_invalid = TRUE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002895
2896 diff_flags = diff_flags_new;
Bram Moolenaarb9ddda62019-02-19 23:00:50 +01002897 diff_context = diff_context_new == 0 ? 1 : diff_context_new;
Jonathon7c7a4e62025-01-12 09:58:00 +01002898 linematch_lines = linematch_lines_new;
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002899 diff_foldcolumn = diff_foldcolumn_new;
Bram Moolenaare828b762018-09-10 17:51:58 +02002900 diff_algorithm = diff_algorithm_new;
Bram Moolenaar071d4272004-06-13 20:20:40 +00002901
2902 diff_redraw(TRUE);
2903
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01002904 // recompute the scroll binding with the new option value, may
2905 // remove or add filler lines
Bram Moolenaar071d4272004-06-13 20:20:40 +00002906 check_scrollbind((linenr_T)0, 0L);
2907
2908 return OK;
2909}
2910
2911/*
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002912 * Return TRUE if 'diffopt' contains "horizontal".
2913 */
2914 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01002915diffopt_horizontal(void)
Bram Moolenaarc4675a12006-03-15 22:50:30 +00002916{
2917 return (diff_flags & DIFF_HORIZONTAL) != 0;
2918}
2919
2920/*
Bram Moolenaar97ce4192017-12-01 20:35:58 +01002921 * Return TRUE if 'diffopt' contains "hiddenoff".
2922 */
2923 int
2924diffopt_hiddenoff(void)
2925{
2926 return (diff_flags & DIFF_HIDDEN_OFF) != 0;
2927}
2928
2929/*
Bram Moolenaarc8234772019-11-10 21:00:27 +01002930 * Return TRUE if 'diffopt' contains "closeoff".
2931 */
2932 int
2933diffopt_closeoff(void)
2934{
2935 return (diff_flags & DIFF_CLOSE_OFF) != 0;
2936}
2937
2938/*
Yee Cheng Chin9943d472025-03-26 19:41:02 +01002939 * Called when a line has been updated. Used for updating inline diff in Insert
2940 * mode without waiting for global diff update later.
2941 */
2942 void
2943diff_update_line(linenr_T lnum)
2944{
2945 int idx;
2946 diff_T *dp;
2947
2948 if (!(diff_flags & ALL_INLINE_DIFF))
2949 // We only care if we are doing inline-diff where we cache the diff results
2950 return;
2951
2952 idx = diff_buf_idx(curbuf);
2953 if (idx == DB_COUNT)
2954 return;
2955 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
2956 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
2957 break;
2958
2959 // clear the inline change cache as it's invalid
2960 if (dp != NULL)
2961 {
2962 dp->has_changes = FALSE;
2963 dp->df_changes.ga_len = 0;
2964 }
2965}
2966
2967static diffline_change_T simple_diffline_change; // used for simple inline diff algorithm
2968
2969/*
2970 * Parse a diffline struct and returns the [start,end] byte offsets
2971 *
2972 * Returns TRUE if this change was added, no other buffer has it.
Bram Moolenaar071d4272004-06-13 20:20:40 +00002973 */
2974 int
Yee Cheng Chin9943d472025-03-26 19:41:02 +01002975diff_change_parse(
2976 diffline_T *diffline,
2977 diffline_change_T *change,
2978 int *change_start,
2979 int *change_end)
2980{
2981 if (change->dc_start_lnum_off[diffline->bufidx] < diffline->lineoff)
2982 *change_start = 0;
2983 else
2984 *change_start = change->dc_start[diffline->bufidx];
2985 if (change->dc_end_lnum_off[diffline->bufidx] > diffline->lineoff)
2986 *change_end = INT_MAX;
2987 else
2988 *change_end = change->dc_end[diffline->bufidx];
2989
2990 if (change == &simple_diffline_change)
2991 {
2992 // This is what we returned from simple inline diff. We always consider
2993 // the range to be changed, rather than added for now.
2994 return FALSE;
2995 }
2996
2997 // Find out whether this is an addition. Note that for multi buffer diff,
2998 // to tell whether lines are additions we check whether all the other diff
2999 // lines are identical (in diff_check_with_linestatus). If so, we mark them
3000 // as add. We don't do that for inline diff here for simplicity.
3001 for (int i = 0; i < DB_COUNT; i++)
3002 {
3003 if (i == diffline->bufidx)
3004 continue;
3005 if (change->dc_start[i] != change->dc_end[i]
3006 || change->dc_end_lnum_off[i] != change->dc_start_lnum_off[i])
3007 {
3008 return FALSE;
3009 }
3010 }
3011 return TRUE;
3012}
3013
3014/*
3015 * Find the difference within a changed line and returns [startp,endp] byte
3016 * positions. Performs a simple algorithm by finding a single range in the
3017 * middle.
3018 *
3019 * If diffopt has DIFF_INLINE_NONE set, then this will only calculate the return
3020 * value (added or changed), but startp/endp will not be calculated.
3021 *
3022 * Returns TRUE if the line was added, no other buffer has it.
3023 */
3024 static int
3025diff_find_change_simple(
Bram Moolenaar7454a062016-01-30 15:14:10 +01003026 win_T *wp,
3027 linenr_T lnum,
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003028 diff_T *dp,
3029 int idx,
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003030 int *startp, // first char of the change
3031 int *endp) // last char of the change
Bram Moolenaar071d4272004-06-13 20:20:40 +00003032{
3033 char_u *line_org;
3034 char_u *line_new;
3035 int i;
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003036 int si_org, si_new;
3037 int ei_org, ei_new;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003038 int off;
3039 int added = TRUE;
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003040 char_u *p1, *p2;
3041 int l;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003042
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003043 if (diff_flags & DIFF_INLINE_NONE)
Bram Moolenaarfa3491a2007-02-20 02:49:19 +00003044 {
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003045 // We only care about the return value, not the actual string comparisons.
3046 line_org = NULL;
Bram Moolenaarfa3491a2007-02-20 02:49:19 +00003047 }
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003048 else
Jonathon7c7a4e62025-01-12 09:58:00 +01003049 {
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003050 // Make a copy of the line, the next ml_get() will invalidate it.
3051 line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, FALSE));
3052 if (line_org == NULL)
3053 return FALSE;
Bram Moolenaarfa3491a2007-02-20 02:49:19 +00003054 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003055
3056 off = lnum - dp->df_lnum[idx];
3057
3058 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003059 if (curtab->tp_diffbuf[i] != NULL && i != idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003060 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003061 // Skip lines that are not in the other change (filler lines).
Bram Moolenaar071d4272004-06-13 20:20:40 +00003062 if (off >= dp->df_count[i])
3063 continue;
3064 added = FALSE;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003065 if (diff_flags & DIFF_INLINE_NONE)
3066 break; // early terminate as we only care about the return value
3067
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003068 line_new = ml_get_buf(curtab->tp_diffbuf[i],
3069 dp->df_lnum[i] + off, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003070
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003071 // Search for start of difference
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003072 si_org = si_new = 0;
3073 while (line_org[si_org] != NUL)
3074 {
Bram Moolenaar785fc652018-09-15 19:17:38 +02003075 if (((diff_flags & DIFF_IWHITE)
3076 && VIM_ISWHITE(line_org[si_org])
3077 && VIM_ISWHITE(line_new[si_new]))
3078 || ((diff_flags & DIFF_IWHITEALL)
3079 && (VIM_ISWHITE(line_org[si_org])
3080 || VIM_ISWHITE(line_new[si_new]))))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003081 {
Bram Moolenaara93fa7e2006-04-17 22:14:47 +00003082 si_org = (int)(skipwhite(line_org + si_org) - line_org);
3083 si_new = (int)(skipwhite(line_new + si_new) - line_new);
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003084 }
3085 else
3086 {
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003087 if (!diff_equal_char(line_org + si_org, line_new + si_new,
3088 &l))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003089 break;
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003090 si_org += l;
3091 si_new += l;
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003092 }
3093 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003094 if (has_mbyte)
3095 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003096 // Move back to first byte of character in both lines (may
3097 // have "nn^" in line_org and "n^ in line_new).
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003098 si_org -= (*mb_head_off)(line_org, line_org + si_org);
3099 si_new -= (*mb_head_off)(line_new, line_new + si_new);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003100 }
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003101 if (*startp > si_org)
3102 *startp = si_org;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003103
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003104 // Search for end of difference, if any.
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003105 if (line_org[si_org] != NUL || line_new[si_new] != NUL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003106 {
3107 ei_org = (int)STRLEN(line_org);
3108 ei_new = (int)STRLEN(line_new);
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003109 while (ei_org >= *startp && ei_new >= si_new
3110 && ei_org >= 0 && ei_new >= 0)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003111 {
Bram Moolenaar785fc652018-09-15 19:17:38 +02003112 if (((diff_flags & DIFF_IWHITE)
3113 && VIM_ISWHITE(line_org[ei_org])
3114 && VIM_ISWHITE(line_new[ei_new]))
3115 || ((diff_flags & DIFF_IWHITEALL)
3116 && (VIM_ISWHITE(line_org[ei_org])
3117 || VIM_ISWHITE(line_new[ei_new]))))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003118 {
3119 while (ei_org >= *startp
Bram Moolenaar1c465442017-03-12 20:10:05 +01003120 && VIM_ISWHITE(line_org[ei_org]))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003121 --ei_org;
3122 while (ei_new >= si_new
Bram Moolenaar1c465442017-03-12 20:10:05 +01003123 && VIM_ISWHITE(line_new[ei_new]))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003124 --ei_new;
3125 }
3126 else
3127 {
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003128 p1 = line_org + ei_org;
3129 p2 = line_new + ei_new;
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003130 p1 -= (*mb_head_off)(line_org, p1);
3131 p2 -= (*mb_head_off)(line_new, p2);
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003132 if (!diff_equal_char(p1, p2, &l))
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003133 break;
Bram Moolenaarda22b8c2017-09-02 18:01:50 +02003134 ei_org -= l;
3135 ei_new -= l;
Bram Moolenaar9e54a0e2006-04-14 20:42:25 +00003136 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003137 }
3138 if (*endp < ei_org)
3139 *endp = ei_org;
3140 }
3141 }
3142
3143 vim_free(line_org);
3144 return added;
3145}
3146
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003147/*
3148 * Mapping used for mapping from temporary mmfile created for inline diff back
3149 * to original buffer's line/col.
3150 */
3151typedef struct
3152{
3153 long byte_start;
3154 long num_bytes;
3155 int lineoff;
3156} linemap_entry_T;
3157
3158/*
3159 * Refine inline character-wise diff blocks to create a more human readable
3160 * highlight. Otherwise a naive diff under existing algorithms tends to create
3161 * a messy output with lots of small gaps.
3162 * It does this by merging adjacent long diff blocks if they are only separated
3163 * by a couple characters.
3164 * These are done by heuristics and can be further tuned.
3165 */
3166 static void
3167diff_refine_inline_char_highlight(diff_T *dp_orig, garray_T *linemap, int idx1)
3168{
3169 // Perform multiple passes so that newly merged blocks will now be long
3170 // enough which may cause other previously unmerged gaps to be merged as
3171 // well.
3172 int pass = 1;
3173 do
3174 {
3175 int has_unmerged_gaps = FALSE;
3176 int has_merged_gaps = FALSE;
3177 diff_T *dp = dp_orig;
3178 while (dp!= NULL && dp->df_next != NULL)
3179 {
3180 // Only use first buffer to calculate the gap because the gap is
3181 // unchanged text, which would be the same in all buffers.
3182 if (dp->df_lnum[idx1] + dp->df_count[idx1] - 1 >= linemap[idx1].ga_len
3183 || dp->df_next->df_lnum[idx1] - 1 >= linemap[idx1].ga_len)
3184 {
3185 dp = dp->df_next;
3186 continue;
3187 }
3188
3189 // If the gap occurs over different lines, don't consider it
3190 linemap_entry_T *entry1 = &((linemap_entry_T*)linemap[idx1].ga_data)[dp->df_lnum[idx1] + dp->df_count[idx1] - 1];
3191 linemap_entry_T *entry2 = &((linemap_entry_T*)linemap[idx1].ga_data)[dp->df_next->df_lnum[idx1] - 1];
3192 if (entry1->lineoff != entry2->lineoff)
3193 {
3194 dp = dp->df_next;
3195 continue;
3196 }
3197
3198 linenr_T gap = dp->df_next->df_lnum[idx1] - (dp->df_lnum[idx1] + dp->df_count[idx1]);
3199 if (gap <= 3)
3200 {
3201 linenr_T max_df_count = 0;
3202 for (int i = 0; i < DB_COUNT; i++)
3203 max_df_count = MAX(max_df_count, dp->df_count[i] + dp->df_next->df_count[i]);
3204
3205 if (max_df_count >= gap * 4)
3206 {
3207 // Merge current block with the next one. Don't advance the
3208 // pointer so we try the same merged block against the next
3209 // one.
3210 for (int i = 0; i < DB_COUNT; i++)
3211 {
3212 dp->df_count[i] = dp->df_next->df_lnum[i]
3213 + dp->df_next->df_count[i] - dp->df_lnum[i];
3214 }
3215 diff_T *dp_next = dp->df_next;
3216 dp->df_next = dp_next->df_next;
3217 clear_diffblock(dp_next);
3218 has_merged_gaps = TRUE;
3219 continue;
3220 }
3221 else
3222 has_unmerged_gaps = TRUE;
3223 }
3224 dp = dp->df_next;
3225 }
3226 if (!has_unmerged_gaps || !has_merged_gaps)
3227 break;
3228 } while (pass++ < 4); // use limited number of passes to avoid excessive looping
3229}
3230
3231/*
3232 * Find the inline difference within a diff block among differnt buffers. Do
3233 * this by splitting each block's content into characters or words, and then
3234 * use internal xdiff to calculate the per-character/word diff. The result is
3235 * stored in dp instead of returned by the function.
3236 */
3237 static void
3238diff_find_change_inline_diff(
3239 diff_T *dp)
3240{
3241 diffio_T dio;
3242 garray_T linemap[DB_COUNT];
3243 garray_T file1_str;
3244 garray_T file2_str;
3245 int file1_idx = -1;
3246
3247 long save_diff_algorithm = diff_algorithm;
3248
3249 CLEAR_FIELD(dio);
3250 ga_init2(&dio.dio_diff.dout_ga, sizeof(char *), 1000);
3251
3252 // inline diff only supports internal algo
3253 dio.dio_internal = TRUE;
3254
3255 // always use indent-heuristics to slide diff splits along
3256 // whitespace
3257 diff_algorithm |= XDF_INDENT_HEURISTIC;
3258
3259 // diff_read() has an implicit dependency on curtab->tp_first_diff
3260 diff_T *orig_diff = curtab->tp_first_diff;
3261 curtab->tp_first_diff = NULL;
3262
3263 // Buffers to populate mmfile 1/2 that would be passed to xdiff as memory
3264 // files. Use a grow array as it is not obvious how much exact space we
3265 // need.
3266 ga_init2(&file1_str, 1, 1024);
3267 ga_init2(&file2_str, 1, 1024);
3268
3269 // Line map to map from generated mmfiles' line numbers back to original
3270 // diff blocks' locations. Need this even for char diff because not all
3271 // characters are 1-byte long / ASCII.
3272 for (int i = 0; i < DB_COUNT; i++)
3273 ga_init2(&linemap[i], sizeof(linemap_entry_T), 128);
3274
3275 for (int i = 0; i < DB_COUNT; i++)
3276 {
3277 dio.dio_diff.dout_ga.ga_len = 0;
3278
3279 buf_T *buf = curtab->tp_diffbuf[i];
3280 if (buf == NULL || buf->b_ml.ml_mfp == NULL)
3281 continue; // skip buffer that isn't loaded
3282
3283 if (dp->df_count[i] == 0)
3284 continue; // skip buffer that don't have any texts in this block
3285
3286 if (file1_idx == -1)
3287 file1_idx = i;
3288
3289 garray_T *curstr = (file1_idx != i) ? &file2_str : &file1_str;
3290
3291 linenr_T numlines = 0;
3292 curstr->ga_len = 0;
3293
3294 // Split each line into chars/words and populate fake file buffer as
3295 // newline-delimited tokens as that's what xdiff requires.
3296 for (int off = 0; off < dp->df_count[i]; off++)
3297 {
3298 char_u *curline = ml_get_buf(curtab->tp_diffbuf[i],
3299 dp->df_lnum[i] + off, FALSE);
3300
3301 int in_keyword = FALSE;
3302
3303 // iwhiteeol support vars
3304 int last_white = FALSE;
3305 int eol_ga_len = -1;
3306 int eol_linemap_len = -1;
3307 int eol_numlines = -1;
3308
3309 char_u *s;
3310 for (s = curline; *s != NUL;)
3311 {
3312 // Always use the first buffer's 'iskeyword' to have a consistent diff
3313 int new_in_keyword = FALSE;
3314 if (diff_flags & DIFF_INLINE_WORD)
3315 new_in_keyword = vim_iswordp_buf(s, curtab->tp_diffbuf[file1_idx]);
3316 if (in_keyword && !new_in_keyword)
3317 {
3318 ga_append(curstr, NL);
3319 numlines++;
3320 }
3321
3322 if (VIM_ISWHITE(*s))
3323 {
3324 if (diff_flags & DIFF_IWHITEALL)
3325 {
3326 in_keyword = FALSE;
3327 s = skipwhite(s);
3328 continue;
3329 }
3330 else if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3331 {
3332 if (!last_white)
3333 {
3334 eol_ga_len = curstr->ga_len;
3335 eol_linemap_len = linemap[i].ga_len;
3336 eol_numlines = numlines;
3337 last_white = TRUE;
3338 }
3339 }
3340 }
3341 else
3342 {
3343 if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3344 {
3345 last_white = FALSE;
3346 eol_ga_len = -1;
3347 eol_linemap_len = -1;
3348 eol_numlines = -1;
3349 }
3350 }
3351
3352 int char_len = 1;
3353 if (*s == NL)
3354 // NL is internal substitute for NUL
3355 ga_append(curstr, NUL);
3356 else
3357 {
3358 char_len = mb_ptr2len(s);
3359
3360 if (VIM_ISWHITE(*s) && (diff_flags & DIFF_IWHITE))
3361 // Treat the entire white space span as a single char.
3362 char_len = skipwhite(s) - s;
3363
3364 if (diff_flags & DIFF_ICASE)
3365 {
3366 int c;
3367 char_u cbuf[MB_MAXBYTES + 1];
3368 // xdiff doesn't support ignoring case, fold-case the text manually.
3369 c = PTR2CHAR(s);
3370 int c_len = MB_CHAR2LEN(c);
3371 c = MB_CASEFOLD(c);
3372 int c_fold_len = mb_char2bytes(c, cbuf);
3373 ga_concat_len(curstr, cbuf, c_fold_len);
3374 if (char_len > c_len)
3375 {
3376 // There may be remaining composing characters. Write those back in.
3377 // Composing characters don't need case folding.
3378 ga_concat_len(curstr, s + c_len, char_len - c_len);
3379 }
3380 }
3381 else
3382 ga_concat_len(curstr, s, char_len);
3383 }
3384
3385 if (!new_in_keyword)
3386 {
3387 ga_append(curstr, NL);
3388 numlines++;
3389 }
3390
3391 if (!new_in_keyword || (new_in_keyword && !in_keyword))
3392 {
3393 // create a new mapping entry from the xdiff mmfile back to
3394 // original line/col.
3395 linemap_entry_T linemap_entry;
3396 linemap_entry.lineoff = off;
3397 linemap_entry.byte_start = s - curline;
3398 linemap_entry.num_bytes = char_len;
3399 if (ga_grow(&linemap[i], 1) != OK)
3400 goto done;
3401 ((linemap_entry_T*)(linemap[i].ga_data))[linemap[i].ga_len]
3402 = linemap_entry;
3403 linemap[i].ga_len += 1;
3404 }
3405 else
3406 {
3407 // Still inside a keyword. Just increment byte count but
3408 // don't make a new entry.
3409 // linemap always has at least one entry here
3410 ((linemap_entry_T*)linemap[i].ga_data)[linemap[i].ga_len-1].num_bytes
3411 += char_len;
3412 }
3413
3414 in_keyword = new_in_keyword;
3415 s += char_len;
3416 }
3417 if (in_keyword)
3418 {
3419 ga_append(curstr, NL);
3420 numlines++;
3421 }
3422
3423 if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3424 {
3425 // Need to trim trailing whitespace. Do this simply by
3426 // resetting arrays back to before we encountered them.
3427 if (eol_ga_len != -1)
3428 {
3429 curstr->ga_len = eol_ga_len;
3430 linemap[i].ga_len = eol_linemap_len;
3431 numlines = eol_numlines;
3432 }
3433 }
3434
3435 if (!(diff_flags & DIFF_IWHITEALL))
3436 {
3437 // Add an empty line token mapped to the end-of-line in the
3438 // original file. This helps diff newline differences among
3439 // files, which will be visualized when using 'list' as the eol
3440 // listchar will be highlighted.
3441 ga_append(curstr, NL);
3442 numlines++;
3443
3444 linemap_entry_T linemap_entry;
3445 linemap_entry.lineoff = off;
3446 linemap_entry.byte_start = s - curline;
3447 linemap_entry.num_bytes = sizeof(NL);
3448 if (ga_grow(&linemap[i], 1) != OK)
3449 goto done;
3450 ((linemap_entry_T*)(linemap[i].ga_data))[linemap[i].ga_len]
3451 = linemap_entry;
3452 linemap[i].ga_len += 1;
3453 }
3454 }
3455
3456 if (file1_idx != i)
3457 {
3458 dio.dio_new.din_mmfile.ptr = (char *)curstr->ga_data;
3459 dio.dio_new.din_mmfile.size = curstr->ga_len;
3460 }
3461 else
3462 {
3463 dio.dio_orig.din_mmfile.ptr = (char *)curstr->ga_data;
3464 dio.dio_orig.din_mmfile.size = curstr->ga_len;
3465 }
3466 if (file1_idx != i)
3467 {
3468 // Perform diff with first file and read the results
3469 int diff_status = diff_file_internal(&dio);
3470 if (diff_status == FAIL)
3471 goto done;
3472
3473 diff_read(0, i, &dio);
3474 clear_diffout(&dio.dio_diff);
3475 }
3476 }
3477 diff_T *new_diff = curtab->tp_first_diff;
3478
3479 if (diff_flags & DIFF_INLINE_CHAR && file1_idx != -1)
3480 diff_refine_inline_char_highlight(new_diff, linemap, file1_idx);
3481
3482 // After the diff, use the linemap to obtain the original line/col of the
3483 // changes and cache them in dp.
3484 dp->df_changes.ga_len = 0; // this should already be zero
3485 for (; new_diff != NULL; new_diff = new_diff->df_next)
3486 {
3487 diffline_change_T change;
3488 CLEAR_FIELD(change);
3489 for (int i = 0; i < DB_COUNT; i++)
3490 {
3491 if (new_diff->df_lnum[i] == 0)
3492 continue;
3493 linenr_T diff_lnum = new_diff->df_lnum[i] - 1; // use zero-index
3494 linenr_T diff_lnum_end = diff_lnum + new_diff->df_count[i];
3495
3496 if (diff_lnum >= linemap[i].ga_len)
3497 {
3498 change.dc_start[i] = MAXCOL;
3499 change.dc_start_lnum_off[i] = INT_MAX;
3500 }
3501 else
3502 {
3503 change.dc_start[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum].byte_start;
3504 change.dc_start_lnum_off[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum].lineoff;
3505 }
3506
3507 if (diff_lnum == diff_lnum_end)
3508 {
3509 change.dc_end[i] = change.dc_start[i];
3510 change.dc_end_lnum_off[i] = change.dc_start_lnum_off[i];
3511 }
3512 else if (diff_lnum_end - 1 >= linemap[i].ga_len)
3513 {
3514 change.dc_end[i] = MAXCOL;
3515 change.dc_end_lnum_off[i] = INT_MAX;
3516 }
3517 else
3518 {
3519 change.dc_end[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].byte_start +
3520 ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].num_bytes;
3521 change.dc_end_lnum_off[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].lineoff;
3522 }
3523 }
3524 if (ga_grow(&dp->df_changes, 1) != OK)
3525 {
3526 dp->df_changes.ga_len = 0;
3527 goto done;
3528 }
3529 ((diffline_change_T*)(dp->df_changes.ga_data))[dp->df_changes.ga_len] = change;
3530 dp->df_changes.ga_len += 1;
3531 }
3532
3533done:
3534 diff_algorithm = save_diff_algorithm;
3535
3536 dp->has_changes = TRUE;
3537
3538 diff_clear(curtab);
3539 curtab->tp_first_diff = orig_diff;
3540
3541 ga_clear(&file1_str);
3542 ga_clear(&file2_str);
3543 // No need to clear dio.dio_orig/dio_new because they were referencing
3544 // strings that are now cleared.
3545 clear_diffout(&dio.dio_diff);
3546 for (int i = 0; i < DB_COUNT; i++)
3547 ga_clear(&linemap[i]);
3548}
3549
3550/*
3551 * Find the difference within a changed line.
3552 * Returns TRUE if the line was added, no other buffer has it.
3553 */
3554 int
3555diff_find_change(
3556 win_T *wp,
3557 linenr_T lnum,
3558 diffline_T *diffline)
3559{
3560 diff_T *dp;
3561 int idx;
3562 int off;
3563
3564 idx = diff_buf_idx(wp->w_buffer);
3565 if (idx == DB_COUNT) // cannot happen
3566 return FALSE;
3567
3568 // search for a change that includes "lnum" in the list of diffblocks.
3569 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
3570 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
3571 break;
3572 if (dp->is_linematched)
3573 {
3574 while (dp && dp->df_next
3575 && lnum == dp->df_count[idx] + dp->df_lnum[idx]
3576 && dp->df_next->df_lnum[idx] == lnum)
3577 dp = dp->df_next;
3578 }
3579 if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL)
3580 return FALSE;
3581
3582 if (lnum - dp->df_lnum[idx] > INT_MAX)
3583 // Integer overflow protection
3584 return FALSE;
3585 off = lnum - dp->df_lnum[idx];
3586
3587 if (!(diff_flags & ALL_INLINE_DIFF) || diff_internal_failed())
3588 {
3589 // Use simple algorithm
3590 int change_start = MAXCOL; // first col of changed area
3591 int change_end = -1; // last col of changed area
3592 int ret;
3593
3594 ret = diff_find_change_simple(wp, lnum, dp, idx, &change_start, &change_end);
3595
3596 // convert from inclusive end to exclusive end per diffline's contract
3597 change_end += 1;
3598
3599 // Create a mock diffline struct. We always only have one so no need to
3600 // allocate memory.
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003601 CLEAR_FIELD(simple_diffline_change);
3602 diffline->changes = &simple_diffline_change;
3603 diffline->num_changes = 1;
3604 diffline->bufidx = idx;
3605 diffline->lineoff = lnum - dp->df_lnum[idx];
3606
3607 simple_diffline_change.dc_start[idx] = change_start;
3608 simple_diffline_change.dc_end[idx] = change_end;
3609 simple_diffline_change.dc_start_lnum_off[idx] = off;
3610 simple_diffline_change.dc_end_lnum_off[idx] = off;
3611 return ret;
3612 }
3613
3614 // Use inline diff algorithm.
3615 // The diff changes are usually cached so we check that first.
3616 if (!dp->has_changes)
3617 diff_find_change_inline_diff(dp);
3618
3619 garray_T *changes = &dp->df_changes;
3620
3621 // Use linear search to find the first change for this line. We could
3622 // optimize this to use binary search, but there should usually be a
3623 // limited number of inline changes per diff block, and limited number of
3624 // diff blocks shown on screen, so it is not necessary.
3625 int num_changes = 0;
3626 int change_idx = 0;
3627 diffline->changes = NULL;
3628 for (change_idx = 0; change_idx < changes->ga_len; change_idx++)
3629 {
3630 diffline_change_T *change = &((diffline_change_T*)dp->df_changes.ga_data)[change_idx];
3631 if (change->dc_end_lnum_off[idx] < off)
3632 continue;
3633 if (change->dc_start_lnum_off[idx] > off)
3634 break;
3635 if (diffline->changes == NULL)
3636 diffline->changes = change;
3637 num_changes++;
3638 }
3639 diffline->num_changes = num_changes;
3640 diffline->bufidx = idx;
3641 diffline->lineoff = off;
3642
3643 // Detect simple cases of added lines in the end within a diff block. This
3644 // has to be the last change of this diff block, and all other buffers are
3645 // considering this to be an addition past their last line. Other scenarios
3646 // will be considered a changed line instead.
3647 int added = FALSE;
3648 if (num_changes == 1 && change_idx == dp->df_changes.ga_len)
3649 {
3650 added = TRUE;
3651 for (int i = 0; i < DB_COUNT; i++)
3652 {
3653 if (idx == i)
3654 continue;
3655 if (curtab->tp_diffbuf[i] == NULL)
3656 continue;
3657 diffline_change_T *change = &((diffline_change_T*)dp->df_changes.ga_data)[dp->df_changes.ga_len-1];
3658 if (change->dc_start_lnum_off[i] != INT_MAX)
3659 {
3660 added = FALSE;
3661 break;
3662 }
3663 }
3664 }
3665 return added;
3666}
3667
Bram Moolenaar071d4272004-06-13 20:20:40 +00003668#if defined(FEAT_FOLDING) || defined(PROTO)
3669/*
3670 * Return TRUE if line "lnum" is not close to a diff block, this line should
3671 * be in a fold.
3672 * Return FALSE if there are no diff blocks at all in this window.
3673 */
3674 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01003675diff_infold(win_T *wp, linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003676{
3677 int i;
3678 int idx = -1;
3679 int other = FALSE;
3680 diff_T *dp;
3681
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003682 // Return if 'diff' isn't set.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003683 if (!wp->w_p_diff)
3684 return FALSE;
3685
3686 for (i = 0; i < DB_COUNT; ++i)
3687 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003688 if (curtab->tp_diffbuf[i] == wp->w_buffer)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003689 idx = i;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003690 else if (curtab->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003691 other = TRUE;
3692 }
3693
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003694 // return here if there are no diffs in the window
Bram Moolenaar071d4272004-06-13 20:20:40 +00003695 if (idx == -1 || !other)
3696 return FALSE;
3697
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003698 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003699 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00003700
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003701 // Return if there are no diff blocks. All lines will be folded.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003702 if (curtab->tp_first_diff == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003703 return TRUE;
3704
Bram Moolenaaraeea7212020-04-02 18:50:46 +02003705 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003706 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003707 // If this change is below the line there can't be any further match.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003708 if (dp->df_lnum[idx] - diff_context > lnum)
3709 break;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003710 // If this change ends before the line we have a match.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003711 if (dp->df_lnum[idx] + dp->df_count[idx] + diff_context > lnum)
3712 return FALSE;
3713 }
3714 return TRUE;
3715}
3716#endif
3717
3718/*
3719 * "dp" and "do" commands.
3720 */
3721 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01003722nv_diffgetput(int put, long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003723{
3724 exarg_T ea;
Bram Moolenaar6a643652014-10-31 13:54:25 +01003725 char_u buf[30];
Bram Moolenaar071d4272004-06-13 20:20:40 +00003726
Bram Moolenaarf2732452018-06-03 14:47:35 +02003727#ifdef FEAT_JOB_CHANNEL
3728 if (bt_prompt(curbuf))
3729 {
3730 vim_beep(BO_OPER);
3731 return;
3732 }
3733#endif
Bram Moolenaar6a643652014-10-31 13:54:25 +01003734 if (count == 0)
3735 ea.arg = (char_u *)"";
3736 else
3737 {
3738 vim_snprintf((char *)buf, 30, "%ld", count);
3739 ea.arg = buf;
3740 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003741 if (put)
3742 ea.cmdidx = CMD_diffput;
3743 else
3744 ea.cmdidx = CMD_diffget;
3745 ea.addr_count = 0;
3746 ea.line1 = curwin->w_cursor.lnum;
3747 ea.line2 = curwin->w_cursor.lnum;
3748 ex_diffgetput(&ea);
3749}
3750
3751/*
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01003752 * Return TRUE if "diff" appears in the list of diff blocks of the current tab.
3753 */
3754 static int
3755valid_diff(diff_T *diff)
3756{
3757 diff_T *dp;
3758
Yegappan Lakshmanan14113fd2023-03-07 17:13:51 +00003759 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01003760 if (dp == diff)
3761 return TRUE;
3762 return FALSE;
3763}
3764
3765/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00003766 * ":diffget"
3767 * ":diffput"
3768 */
3769 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01003770ex_diffgetput(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003771{
3772 linenr_T lnum;
3773 int count;
3774 linenr_T off = 0;
3775 diff_T *dp;
3776 diff_T *dprev;
3777 diff_T *dfree;
3778 int idx_cur;
3779 int idx_other;
3780 int idx_from;
3781 int idx_to;
3782 int i;
3783 int added;
3784 char_u *p;
3785 aco_save_T aco;
3786 buf_T *buf;
3787 int start_skip, end_skip;
3788 int new_count;
Bram Moolenaar280f1262006-01-30 00:14:18 +00003789 int buf_empty;
Bram Moolenaar602eb742007-02-20 03:43:38 +00003790 int found_not_ma = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003791
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003792 // Find the current buffer in the list of diff buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003793 idx_cur = diff_buf_idx(curbuf);
3794 if (idx_cur == DB_COUNT)
3795 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003796 emsg(_(e_current_buffer_is_not_in_diff_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003797 return;
3798 }
3799
3800 if (*eap->arg == NUL)
3801 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003802 // No argument: Find the other buffer in the list of diff buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003803 for (idx_other = 0; idx_other < DB_COUNT; ++idx_other)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003804 if (curtab->tp_diffbuf[idx_other] != curbuf
Bram Moolenaar602eb742007-02-20 03:43:38 +00003805 && curtab->tp_diffbuf[idx_other] != NULL)
3806 {
3807 if (eap->cmdidx != CMD_diffput
3808 || curtab->tp_diffbuf[idx_other]->b_p_ma)
3809 break;
3810 found_not_ma = TRUE;
3811 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003812 if (idx_other == DB_COUNT)
3813 {
Bram Moolenaar602eb742007-02-20 03:43:38 +00003814 if (found_not_ma)
Bram Moolenaar677658a2022-01-05 16:09:06 +00003815 emsg(_(e_no_other_buffer_in_diff_mode_is_modifiable));
Bram Moolenaar602eb742007-02-20 03:43:38 +00003816 else
Bram Moolenaare1242042021-12-16 20:56:57 +00003817 emsg(_(e_no_other_buffer_in_diff_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003818 return;
3819 }
3820
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003821 // Check that there isn't a third buffer in the list
Bram Moolenaar071d4272004-06-13 20:20:40 +00003822 for (i = idx_other + 1; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003823 if (curtab->tp_diffbuf[i] != curbuf
3824 && curtab->tp_diffbuf[i] != NULL
3825 && (eap->cmdidx != CMD_diffput || curtab->tp_diffbuf[i]->b_p_ma))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003826 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003827 emsg(_(e_more_than_two_buffers_in_diff_mode_dont_know_which_one_to_use));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003828 return;
3829 }
3830 }
3831 else
3832 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003833 // Buffer number or pattern given. Ignore trailing white space.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003834 p = eap->arg + STRLEN(eap->arg);
Bram Moolenaar1c465442017-03-12 20:10:05 +01003835 while (p > eap->arg && VIM_ISWHITE(p[-1]))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003836 --p;
3837 for (i = 0; vim_isdigit(eap->arg[i]) && eap->arg + i < p; ++i)
3838 ;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003839 if (eap->arg + i == p) // digits only
Bram Moolenaar071d4272004-06-13 20:20:40 +00003840 i = atol((char *)eap->arg);
3841 else
3842 {
Bram Moolenaar0c279bb2013-03-19 14:25:54 +01003843 i = buflist_findpat(eap->arg, p, FALSE, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003844 if (i < 0)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003845 return; // error message already given
Bram Moolenaar071d4272004-06-13 20:20:40 +00003846 }
3847 buf = buflist_findnr(i);
3848 if (buf == NULL)
3849 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003850 semsg(_(e_cant_find_buffer_str), eap->arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003851 return;
3852 }
Bram Moolenaar5cc6a6e2009-01-22 19:48:55 +00003853 if (buf == curbuf)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003854 return; // nothing to do
Bram Moolenaar071d4272004-06-13 20:20:40 +00003855 idx_other = diff_buf_idx(buf);
3856 if (idx_other == DB_COUNT)
3857 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003858 semsg(_(e_buffer_str_is_not_in_diff_mode), eap->arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003859 return;
3860 }
3861 }
3862
3863 diff_busy = TRUE;
3864
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003865 // When no range given include the line above or below the cursor.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003866 if (eap->addr_count == 0)
3867 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003868 // Make it possible that ":diffget" on the last line gets line below
3869 // the cursor line when there is no difference above the cursor.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003870 if (eap->cmdidx == CMD_diffget
3871 && eap->line1 == curbuf->b_ml.ml_line_count
3872 && diff_check(curwin, eap->line1) == 0
3873 && (eap->line1 == 1 || diff_check(curwin, eap->line1 - 1) == 0))
3874 ++eap->line2;
3875 else if (eap->line1 > 0)
3876 --eap->line1;
3877 }
3878
3879 if (eap->cmdidx == CMD_diffget)
3880 {
3881 idx_from = idx_other;
3882 idx_to = idx_cur;
3883 }
3884 else
3885 {
3886 idx_from = idx_cur;
3887 idx_to = idx_other;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003888 // Need to make the other buffer the current buffer to be able to make
3889 // changes in it.
Bram Moolenaare76062c2022-11-28 18:51:43 +00003890 // Set curwin/curbuf to buf and save a few things.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003891 aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]);
Bram Moolenaare76062c2022-11-28 18:51:43 +00003892 if (curbuf != curtab->tp_diffbuf[idx_other])
3893 // Could not find a window for this buffer, the rest is likely to
3894 // fail.
3895 goto theend;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003896 }
3897
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003898 // May give the warning for a changed buffer here, which can trigger the
3899 // FileChangedRO autocommand, which may do nasty things and mess
3900 // everything up.
Bram Moolenaar910f66f2006-04-05 20:41:53 +00003901 if (!curbuf->b_changed)
3902 {
3903 change_warning(0);
3904 if (diff_buf_idx(curbuf) != idx_to)
3905 {
Bram Moolenaar677658a2022-01-05 16:09:06 +00003906 emsg(_(e_buffer_changed_unexpectedly));
Bram Moolenaard2b58c02018-09-16 18:10:48 +02003907 goto theend;
Bram Moolenaar910f66f2006-04-05 20:41:53 +00003908 }
3909 }
3910
Bram Moolenaar071d4272004-06-13 20:20:40 +00003911 dprev = NULL;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003912 for (dp = curtab->tp_first_diff; dp != NULL; )
Bram Moolenaar071d4272004-06-13 20:20:40 +00003913 {
Jonathon7c7a4e62025-01-12 09:58:00 +01003914 if (!eap->addr_count)
3915 {
3916 // handle the case with adjacent diff blocks
3917 while (dp->is_linematched
3918 && dp->df_next
3919 && dp->df_next->df_lnum[idx_cur] == dp->df_lnum[idx_cur] +
3920 dp->df_count[idx_cur]
3921 && dp->df_next->df_lnum[idx_cur] == eap->line1 + off + 1)
3922 {
3923 dprev = dp;
3924 dp = dp->df_next;
3925 }
3926 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003927 if (dp->df_lnum[idx_cur] > eap->line2 + off)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003928 break; // past the range that was specified
Bram Moolenaar071d4272004-06-13 20:20:40 +00003929
3930 dfree = NULL;
3931 lnum = dp->df_lnum[idx_to];
3932 count = dp->df_count[idx_to];
3933 if (dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off
3934 && u_save(lnum - 1, lnum + count) != FAIL)
3935 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003936 // Inside the specified range and saving for undo worked.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003937 start_skip = 0;
3938 end_skip = 0;
3939 if (eap->addr_count > 0)
3940 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003941 // A range was specified: check if lines need to be skipped.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003942 start_skip = eap->line1 + off - dp->df_lnum[idx_cur];
3943 if (start_skip > 0)
3944 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003945 // range starts below start of current diff block
Bram Moolenaar071d4272004-06-13 20:20:40 +00003946 if (start_skip > count)
3947 {
3948 lnum += count;
3949 count = 0;
3950 }
3951 else
3952 {
3953 count -= start_skip;
3954 lnum += start_skip;
3955 }
3956 }
3957 else
3958 start_skip = 0;
3959
3960 end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1
3961 - (eap->line2 + off);
3962 if (end_skip > 0)
3963 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003964 // range ends above end of current/from diff block
3965 if (idx_cur == idx_from) // :diffput
Bram Moolenaar071d4272004-06-13 20:20:40 +00003966 {
3967 i = dp->df_count[idx_cur] - start_skip - end_skip;
3968 if (count > i)
3969 count = i;
3970 }
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003971 else // :diffget
Bram Moolenaar071d4272004-06-13 20:20:40 +00003972 {
3973 count -= end_skip;
3974 end_skip = dp->df_count[idx_from] - start_skip - count;
3975 if (end_skip < 0)
3976 end_skip = 0;
3977 }
3978 }
3979 else
3980 end_skip = 0;
3981 }
3982
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003983 buf_empty = BUFEMPTY();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003984 added = 0;
3985 for (i = 0; i < count; ++i)
3986 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003987 // remember deleting the last line of the buffer
Bram Moolenaar280f1262006-01-30 00:14:18 +00003988 buf_empty = curbuf->b_ml.ml_line_count == 1;
Bram Moolenaar4e677b92022-07-28 18:44:27 +01003989 if (ml_delete(lnum) == OK)
3990 --added;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003991 }
3992 for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i)
3993 {
3994 linenr_T nr;
3995
3996 nr = dp->df_lnum[idx_from] + start_skip + i;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003997 if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003998 break;
Bram Moolenaar910f66f2006-04-05 20:41:53 +00003999 p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from],
4000 nr, FALSE));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004001 if (p != NULL)
4002 {
4003 ml_append(lnum + i - 1, p, 0, FALSE);
4004 vim_free(p);
4005 ++added;
Bram Moolenaar280f1262006-01-30 00:14:18 +00004006 if (buf_empty && curbuf->b_ml.ml_line_count == 2)
4007 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004008 // Added the first line into an empty buffer, need to
4009 // delete the dummy empty line.
Bram Moolenaar280f1262006-01-30 00:14:18 +00004010 buf_empty = FALSE;
Bram Moolenaarca70c072020-05-30 20:30:46 +02004011 ml_delete((linenr_T)2);
Bram Moolenaar280f1262006-01-30 00:14:18 +00004012 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004013 }
4014 }
4015 new_count = dp->df_count[idx_to] + added;
4016 dp->df_count[idx_to] = new_count;
4017
4018 if (start_skip == 0 && end_skip == 0)
4019 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004020 // Check if there are any other buffers and if the diff is
4021 // equal in them.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004022 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar910f66f2006-04-05 20:41:53 +00004023 if (curtab->tp_diffbuf[i] != NULL && i != idx_from
4024 && i != idx_to
Bram Moolenaar071d4272004-06-13 20:20:40 +00004025 && !diff_equal_entry(dp, idx_from, i))
4026 break;
4027 if (i == DB_COUNT)
4028 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004029 // delete the diff entry, the buffers are now equal here
Bram Moolenaar071d4272004-06-13 20:20:40 +00004030 dfree = dp;
4031 dp = dp->df_next;
4032 if (dprev == NULL)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004033 curtab->tp_first_diff = dp;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004034 else
4035 dprev->df_next = dp;
4036 }
4037 }
4038
Bram Moolenaar071d4272004-06-13 20:20:40 +00004039 if (added != 0)
4040 {
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01004041 // Adjust marks. This will change the following entries!
Bram Moolenaar071d4272004-06-13 20:20:40 +00004042 mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added);
4043 if (curwin->w_cursor.lnum >= lnum)
4044 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004045 // Adjust the cursor position if it's in/after the changed
4046 // lines.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004047 if (curwin->w_cursor.lnum >= lnum + count)
4048 curwin->w_cursor.lnum += added;
4049 else if (added < 0)
4050 curwin->w_cursor.lnum = lnum;
4051 }
4052 }
4053 changed_lines(lnum, 0, lnum + count, (long)added);
4054
4055 if (dfree != NULL)
4056 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004057 // Diff is deleted, update folds in other windows.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004058#ifdef FEAT_FOLDING
4059 diff_fold_update(dfree, idx_to);
4060#endif
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004061 clear_diffblock(dfree);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004062 }
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01004063
4064 // mark_adjust() may have made "dp" invalid. We don't know where
4065 // to continue then, bail out.
4066 if (added != 0 && !valid_diff(dp))
4067 break;
4068
4069 if (dfree == NULL)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004070 // mark_adjust() may have changed the count in a wrong way
Bram Moolenaar071d4272004-06-13 20:20:40 +00004071 dp->df_count[idx_to] = new_count;
4072
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004073 // When changing the current buffer, keep track of line numbers
Bram Moolenaar071d4272004-06-13 20:20:40 +00004074 if (idx_cur == idx_to)
4075 off += added;
4076 }
4077
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004078 // If before the range or not deleted, go to next diff.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004079 if (dfree == NULL)
4080 {
4081 dprev = dp;
4082 dp = dp->df_next;
4083 }
4084 }
4085
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004086 // restore curwin/curbuf and a few other things
Bram Moolenaara9d52e32010-07-31 16:44:19 +02004087 if (eap->cmdidx != CMD_diffget)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004088 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004089 // Syncing undo only works for the current buffer, but we change
4090 // another buffer. Sync undo if the command was typed. This isn't
4091 // 100% right when ":diffput" is used in a function or mapping.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004092 if (KeyTyped)
Bram Moolenaar779b74b2006-04-10 14:55:34 +00004093 u_sync(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004094 aucmd_restbuf(&aco);
4095 }
4096
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004097theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +00004098 diff_busy = FALSE;
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004099 if (diff_need_update)
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004100 ex_diffupdate(NULL);
Bram Moolenaardf77cef2018-10-07 17:46:42 +02004101
Bram Moolenaar5f57bdc2018-10-25 17:52:23 +02004102 // Check that the cursor is on a valid character and update its
Bram Moolenaardf77cef2018-10-07 17:46:42 +02004103 // position. When there were filler lines the topline has become
4104 // invalid.
4105 check_cursor();
4106 changed_line_abv_curs();
4107
4108 if (diff_need_update)
4109 // redraw already done by ex_diffupdate()
4110 diff_need_update = FALSE;
Bram Moolenaar198fa062018-09-18 21:20:26 +02004111 else
4112 {
Bram Moolenaar198fa062018-09-18 21:20:26 +02004113 // Also need to redraw the other buffers.
4114 diff_redraw(FALSE);
4115 apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, FALSE, curbuf);
4116 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004117}
4118
4119#ifdef FEAT_FOLDING
4120/*
4121 * Update folds for all diff buffers for entry "dp".
4122 * Skip buffer with index "skip_idx".
4123 * When there are no diffs, all folds are removed.
4124 */
4125 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +01004126diff_fold_update(diff_T *dp, int skip_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004127{
4128 int i;
4129 win_T *wp;
4130
Bram Moolenaar29323592016-07-24 22:04:11 +02004131 FOR_ALL_WINDOWS(wp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004132 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004133 if (curtab->tp_diffbuf[i] == wp->w_buffer && i != skip_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004134 foldUpdate(wp, dp->df_lnum[i],
4135 dp->df_lnum[i] + dp->df_count[i]);
4136}
4137#endif
4138
4139/*
4140 * Return TRUE if buffer "buf" is in diff-mode.
4141 */
4142 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01004143diff_mode_buf(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004144{
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004145 tabpage_T *tp;
4146
Bram Moolenaar29323592016-07-24 22:04:11 +02004147 FOR_ALL_TABPAGES(tp)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004148 if (diff_buf_idx_tp(buf, tp) != DB_COUNT)
4149 return TRUE;
4150 return FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004151}
4152
4153/*
4154 * Move "count" times in direction "dir" to the next diff block.
4155 * Return FAIL if there isn't such a diff block.
4156 */
4157 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01004158diff_move_to(int dir, long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004159{
4160 int idx;
4161 linenr_T lnum = curwin->w_cursor.lnum;
4162 diff_T *dp;
4163
4164 idx = diff_buf_idx(curbuf);
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004165 if (idx == DB_COUNT || curtab->tp_first_diff == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004166 return FAIL;
4167
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004168 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004169 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00004170
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004171 if (curtab->tp_first_diff == NULL) // no diffs today
Bram Moolenaar071d4272004-06-13 20:20:40 +00004172 return FAIL;
4173
4174 while (--count >= 0)
4175 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004176 // Check if already before first diff.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004177 if (dir == BACKWARD && lnum <= curtab->tp_first_diff->df_lnum[idx])
Bram Moolenaar071d4272004-06-13 20:20:40 +00004178 break;
4179
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004180 for (dp = curtab->tp_first_diff; ; dp = dp->df_next)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004181 {
4182 if (dp == NULL)
4183 break;
4184 if ((dir == FORWARD && lnum < dp->df_lnum[idx])
4185 || (dir == BACKWARD
4186 && (dp->df_next == NULL
4187 || lnum <= dp->df_next->df_lnum[idx])))
4188 {
4189 lnum = dp->df_lnum[idx];
4190 break;
4191 }
4192 }
4193 }
4194
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004195 // don't end up past the end of the file
Bram Moolenaar071d4272004-06-13 20:20:40 +00004196 if (lnum > curbuf->b_ml.ml_line_count)
4197 lnum = curbuf->b_ml.ml_line_count;
4198
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004199 // When the cursor didn't move at all we fail.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004200 if (lnum == curwin->w_cursor.lnum)
4201 return FAIL;
4202
4203 setpcmark();
4204 curwin->w_cursor.lnum = lnum;
4205 curwin->w_cursor.col = 0;
4206
4207 return OK;
4208}
4209
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004210/*
4211 * Return the line number in the current window that is closest to "lnum1" in
4212 * "buf1" in diff mode.
4213 */
4214 static linenr_T
4215diff_get_corresponding_line_int(
Bram Moolenaar7454a062016-01-30 15:14:10 +01004216 buf_T *buf1,
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004217 linenr_T lnum1)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004218{
4219 int idx1;
4220 int idx2;
4221 diff_T *dp;
4222 int baseline = 0;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004223
4224 idx1 = diff_buf_idx(buf1);
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004225 idx2 = diff_buf_idx(curbuf);
Bram Moolenaar860cae12010-06-05 23:22:07 +02004226 if (idx1 == DB_COUNT || idx2 == DB_COUNT || curtab->tp_first_diff == NULL)
4227 return lnum1;
4228
4229 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004230 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar860cae12010-06-05 23:22:07 +02004231
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004232 if (curtab->tp_first_diff == NULL) // no diffs today
Bram Moolenaar860cae12010-06-05 23:22:07 +02004233 return lnum1;
4234
Bram Moolenaaraeea7212020-04-02 18:50:46 +02004235 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004236 {
4237 if (dp->df_lnum[idx1] > lnum1)
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004238 return lnum1 - baseline;
4239 if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004240 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004241 // Inside the diffblock
Bram Moolenaar860cae12010-06-05 23:22:07 +02004242 baseline = lnum1 - dp->df_lnum[idx1];
4243 if (baseline > dp->df_count[idx2])
4244 baseline = dp->df_count[idx2];
4245
4246 return dp->df_lnum[idx2] + baseline;
4247 }
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004248 if ( (dp->df_lnum[idx1] == lnum1)
4249 && (dp->df_count[idx1] == 0)
4250 && (dp->df_lnum[idx2] <= curwin->w_cursor.lnum)
4251 && ((dp->df_lnum[idx2] + dp->df_count[idx2])
4252 > curwin->w_cursor.lnum))
Bram Moolenaar860cae12010-06-05 23:22:07 +02004253 /*
4254 * Special case: if the cursor is just after a zero-count
4255 * block (i.e. all filler) and the target cursor is already
4256 * inside the corresponding block, leave the target cursor
4257 * unmoved. This makes repeated CTRL-W W operations work
4258 * as expected.
4259 */
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004260 return curwin->w_cursor.lnum;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004261 baseline = (dp->df_lnum[idx1] + dp->df_count[idx1])
4262 - (dp->df_lnum[idx2] + dp->df_count[idx2]);
4263 }
4264
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004265 // If we get here then the cursor is after the last diff
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004266 return lnum1 - baseline;
4267}
Bram Moolenaar860cae12010-06-05 23:22:07 +02004268
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004269/*
4270 * Return the line number in the current window that is closest to "lnum1" in
4271 * "buf1" in diff mode. Checks the line number to be valid.
4272 */
4273 linenr_T
4274diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1)
4275{
4276 linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1);
4277
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004278 // don't end up past the end of the file
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004279 if (lnum > curbuf->b_ml.ml_line_count)
4280 return curbuf->b_ml.ml_line_count;
4281 return lnum;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004282}
Bram Moolenaar860cae12010-06-05 23:22:07 +02004283
Bram Moolenaar071d4272004-06-13 20:20:40 +00004284/*
4285 * For line "lnum" in the current window find the equivalent lnum in window
4286 * "wp", compensating for inserted/deleted lines.
4287 */
4288 linenr_T
Bram Moolenaar7454a062016-01-30 15:14:10 +01004289diff_lnum_win(linenr_T lnum, win_T *wp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004290{
4291 diff_T *dp;
4292 int idx;
4293 int i;
4294 linenr_T n;
4295
4296 idx = diff_buf_idx(curbuf);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004297 if (idx == DB_COUNT) // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00004298 return (linenr_T)0;
4299
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004300 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004301 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00004302
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004303 // search for a change that includes "lnum" in the list of diffblocks.
Bram Moolenaaraeea7212020-04-02 18:50:46 +02004304 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004305 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
4306 break;
4307
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004308 // When after the last change, compute relative to the last line number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004309 if (dp == NULL)
4310 return wp->w_buffer->b_ml.ml_line_count
4311 - (curbuf->b_ml.ml_line_count - lnum);
4312
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004313 // Find index for "wp".
Bram Moolenaar071d4272004-06-13 20:20:40 +00004314 i = diff_buf_idx(wp->w_buffer);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004315 if (i == DB_COUNT) // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00004316 return (linenr_T)0;
4317
4318 n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]);
4319 if (n > dp->df_lnum[i] + dp->df_count[i])
4320 n = dp->df_lnum[i] + dp->df_count[i];
4321 return n;
4322}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004323
Bram Moolenaare828b762018-09-10 17:51:58 +02004324/*
4325 * Handle an ED style diff line.
4326 * Return FAIL if the line does not contain diff info.
4327 */
4328 static int
4329parse_diff_ed(
4330 char_u *line,
Lewis Russelld9da86e2021-12-28 13:54:41 +00004331 diffhunk_T *hunk)
Bram Moolenaare828b762018-09-10 17:51:58 +02004332{
4333 char_u *p;
4334 long f1, l1, f2, l2;
4335 int difftype;
4336
4337 // The line must be one of three formats:
4338 // change: {first}[,{last}]c{first}[,{last}]
4339 // append: {first}a{first}[,{last}]
4340 // delete: {first}[,{last}]d{first}
4341 p = line;
4342 f1 = getdigits(&p);
4343 if (*p == ',')
4344 {
4345 ++p;
4346 l1 = getdigits(&p);
4347 }
4348 else
4349 l1 = f1;
4350 if (*p != 'a' && *p != 'c' && *p != 'd')
4351 return FAIL; // invalid diff format
4352 difftype = *p++;
4353 f2 = getdigits(&p);
4354 if (*p == ',')
4355 {
4356 ++p;
4357 l2 = getdigits(&p);
4358 }
4359 else
4360 l2 = f2;
4361 if (l1 < f1 || l2 < f2)
4362 return FAIL;
4363
4364 if (difftype == 'a')
4365 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004366 hunk->lnum_orig = f1 + 1;
4367 hunk->count_orig = 0;
Bram Moolenaare828b762018-09-10 17:51:58 +02004368 }
4369 else
4370 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004371 hunk->lnum_orig = f1;
4372 hunk->count_orig = l1 - f1 + 1;
Bram Moolenaare828b762018-09-10 17:51:58 +02004373 }
4374 if (difftype == 'd')
4375 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004376 hunk->lnum_new = f2 + 1;
4377 hunk->count_new = 0;
Bram Moolenaare828b762018-09-10 17:51:58 +02004378 }
4379 else
4380 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004381 hunk->lnum_new = f2;
4382 hunk->count_new = l2 - f2 + 1;
Bram Moolenaare828b762018-09-10 17:51:58 +02004383 }
4384 return OK;
4385}
4386
4387/*
4388 * Parses unified diff with zero(!) context lines.
4389 * Return FAIL if there is no diff information in "line".
4390 */
4391 static int
4392parse_diff_unified(
4393 char_u *line,
Lewis Russelld9da86e2021-12-28 13:54:41 +00004394 diffhunk_T *hunk)
Bram Moolenaare828b762018-09-10 17:51:58 +02004395{
4396 char_u *p;
4397 long oldline, oldcount, newline, newcount;
4398
4399 // Parse unified diff hunk header:
4400 // @@ -oldline,oldcount +newline,newcount @@
4401 p = line;
4402 if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-')
4403 {
4404 oldline = getdigits(&p);
4405 if (*p == ',')
4406 {
4407 ++p;
4408 oldcount = getdigits(&p);
4409 }
4410 else
4411 oldcount = 1;
4412 if (*p++ == ' ' && *p++ == '+')
4413 {
4414 newline = getdigits(&p);
4415 if (*p == ',')
4416 {
4417 ++p;
4418 newcount = getdigits(&p);
4419 }
4420 else
4421 newcount = 1;
4422 }
4423 else
4424 return FAIL; // invalid diff format
4425
4426 if (oldcount == 0)
4427 oldline += 1;
4428 if (newcount == 0)
4429 newline += 1;
4430 if (newline == 0)
4431 newline = 1;
4432
Lewis Russelld9da86e2021-12-28 13:54:41 +00004433 hunk->lnum_orig = oldline;
4434 hunk->count_orig = oldcount;
4435 hunk->lnum_new = newline;
4436 hunk->count_new = newcount;
Bram Moolenaare828b762018-09-10 17:51:58 +02004437
4438 return OK;
4439 }
4440
4441 return FAIL;
4442}
4443
4444/*
4445 * Callback function for the xdl_diff() function.
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004446 * Stores the diff output (indices) in a grow array.
Bram Moolenaare828b762018-09-10 17:51:58 +02004447 */
4448 static int
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004449xdiff_out_indices(
Lewis Russelld9da86e2021-12-28 13:54:41 +00004450 long start_a,
4451 long count_a,
4452 long start_b,
4453 long count_b,
4454 void *priv)
Bram Moolenaare828b762018-09-10 17:51:58 +02004455{
4456 diffout_T *dout = (diffout_T *)priv;
Lewis Russelld9da86e2021-12-28 13:54:41 +00004457 diffhunk_T *p = ALLOC_ONE(diffhunk_T);
Bram Moolenaare828b762018-09-10 17:51:58 +02004458
Lewis Russelld9da86e2021-12-28 13:54:41 +00004459 if (p == NULL)
4460 return -1;
Bram Moolenaarf080d702018-10-31 22:57:26 +01004461
4462 if (ga_grow(&dout->dout_ga, 1) == FAIL)
Bram Moolenaarfebb78f2021-12-29 11:59:53 +00004463 {
4464 vim_free(p);
Bram Moolenaarf080d702018-10-31 22:57:26 +01004465 return -1;
Bram Moolenaarfebb78f2021-12-29 11:59:53 +00004466 }
Lewis Russelld9da86e2021-12-28 13:54:41 +00004467
4468 p->lnum_orig = start_a + 1;
4469 p->count_orig = count_a;
4470 p->lnum_new = start_b + 1;
4471 p->count_new = count_b;
4472 ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
Bram Moolenaare828b762018-09-10 17:51:58 +02004473 return 0;
4474}
4475
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004476/*
4477 * Callback function for the xdl_diff() function.
4478 * Stores the unified diff output in a grow array.
4479 */
4480 static int
4481xdiff_out_unified(
4482 void *priv,
4483 mmbuffer_t *mb,
4484 int nbuf)
4485{
4486 diffout_T *dout = (diffout_T *)priv;
4487 int i;
4488
4489 for (i = 0; i < nbuf; i++)
4490 ga_concat_len(&dout->dout_ga, (char_u *)mb[i].ptr, mb[i].size);
4491
4492 return 0;
4493}
4494
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004495#endif // FEAT_DIFF
4496
4497#if defined(FEAT_EVAL) || defined(PROTO)
4498
4499/*
4500 * "diff_filler()" function
4501 */
4502 void
4503f_diff_filler(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4504{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004505# ifdef FEAT_DIFF
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02004506 if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
4507 return;
4508
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004509 rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004510# endif
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004511}
4512
4513/*
4514 * "diff_hlID()" function
4515 */
4516 void
4517f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4518{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004519# ifdef FEAT_DIFF
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004520 linenr_T lnum;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004521 static linenr_T prev_lnum = 0;
4522 static varnumber_T changedtick = 0;
4523 static int fnum = 0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004524 static int prev_diff_flags = 0;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004525 static int change_start = 0;
4526 static int change_end = 0;
4527 static hlf_T hlID = (hlf_T)0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004528 int cache_results = TRUE;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004529 int filler_lines;
4530 int col;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004531 diffline_T diffline;
4532
4533 CLEAR_FIELD(diffline);
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004534
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004535 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02004536 && (check_for_lnum_arg(argvars,0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004537 || check_for_number_arg(argvars, 1) == FAIL))
4538 return;
4539
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004540 if (diff_flags & ALL_INLINE_DIFF)
4541 {
4542 // Remember the results if using simple since it's recalculated per
4543 // call. Otherwise just call diff_find_change() every time since
4544 // internally the result is cached interally.
4545 cache_results = FALSE;
4546 }
4547
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004548 lnum = tv_get_lnum(argvars);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004549 if (lnum < 0) // ignore type error in {lnum} arg
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004550 lnum = 0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004551 if (!cache_results
4552 || lnum != prev_lnum
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004553 || changedtick != CHANGEDTICK(curbuf)
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004554 || fnum != curbuf->b_fnum
4555 || diff_flags != prev_diff_flags)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004556 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004557 // New line, buffer, change: need to get the values.
Jonathon7c7a4e62025-01-12 09:58:00 +01004558 int linestatus = 0;
4559 filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus);
4560 if (filler_lines < 0 || linestatus < 0)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004561 {
Jonathon7c7a4e62025-01-12 09:58:00 +01004562 if (filler_lines == -1 || linestatus == -1)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004563 {
4564 change_start = MAXCOL;
4565 change_end = -1;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004566 if (diff_find_change(curwin, lnum, &diffline))
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004567 hlID = HLF_ADD; // added line
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004568 else
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004569 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004570 hlID = HLF_CHD; // changed line
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004571 if (diffline.num_changes > 0 && cache_results)
4572 {
4573 change_start = diffline.changes[0].dc_start[diffline.bufidx];
4574 change_end = diffline.changes[0].dc_end[diffline.bufidx];
4575 }
4576 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004577 }
4578 else
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004579 hlID = HLF_ADD; // added line
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004580 }
4581 else
4582 hlID = (hlf_T)0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004583
4584 if (cache_results)
4585 {
4586 prev_lnum = lnum;
4587 changedtick = CHANGEDTICK(curbuf);
4588 fnum = curbuf->b_fnum;
4589 prev_diff_flags = diff_flags;
4590 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004591 }
4592
4593 if (hlID == HLF_CHD || hlID == HLF_TXD)
4594 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004595 col = tv_get_number(&argvars[1]) - 1; // ignore type error in {col}
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004596 if (cache_results)
4597 {
4598 if (col >= change_start && col < change_end)
4599 hlID = HLF_TXD; // changed text
4600 else
4601 hlID = HLF_CHD; // changed line
4602 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004603 else
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004604 {
4605 hlID = HLF_CHD;
4606 for (int i = 0; i < diffline.num_changes; i++)
4607 {
4608 int added = diff_change_parse(&diffline, &diffline.changes[i],
4609 &change_start, &change_end);
4610 if (col >= change_start && col < change_end)
4611 {
4612 hlID = added ? HLF_TXA : HLF_TXD;
4613 break;
4614 }
4615 if (col < change_start)
4616 // the remaining changes are past this column and not relevant
4617 break;
4618 }
4619 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004620 }
4621 rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID;
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004622# endif
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004623}
4624
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004625# ifdef FEAT_DIFF
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004626/*
4627 * Parse the diff options passed in "optarg" to the diff() function and return
4628 * the options in "diffopts" and the diff algorithm in "diffalgo".
4629 */
4630 static int
4631parse_diff_optarg(
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004632 typval_T *opts,
4633 int *diffopts,
4634 long *diffalgo,
4635 dio_outfmt_T *diff_output_fmt,
4636 int *diff_ctxlen)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004637{
4638 dict_T *d = opts->vval.v_dict;
4639
4640 char_u *algo = dict_get_string(d, "algorithm", FALSE);
4641 if (algo != NULL)
4642 {
4643 if (STRNCMP(algo, "myers", 5) == 0)
4644 *diffalgo = 0;
4645 else if (STRNCMP(algo, "minimal", 7) == 0)
4646 *diffalgo = XDF_NEED_MINIMAL;
4647 else if (STRNCMP(algo, "patience", 8) == 0)
4648 *diffalgo = XDF_PATIENCE_DIFF;
4649 else if (STRNCMP(algo, "histogram", 9) == 0)
4650 *diffalgo = XDF_HISTOGRAM_DIFF;
4651 }
4652
4653 char_u *output_fmt = dict_get_string(d, "output", FALSE);
4654 if (output_fmt != NULL)
4655 {
4656 if (STRNCMP(output_fmt, "unified", 7) == 0)
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004657 *diff_output_fmt = DIO_OUTPUT_UNIFIED;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004658 else if (STRNCMP(output_fmt, "indices", 7) == 0)
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004659 *diff_output_fmt = DIO_OUTPUT_INDICES;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004660 else
4661 {
4662 semsg(_(e_unsupported_diff_output_format_str), output_fmt);
4663 return FAIL;
4664 }
4665 }
4666
Yegappan Lakshmanana0010a12024-02-12 20:21:26 +01004667 *diff_ctxlen = dict_get_number_def(d, "context", 0);
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004668 if (*diff_ctxlen < 0)
Yegappan Lakshmanana0010a12024-02-12 20:21:26 +01004669 *diff_ctxlen = 0;
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004670
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004671 if (dict_get_bool(d, "iblank", FALSE))
4672 *diffopts |= DIFF_IBLANK;
4673 if (dict_get_bool(d, "icase", FALSE))
4674 *diffopts |= DIFF_ICASE;
4675 if (dict_get_bool(d, "iwhite", FALSE))
4676 *diffopts |= DIFF_IWHITE;
4677 if (dict_get_bool(d, "iwhiteall", FALSE))
4678 *diffopts |= DIFF_IWHITEALL;
4679 if (dict_get_bool(d, "iwhiteeol", FALSE))
4680 *diffopts |= DIFF_IWHITEEOL;
4681 if (dict_get_bool(d, "indent-heuristic", FALSE))
4682 *diffalgo |= XDF_INDENT_HEURISTIC;
4683
4684 return OK;
4685}
4686
4687/*
4688 * Concatenate the List of strings in "l" and store the result in
4689 * "din->din_mmfile.ptr" and the length in "din->din_mmfile.size".
4690 */
4691 static void
4692list_to_diffin(list_T *l, diffin_T *din, int icase)
4693{
4694 garray_T ga;
4695 listitem_T *li;
4696 char_u *str;
4697
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004698 ga_init2(&ga, 1, 2048);
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004699
4700 FOR_ALL_LIST_ITEMS(l, li)
4701 {
4702 str = tv_get_string(&li->li_tv);
4703 if (icase)
4704 {
4705 str = strlow_save(str);
4706 if (str == NULL)
4707 continue;
4708 }
4709 ga_concat(&ga, str);
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004710 ga_append(&ga, NL);
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004711 if (icase)
4712 vim_free(str);
4713 }
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004714
4715 din->din_mmfile.ptr = (char *)ga.ga_data;
4716 din->din_mmfile.size = ga.ga_len;
4717}
4718
4719/*
4720 * Get the start and end indices from the diff "hunk".
4721 */
4722 static dict_T *
4723get_diff_hunk_indices(diffhunk_T *hunk)
4724{
4725 dict_T *hunk_dict;
4726
4727 hunk_dict = dict_alloc();
4728 if (hunk_dict == NULL)
4729 return NULL;
4730
4731 dict_add_number(hunk_dict, "from_idx", hunk->lnum_orig - 1);
4732 dict_add_number(hunk_dict, "from_count", hunk->count_orig);
4733 dict_add_number(hunk_dict, "to_idx", hunk->lnum_new - 1);
4734 dict_add_number(hunk_dict, "to_count", hunk->count_new);
4735
4736 return hunk_dict;
4737}
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004738# endif
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004739
4740/*
4741 * "diff()" function
4742 */
4743 void
4744f_diff(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4745{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004746# ifdef FEAT_DIFF
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004747 diffio_T dio;
4748
4749 if (check_for_nonnull_list_arg(argvars, 0) == FAIL
4750 || check_for_nonnull_list_arg(argvars, 1) == FAIL
4751 || check_for_opt_nonnull_dict_arg(argvars, 2) == FAIL)
4752 return;
4753
4754 CLEAR_FIELD(dio);
4755 dio.dio_internal = TRUE;
4756 ga_init2(&dio.dio_diff.dout_ga, sizeof(char *), 1000);
4757
4758 list_T *orig_list = argvars[0].vval.v_list;
4759 list_T *new_list = argvars[1].vval.v_list;
4760
4761 // Save the 'diffopt' option value and restore it after getting the diff.
4762 int save_diff_flags = diff_flags;
4763 long save_diff_algorithm = diff_algorithm;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004764 diff_flags = DIFF_INTERNAL;
4765 diff_algorithm = 0;
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004766 dio.dio_outfmt = DIO_OUTPUT_UNIFIED;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004767 if (argvars[2].v_type != VAR_UNKNOWN)
4768 if (parse_diff_optarg(&argvars[2], &diff_flags, &diff_algorithm,
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004769 &dio.dio_outfmt, &dio.dio_ctxlen) == FAIL)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004770 return;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004771
4772 // Concatenate the List of strings into a single string using newline
4773 // separator. Internal diff library expects a single string.
4774 list_to_diffin(orig_list, &dio.dio_orig, diff_flags & DIFF_ICASE);
4775 list_to_diffin(new_list, &dio.dio_new, diff_flags & DIFF_ICASE);
4776
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004777 // If 'diffexpr' is set, then the internal diff is not used. Set
4778 // 'diffexpr' to an empty string temporarily.
4779 int restore_diffexpr = FALSE;
4780 char_u cc = *p_dex;
4781 if (*p_dex != NUL)
4782 {
4783 restore_diffexpr = TRUE;
4784 *p_dex = NUL;
4785 }
4786
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004787 // Compute the diff
4788 int diff_status = diff_file(&dio);
4789
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004790 // restore 'diffexpr'
4791 if (restore_diffexpr)
4792 *p_dex = cc;
4793
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004794 if (diff_status == FAIL)
4795 goto done;
4796
4797 int hunk_idx = 0;
4798 dict_T *hunk_dict;
4799
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004800 if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004801 {
4802 if (rettv_list_alloc(rettv) != OK)
4803 goto done;
4804 list_T *l = rettv->vval.v_list;
4805
4806 // Process each diff hunk
4807 diffhunk_T *hunk = NULL;
4808 while (hunk_idx < dio.dio_diff.dout_ga.ga_len)
4809 {
4810 hunk = ((diffhunk_T **)dio.dio_diff.dout_ga.ga_data)[hunk_idx++];
4811
4812 hunk_dict = get_diff_hunk_indices(hunk);
4813 if (hunk_dict == NULL)
4814 goto done;
4815
4816 list_append_dict(l, hunk_dict);
4817 }
4818 }
4819 else
4820 {
4821 ga_append(&dio.dio_diff.dout_ga, NUL);
4822 rettv->v_type = VAR_STRING;
4823 rettv->vval.v_string =
4824 vim_strsave((char_u *)dio.dio_diff.dout_ga.ga_data);
4825 }
4826
4827done:
4828 clear_diffin(&dio.dio_new);
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004829 if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004830 clear_diffout(&dio.dio_diff);
4831 else
4832 ga_clear(&dio.dio_diff.dout_ga);
4833 clear_diffin(&dio.dio_orig);
4834 // Restore the 'diffopt' option value.
4835 diff_flags = save_diff_flags;
4836 diff_algorithm = save_diff_algorithm;
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004837# endif
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004838}
4839
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004840#endif