blob: e694cf20cd49acf96aad8b4ed14761d5cb6b6908 [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 {
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003312 int new_in_keyword = FALSE;
3313 if (diff_flags & DIFF_INLINE_WORD)
Yee Cheng Chin9aa120f2025-04-04 19:16:21 +02003314 {
3315 // Always use the first buffer's 'iskeyword' to have a
3316 // consistent diff.
3317 // For multibyte chars, only treat alphanumeric chars
3318 // (class 2) as "word", as other classes such as emojis and
3319 // CJK ideographs do not usually benefit from word diff as
3320 // Vim doesn't have a good way to segment them.
3321 new_in_keyword = (mb_get_class_buf(s, curtab->tp_diffbuf[file1_idx]) == 2);
3322 }
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003323 if (in_keyword && !new_in_keyword)
3324 {
3325 ga_append(curstr, NL);
3326 numlines++;
3327 }
3328
3329 if (VIM_ISWHITE(*s))
3330 {
3331 if (diff_flags & DIFF_IWHITEALL)
3332 {
3333 in_keyword = FALSE;
3334 s = skipwhite(s);
3335 continue;
3336 }
3337 else if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3338 {
3339 if (!last_white)
3340 {
3341 eol_ga_len = curstr->ga_len;
3342 eol_linemap_len = linemap[i].ga_len;
3343 eol_numlines = numlines;
3344 last_white = TRUE;
3345 }
3346 }
3347 }
3348 else
3349 {
3350 if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3351 {
3352 last_white = FALSE;
3353 eol_ga_len = -1;
3354 eol_linemap_len = -1;
3355 eol_numlines = -1;
3356 }
3357 }
3358
3359 int char_len = 1;
3360 if (*s == NL)
3361 // NL is internal substitute for NUL
3362 ga_append(curstr, NUL);
3363 else
3364 {
3365 char_len = mb_ptr2len(s);
3366
3367 if (VIM_ISWHITE(*s) && (diff_flags & DIFF_IWHITE))
3368 // Treat the entire white space span as a single char.
3369 char_len = skipwhite(s) - s;
3370
3371 if (diff_flags & DIFF_ICASE)
3372 {
3373 int c;
3374 char_u cbuf[MB_MAXBYTES + 1];
3375 // xdiff doesn't support ignoring case, fold-case the text manually.
3376 c = PTR2CHAR(s);
3377 int c_len = MB_CHAR2LEN(c);
3378 c = MB_CASEFOLD(c);
3379 int c_fold_len = mb_char2bytes(c, cbuf);
3380 ga_concat_len(curstr, cbuf, c_fold_len);
3381 if (char_len > c_len)
3382 {
3383 // There may be remaining composing characters. Write those back in.
3384 // Composing characters don't need case folding.
3385 ga_concat_len(curstr, s + c_len, char_len - c_len);
3386 }
3387 }
3388 else
3389 ga_concat_len(curstr, s, char_len);
3390 }
3391
3392 if (!new_in_keyword)
3393 {
3394 ga_append(curstr, NL);
3395 numlines++;
3396 }
3397
3398 if (!new_in_keyword || (new_in_keyword && !in_keyword))
3399 {
3400 // create a new mapping entry from the xdiff mmfile back to
3401 // original line/col.
3402 linemap_entry_T linemap_entry;
3403 linemap_entry.lineoff = off;
3404 linemap_entry.byte_start = s - curline;
3405 linemap_entry.num_bytes = char_len;
3406 if (ga_grow(&linemap[i], 1) != OK)
3407 goto done;
3408 ((linemap_entry_T*)(linemap[i].ga_data))[linemap[i].ga_len]
3409 = linemap_entry;
3410 linemap[i].ga_len += 1;
3411 }
3412 else
3413 {
3414 // Still inside a keyword. Just increment byte count but
3415 // don't make a new entry.
3416 // linemap always has at least one entry here
3417 ((linemap_entry_T*)linemap[i].ga_data)[linemap[i].ga_len-1].num_bytes
3418 += char_len;
3419 }
3420
3421 in_keyword = new_in_keyword;
3422 s += char_len;
3423 }
3424 if (in_keyword)
3425 {
3426 ga_append(curstr, NL);
3427 numlines++;
3428 }
3429
3430 if ((diff_flags & DIFF_IWHITEEOL) || (diff_flags & DIFF_IWHITE))
3431 {
3432 // Need to trim trailing whitespace. Do this simply by
3433 // resetting arrays back to before we encountered them.
3434 if (eol_ga_len != -1)
3435 {
3436 curstr->ga_len = eol_ga_len;
3437 linemap[i].ga_len = eol_linemap_len;
3438 numlines = eol_numlines;
3439 }
3440 }
3441
3442 if (!(diff_flags & DIFF_IWHITEALL))
3443 {
3444 // Add an empty line token mapped to the end-of-line in the
3445 // original file. This helps diff newline differences among
3446 // files, which will be visualized when using 'list' as the eol
3447 // listchar will be highlighted.
3448 ga_append(curstr, NL);
3449 numlines++;
3450
3451 linemap_entry_T linemap_entry;
3452 linemap_entry.lineoff = off;
3453 linemap_entry.byte_start = s - curline;
3454 linemap_entry.num_bytes = sizeof(NL);
3455 if (ga_grow(&linemap[i], 1) != OK)
3456 goto done;
3457 ((linemap_entry_T*)(linemap[i].ga_data))[linemap[i].ga_len]
3458 = linemap_entry;
3459 linemap[i].ga_len += 1;
3460 }
3461 }
3462
3463 if (file1_idx != i)
3464 {
3465 dio.dio_new.din_mmfile.ptr = (char *)curstr->ga_data;
3466 dio.dio_new.din_mmfile.size = curstr->ga_len;
3467 }
3468 else
3469 {
3470 dio.dio_orig.din_mmfile.ptr = (char *)curstr->ga_data;
3471 dio.dio_orig.din_mmfile.size = curstr->ga_len;
3472 }
3473 if (file1_idx != i)
3474 {
3475 // Perform diff with first file and read the results
3476 int diff_status = diff_file_internal(&dio);
3477 if (diff_status == FAIL)
3478 goto done;
3479
3480 diff_read(0, i, &dio);
3481 clear_diffout(&dio.dio_diff);
3482 }
3483 }
3484 diff_T *new_diff = curtab->tp_first_diff;
3485
3486 if (diff_flags & DIFF_INLINE_CHAR && file1_idx != -1)
3487 diff_refine_inline_char_highlight(new_diff, linemap, file1_idx);
3488
3489 // After the diff, use the linemap to obtain the original line/col of the
3490 // changes and cache them in dp.
3491 dp->df_changes.ga_len = 0; // this should already be zero
3492 for (; new_diff != NULL; new_diff = new_diff->df_next)
3493 {
3494 diffline_change_T change;
3495 CLEAR_FIELD(change);
3496 for (int i = 0; i < DB_COUNT; i++)
3497 {
3498 if (new_diff->df_lnum[i] == 0)
3499 continue;
3500 linenr_T diff_lnum = new_diff->df_lnum[i] - 1; // use zero-index
3501 linenr_T diff_lnum_end = diff_lnum + new_diff->df_count[i];
3502
3503 if (diff_lnum >= linemap[i].ga_len)
3504 {
3505 change.dc_start[i] = MAXCOL;
3506 change.dc_start_lnum_off[i] = INT_MAX;
3507 }
3508 else
3509 {
3510 change.dc_start[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum].byte_start;
3511 change.dc_start_lnum_off[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum].lineoff;
3512 }
3513
3514 if (diff_lnum == diff_lnum_end)
3515 {
3516 change.dc_end[i] = change.dc_start[i];
3517 change.dc_end_lnum_off[i] = change.dc_start_lnum_off[i];
3518 }
3519 else if (diff_lnum_end - 1 >= linemap[i].ga_len)
3520 {
3521 change.dc_end[i] = MAXCOL;
3522 change.dc_end_lnum_off[i] = INT_MAX;
3523 }
3524 else
3525 {
3526 change.dc_end[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].byte_start +
3527 ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].num_bytes;
3528 change.dc_end_lnum_off[i] = ((linemap_entry_T*)linemap[i].ga_data)[diff_lnum_end-1].lineoff;
3529 }
3530 }
3531 if (ga_grow(&dp->df_changes, 1) != OK)
3532 {
3533 dp->df_changes.ga_len = 0;
3534 goto done;
3535 }
3536 ((diffline_change_T*)(dp->df_changes.ga_data))[dp->df_changes.ga_len] = change;
3537 dp->df_changes.ga_len += 1;
3538 }
3539
3540done:
3541 diff_algorithm = save_diff_algorithm;
3542
3543 dp->has_changes = TRUE;
3544
3545 diff_clear(curtab);
3546 curtab->tp_first_diff = orig_diff;
3547
3548 ga_clear(&file1_str);
3549 ga_clear(&file2_str);
3550 // No need to clear dio.dio_orig/dio_new because they were referencing
3551 // strings that are now cleared.
3552 clear_diffout(&dio.dio_diff);
3553 for (int i = 0; i < DB_COUNT; i++)
3554 ga_clear(&linemap[i]);
3555}
3556
3557/*
3558 * Find the difference within a changed line.
3559 * Returns TRUE if the line was added, no other buffer has it.
3560 */
3561 int
3562diff_find_change(
3563 win_T *wp,
3564 linenr_T lnum,
3565 diffline_T *diffline)
3566{
3567 diff_T *dp;
3568 int idx;
3569 int off;
3570
3571 idx = diff_buf_idx(wp->w_buffer);
3572 if (idx == DB_COUNT) // cannot happen
3573 return FALSE;
3574
3575 // search for a change that includes "lnum" in the list of diffblocks.
3576 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
3577 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
3578 break;
3579 if (dp->is_linematched)
3580 {
3581 while (dp && dp->df_next
3582 && lnum == dp->df_count[idx] + dp->df_lnum[idx]
3583 && dp->df_next->df_lnum[idx] == lnum)
3584 dp = dp->df_next;
3585 }
3586 if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL)
3587 return FALSE;
3588
3589 if (lnum - dp->df_lnum[idx] > INT_MAX)
3590 // Integer overflow protection
3591 return FALSE;
3592 off = lnum - dp->df_lnum[idx];
3593
3594 if (!(diff_flags & ALL_INLINE_DIFF) || diff_internal_failed())
3595 {
3596 // Use simple algorithm
3597 int change_start = MAXCOL; // first col of changed area
3598 int change_end = -1; // last col of changed area
3599 int ret;
3600
3601 ret = diff_find_change_simple(wp, lnum, dp, idx, &change_start, &change_end);
3602
3603 // convert from inclusive end to exclusive end per diffline's contract
3604 change_end += 1;
3605
3606 // Create a mock diffline struct. We always only have one so no need to
3607 // allocate memory.
Yee Cheng Chin9943d472025-03-26 19:41:02 +01003608 CLEAR_FIELD(simple_diffline_change);
3609 diffline->changes = &simple_diffline_change;
3610 diffline->num_changes = 1;
3611 diffline->bufidx = idx;
3612 diffline->lineoff = lnum - dp->df_lnum[idx];
3613
3614 simple_diffline_change.dc_start[idx] = change_start;
3615 simple_diffline_change.dc_end[idx] = change_end;
3616 simple_diffline_change.dc_start_lnum_off[idx] = off;
3617 simple_diffline_change.dc_end_lnum_off[idx] = off;
3618 return ret;
3619 }
3620
3621 // Use inline diff algorithm.
3622 // The diff changes are usually cached so we check that first.
3623 if (!dp->has_changes)
3624 diff_find_change_inline_diff(dp);
3625
3626 garray_T *changes = &dp->df_changes;
3627
3628 // Use linear search to find the first change for this line. We could
3629 // optimize this to use binary search, but there should usually be a
3630 // limited number of inline changes per diff block, and limited number of
3631 // diff blocks shown on screen, so it is not necessary.
3632 int num_changes = 0;
3633 int change_idx = 0;
3634 diffline->changes = NULL;
3635 for (change_idx = 0; change_idx < changes->ga_len; change_idx++)
3636 {
3637 diffline_change_T *change = &((diffline_change_T*)dp->df_changes.ga_data)[change_idx];
3638 if (change->dc_end_lnum_off[idx] < off)
3639 continue;
3640 if (change->dc_start_lnum_off[idx] > off)
3641 break;
3642 if (diffline->changes == NULL)
3643 diffline->changes = change;
3644 num_changes++;
3645 }
3646 diffline->num_changes = num_changes;
3647 diffline->bufidx = idx;
3648 diffline->lineoff = off;
3649
3650 // Detect simple cases of added lines in the end within a diff block. This
3651 // has to be the last change of this diff block, and all other buffers are
3652 // considering this to be an addition past their last line. Other scenarios
3653 // will be considered a changed line instead.
3654 int added = FALSE;
3655 if (num_changes == 1 && change_idx == dp->df_changes.ga_len)
3656 {
3657 added = TRUE;
3658 for (int i = 0; i < DB_COUNT; i++)
3659 {
3660 if (idx == i)
3661 continue;
3662 if (curtab->tp_diffbuf[i] == NULL)
3663 continue;
3664 diffline_change_T *change = &((diffline_change_T*)dp->df_changes.ga_data)[dp->df_changes.ga_len-1];
3665 if (change->dc_start_lnum_off[i] != INT_MAX)
3666 {
3667 added = FALSE;
3668 break;
3669 }
3670 }
3671 }
3672 return added;
3673}
3674
Bram Moolenaar071d4272004-06-13 20:20:40 +00003675#if defined(FEAT_FOLDING) || defined(PROTO)
3676/*
3677 * Return TRUE if line "lnum" is not close to a diff block, this line should
3678 * be in a fold.
3679 * Return FALSE if there are no diff blocks at all in this window.
3680 */
3681 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01003682diff_infold(win_T *wp, linenr_T lnum)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003683{
3684 int i;
3685 int idx = -1;
3686 int other = FALSE;
3687 diff_T *dp;
3688
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003689 // Return if 'diff' isn't set.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003690 if (!wp->w_p_diff)
3691 return FALSE;
3692
3693 for (i = 0; i < DB_COUNT; ++i)
3694 {
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003695 if (curtab->tp_diffbuf[i] == wp->w_buffer)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003696 idx = i;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003697 else if (curtab->tp_diffbuf[i] != NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003698 other = TRUE;
3699 }
3700
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003701 // return here if there are no diffs in the window
Bram Moolenaar071d4272004-06-13 20:20:40 +00003702 if (idx == -1 || !other)
3703 return FALSE;
3704
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003705 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003706 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00003707
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003708 // Return if there are no diff blocks. All lines will be folded.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003709 if (curtab->tp_first_diff == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003710 return TRUE;
3711
Bram Moolenaaraeea7212020-04-02 18:50:46 +02003712 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003713 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003714 // If this change is below the line there can't be any further match.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003715 if (dp->df_lnum[idx] - diff_context > lnum)
3716 break;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003717 // If this change ends before the line we have a match.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003718 if (dp->df_lnum[idx] + dp->df_count[idx] + diff_context > lnum)
3719 return FALSE;
3720 }
3721 return TRUE;
3722}
3723#endif
3724
3725/*
3726 * "dp" and "do" commands.
3727 */
3728 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01003729nv_diffgetput(int put, long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003730{
3731 exarg_T ea;
Bram Moolenaar6a643652014-10-31 13:54:25 +01003732 char_u buf[30];
Bram Moolenaar071d4272004-06-13 20:20:40 +00003733
Bram Moolenaarf2732452018-06-03 14:47:35 +02003734#ifdef FEAT_JOB_CHANNEL
3735 if (bt_prompt(curbuf))
3736 {
3737 vim_beep(BO_OPER);
3738 return;
3739 }
3740#endif
Bram Moolenaar6a643652014-10-31 13:54:25 +01003741 if (count == 0)
3742 ea.arg = (char_u *)"";
3743 else
3744 {
3745 vim_snprintf((char *)buf, 30, "%ld", count);
3746 ea.arg = buf;
3747 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003748 if (put)
3749 ea.cmdidx = CMD_diffput;
3750 else
3751 ea.cmdidx = CMD_diffget;
3752 ea.addr_count = 0;
3753 ea.line1 = curwin->w_cursor.lnum;
3754 ea.line2 = curwin->w_cursor.lnum;
3755 ex_diffgetput(&ea);
3756}
3757
3758/*
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01003759 * Return TRUE if "diff" appears in the list of diff blocks of the current tab.
3760 */
3761 static int
3762valid_diff(diff_T *diff)
3763{
3764 diff_T *dp;
3765
Yegappan Lakshmanan14113fd2023-03-07 17:13:51 +00003766 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01003767 if (dp == diff)
3768 return TRUE;
3769 return FALSE;
3770}
3771
3772/*
Bram Moolenaar071d4272004-06-13 20:20:40 +00003773 * ":diffget"
3774 * ":diffput"
3775 */
3776 void
Bram Moolenaar7454a062016-01-30 15:14:10 +01003777ex_diffgetput(exarg_T *eap)
Bram Moolenaar071d4272004-06-13 20:20:40 +00003778{
3779 linenr_T lnum;
3780 int count;
3781 linenr_T off = 0;
3782 diff_T *dp;
3783 diff_T *dprev;
3784 diff_T *dfree;
3785 int idx_cur;
3786 int idx_other;
3787 int idx_from;
3788 int idx_to;
3789 int i;
3790 int added;
3791 char_u *p;
3792 aco_save_T aco;
3793 buf_T *buf;
3794 int start_skip, end_skip;
3795 int new_count;
Bram Moolenaar280f1262006-01-30 00:14:18 +00003796 int buf_empty;
Bram Moolenaar602eb742007-02-20 03:43:38 +00003797 int found_not_ma = FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003798
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003799 // Find the current buffer in the list of diff buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003800 idx_cur = diff_buf_idx(curbuf);
3801 if (idx_cur == DB_COUNT)
3802 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003803 emsg(_(e_current_buffer_is_not_in_diff_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003804 return;
3805 }
3806
3807 if (*eap->arg == NUL)
3808 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003809 // No argument: Find the other buffer in the list of diff buffers.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003810 for (idx_other = 0; idx_other < DB_COUNT; ++idx_other)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003811 if (curtab->tp_diffbuf[idx_other] != curbuf
Bram Moolenaar602eb742007-02-20 03:43:38 +00003812 && curtab->tp_diffbuf[idx_other] != NULL)
3813 {
3814 if (eap->cmdidx != CMD_diffput
3815 || curtab->tp_diffbuf[idx_other]->b_p_ma)
3816 break;
3817 found_not_ma = TRUE;
3818 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003819 if (idx_other == DB_COUNT)
3820 {
Bram Moolenaar602eb742007-02-20 03:43:38 +00003821 if (found_not_ma)
Bram Moolenaar677658a2022-01-05 16:09:06 +00003822 emsg(_(e_no_other_buffer_in_diff_mode_is_modifiable));
Bram Moolenaar602eb742007-02-20 03:43:38 +00003823 else
Bram Moolenaare1242042021-12-16 20:56:57 +00003824 emsg(_(e_no_other_buffer_in_diff_mode));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003825 return;
3826 }
3827
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003828 // Check that there isn't a third buffer in the list
Bram Moolenaar071d4272004-06-13 20:20:40 +00003829 for (i = idx_other + 1; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003830 if (curtab->tp_diffbuf[i] != curbuf
3831 && curtab->tp_diffbuf[i] != NULL
3832 && (eap->cmdidx != CMD_diffput || curtab->tp_diffbuf[i]->b_p_ma))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003833 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003834 emsg(_(e_more_than_two_buffers_in_diff_mode_dont_know_which_one_to_use));
Bram Moolenaar071d4272004-06-13 20:20:40 +00003835 return;
3836 }
3837 }
3838 else
3839 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003840 // Buffer number or pattern given. Ignore trailing white space.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003841 p = eap->arg + STRLEN(eap->arg);
Bram Moolenaar1c465442017-03-12 20:10:05 +01003842 while (p > eap->arg && VIM_ISWHITE(p[-1]))
Bram Moolenaar071d4272004-06-13 20:20:40 +00003843 --p;
3844 for (i = 0; vim_isdigit(eap->arg[i]) && eap->arg + i < p; ++i)
3845 ;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003846 if (eap->arg + i == p) // digits only
Bram Moolenaar071d4272004-06-13 20:20:40 +00003847 i = atol((char *)eap->arg);
3848 else
3849 {
Bram Moolenaar0c279bb2013-03-19 14:25:54 +01003850 i = buflist_findpat(eap->arg, p, FALSE, TRUE, FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003851 if (i < 0)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003852 return; // error message already given
Bram Moolenaar071d4272004-06-13 20:20:40 +00003853 }
3854 buf = buflist_findnr(i);
3855 if (buf == NULL)
3856 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003857 semsg(_(e_cant_find_buffer_str), eap->arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003858 return;
3859 }
Bram Moolenaar5cc6a6e2009-01-22 19:48:55 +00003860 if (buf == curbuf)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003861 return; // nothing to do
Bram Moolenaar071d4272004-06-13 20:20:40 +00003862 idx_other = diff_buf_idx(buf);
3863 if (idx_other == DB_COUNT)
3864 {
Bram Moolenaare1242042021-12-16 20:56:57 +00003865 semsg(_(e_buffer_str_is_not_in_diff_mode), eap->arg);
Bram Moolenaar071d4272004-06-13 20:20:40 +00003866 return;
3867 }
3868 }
3869
3870 diff_busy = TRUE;
3871
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003872 // When no range given include the line above or below the cursor.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003873 if (eap->addr_count == 0)
3874 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003875 // Make it possible that ":diffget" on the last line gets line below
3876 // the cursor line when there is no difference above the cursor.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003877 if (eap->cmdidx == CMD_diffget
3878 && eap->line1 == curbuf->b_ml.ml_line_count
3879 && diff_check(curwin, eap->line1) == 0
3880 && (eap->line1 == 1 || diff_check(curwin, eap->line1 - 1) == 0))
3881 ++eap->line2;
3882 else if (eap->line1 > 0)
3883 --eap->line1;
3884 }
3885
3886 if (eap->cmdidx == CMD_diffget)
3887 {
3888 idx_from = idx_other;
3889 idx_to = idx_cur;
3890 }
3891 else
3892 {
3893 idx_from = idx_cur;
3894 idx_to = idx_other;
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003895 // Need to make the other buffer the current buffer to be able to make
3896 // changes in it.
Bram Moolenaare76062c2022-11-28 18:51:43 +00003897 // Set curwin/curbuf to buf and save a few things.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003898 aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]);
Bram Moolenaare76062c2022-11-28 18:51:43 +00003899 if (curbuf != curtab->tp_diffbuf[idx_other])
3900 // Could not find a window for this buffer, the rest is likely to
3901 // fail.
3902 goto theend;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003903 }
3904
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003905 // May give the warning for a changed buffer here, which can trigger the
3906 // FileChangedRO autocommand, which may do nasty things and mess
3907 // everything up.
Bram Moolenaar910f66f2006-04-05 20:41:53 +00003908 if (!curbuf->b_changed)
3909 {
3910 change_warning(0);
3911 if (diff_buf_idx(curbuf) != idx_to)
3912 {
Bram Moolenaar677658a2022-01-05 16:09:06 +00003913 emsg(_(e_buffer_changed_unexpectedly));
Bram Moolenaard2b58c02018-09-16 18:10:48 +02003914 goto theend;
Bram Moolenaar910f66f2006-04-05 20:41:53 +00003915 }
3916 }
3917
Bram Moolenaar071d4272004-06-13 20:20:40 +00003918 dprev = NULL;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00003919 for (dp = curtab->tp_first_diff; dp != NULL; )
Bram Moolenaar071d4272004-06-13 20:20:40 +00003920 {
Jonathon7c7a4e62025-01-12 09:58:00 +01003921 if (!eap->addr_count)
3922 {
3923 // handle the case with adjacent diff blocks
3924 while (dp->is_linematched
3925 && dp->df_next
3926 && dp->df_next->df_lnum[idx_cur] == dp->df_lnum[idx_cur] +
3927 dp->df_count[idx_cur]
3928 && dp->df_next->df_lnum[idx_cur] == eap->line1 + off + 1)
3929 {
3930 dprev = dp;
3931 dp = dp->df_next;
3932 }
3933 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00003934 if (dp->df_lnum[idx_cur] > eap->line2 + off)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003935 break; // past the range that was specified
Bram Moolenaar071d4272004-06-13 20:20:40 +00003936
3937 dfree = NULL;
3938 lnum = dp->df_lnum[idx_to];
3939 count = dp->df_count[idx_to];
3940 if (dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off
3941 && u_save(lnum - 1, lnum + count) != FAIL)
3942 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003943 // Inside the specified range and saving for undo worked.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003944 start_skip = 0;
3945 end_skip = 0;
3946 if (eap->addr_count > 0)
3947 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003948 // A range was specified: check if lines need to be skipped.
Bram Moolenaar071d4272004-06-13 20:20:40 +00003949 start_skip = eap->line1 + off - dp->df_lnum[idx_cur];
3950 if (start_skip > 0)
3951 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003952 // range starts below start of current diff block
Bram Moolenaar071d4272004-06-13 20:20:40 +00003953 if (start_skip > count)
3954 {
3955 lnum += count;
3956 count = 0;
3957 }
3958 else
3959 {
3960 count -= start_skip;
3961 lnum += start_skip;
3962 }
3963 }
3964 else
3965 start_skip = 0;
3966
3967 end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1
3968 - (eap->line2 + off);
3969 if (end_skip > 0)
3970 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003971 // range ends above end of current/from diff block
3972 if (idx_cur == idx_from) // :diffput
Bram Moolenaar071d4272004-06-13 20:20:40 +00003973 {
3974 i = dp->df_count[idx_cur] - start_skip - end_skip;
3975 if (count > i)
3976 count = i;
3977 }
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003978 else // :diffget
Bram Moolenaar071d4272004-06-13 20:20:40 +00003979 {
3980 count -= end_skip;
3981 end_skip = dp->df_count[idx_from] - start_skip - count;
3982 if (end_skip < 0)
3983 end_skip = 0;
3984 }
3985 }
3986 else
3987 end_skip = 0;
3988 }
3989
Bram Moolenaarb5aedf32017-03-12 18:23:53 +01003990 buf_empty = BUFEMPTY();
Bram Moolenaar071d4272004-06-13 20:20:40 +00003991 added = 0;
3992 for (i = 0; i < count; ++i)
3993 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01003994 // remember deleting the last line of the buffer
Bram Moolenaar280f1262006-01-30 00:14:18 +00003995 buf_empty = curbuf->b_ml.ml_line_count == 1;
Bram Moolenaar4e677b92022-07-28 18:44:27 +01003996 if (ml_delete(lnum) == OK)
3997 --added;
Bram Moolenaar071d4272004-06-13 20:20:40 +00003998 }
3999 for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i)
4000 {
4001 linenr_T nr;
4002
4003 nr = dp->df_lnum[idx_from] + start_skip + i;
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004004 if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004005 break;
Bram Moolenaar910f66f2006-04-05 20:41:53 +00004006 p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from],
4007 nr, FALSE));
Bram Moolenaar071d4272004-06-13 20:20:40 +00004008 if (p != NULL)
4009 {
4010 ml_append(lnum + i - 1, p, 0, FALSE);
4011 vim_free(p);
4012 ++added;
Bram Moolenaar280f1262006-01-30 00:14:18 +00004013 if (buf_empty && curbuf->b_ml.ml_line_count == 2)
4014 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004015 // Added the first line into an empty buffer, need to
4016 // delete the dummy empty line.
Bram Moolenaar280f1262006-01-30 00:14:18 +00004017 buf_empty = FALSE;
Bram Moolenaarca70c072020-05-30 20:30:46 +02004018 ml_delete((linenr_T)2);
Bram Moolenaar280f1262006-01-30 00:14:18 +00004019 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004020 }
4021 }
4022 new_count = dp->df_count[idx_to] + added;
4023 dp->df_count[idx_to] = new_count;
4024
4025 if (start_skip == 0 && end_skip == 0)
4026 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004027 // Check if there are any other buffers and if the diff is
4028 // equal in them.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004029 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar910f66f2006-04-05 20:41:53 +00004030 if (curtab->tp_diffbuf[i] != NULL && i != idx_from
4031 && i != idx_to
Bram Moolenaar071d4272004-06-13 20:20:40 +00004032 && !diff_equal_entry(dp, idx_from, i))
4033 break;
4034 if (i == DB_COUNT)
4035 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004036 // delete the diff entry, the buffers are now equal here
Bram Moolenaar071d4272004-06-13 20:20:40 +00004037 dfree = dp;
4038 dp = dp->df_next;
4039 if (dprev == NULL)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004040 curtab->tp_first_diff = dp;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004041 else
4042 dprev->df_next = dp;
4043 }
4044 }
4045
Bram Moolenaar071d4272004-06-13 20:20:40 +00004046 if (added != 0)
4047 {
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01004048 // Adjust marks. This will change the following entries!
Bram Moolenaar071d4272004-06-13 20:20:40 +00004049 mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added);
4050 if (curwin->w_cursor.lnum >= lnum)
4051 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004052 // Adjust the cursor position if it's in/after the changed
4053 // lines.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004054 if (curwin->w_cursor.lnum >= lnum + count)
4055 curwin->w_cursor.lnum += added;
4056 else if (added < 0)
4057 curwin->w_cursor.lnum = lnum;
4058 }
4059 }
4060 changed_lines(lnum, 0, lnum + count, (long)added);
4061
4062 if (dfree != NULL)
4063 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004064 // Diff is deleted, update folds in other windows.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004065#ifdef FEAT_FOLDING
4066 diff_fold_update(dfree, idx_to);
4067#endif
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004068 clear_diffblock(dfree);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004069 }
Bram Moolenaarc5274dd2022-07-02 15:10:00 +01004070
4071 // mark_adjust() may have made "dp" invalid. We don't know where
4072 // to continue then, bail out.
4073 if (added != 0 && !valid_diff(dp))
4074 break;
4075
4076 if (dfree == NULL)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004077 // mark_adjust() may have changed the count in a wrong way
Bram Moolenaar071d4272004-06-13 20:20:40 +00004078 dp->df_count[idx_to] = new_count;
4079
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004080 // When changing the current buffer, keep track of line numbers
Bram Moolenaar071d4272004-06-13 20:20:40 +00004081 if (idx_cur == idx_to)
4082 off += added;
4083 }
4084
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004085 // If before the range or not deleted, go to next diff.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004086 if (dfree == NULL)
4087 {
4088 dprev = dp;
4089 dp = dp->df_next;
4090 }
4091 }
4092
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004093 // restore curwin/curbuf and a few other things
Bram Moolenaara9d52e32010-07-31 16:44:19 +02004094 if (eap->cmdidx != CMD_diffget)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004095 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004096 // Syncing undo only works for the current buffer, but we change
4097 // another buffer. Sync undo if the command was typed. This isn't
4098 // 100% right when ":diffput" is used in a function or mapping.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004099 if (KeyTyped)
Bram Moolenaar779b74b2006-04-10 14:55:34 +00004100 u_sync(FALSE);
Bram Moolenaar071d4272004-06-13 20:20:40 +00004101 aucmd_restbuf(&aco);
4102 }
4103
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004104theend:
Bram Moolenaar071d4272004-06-13 20:20:40 +00004105 diff_busy = FALSE;
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004106 if (diff_need_update)
Bram Moolenaard2b58c02018-09-16 18:10:48 +02004107 ex_diffupdate(NULL);
Bram Moolenaardf77cef2018-10-07 17:46:42 +02004108
Bram Moolenaar5f57bdc2018-10-25 17:52:23 +02004109 // Check that the cursor is on a valid character and update its
Bram Moolenaardf77cef2018-10-07 17:46:42 +02004110 // position. When there were filler lines the topline has become
4111 // invalid.
4112 check_cursor();
4113 changed_line_abv_curs();
4114
4115 if (diff_need_update)
4116 // redraw already done by ex_diffupdate()
4117 diff_need_update = FALSE;
Bram Moolenaar198fa062018-09-18 21:20:26 +02004118 else
4119 {
Bram Moolenaar198fa062018-09-18 21:20:26 +02004120 // Also need to redraw the other buffers.
4121 diff_redraw(FALSE);
4122 apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, FALSE, curbuf);
4123 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00004124}
4125
4126#ifdef FEAT_FOLDING
4127/*
4128 * Update folds for all diff buffers for entry "dp".
4129 * Skip buffer with index "skip_idx".
4130 * When there are no diffs, all folds are removed.
4131 */
4132 static void
Bram Moolenaar7454a062016-01-30 15:14:10 +01004133diff_fold_update(diff_T *dp, int skip_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004134{
4135 int i;
4136 win_T *wp;
4137
Bram Moolenaar29323592016-07-24 22:04:11 +02004138 FOR_ALL_WINDOWS(wp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004139 for (i = 0; i < DB_COUNT; ++i)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004140 if (curtab->tp_diffbuf[i] == wp->w_buffer && i != skip_idx)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004141 foldUpdate(wp, dp->df_lnum[i],
4142 dp->df_lnum[i] + dp->df_count[i]);
4143}
4144#endif
4145
4146/*
4147 * Return TRUE if buffer "buf" is in diff-mode.
4148 */
4149 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01004150diff_mode_buf(buf_T *buf)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004151{
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004152 tabpage_T *tp;
4153
Bram Moolenaar29323592016-07-24 22:04:11 +02004154 FOR_ALL_TABPAGES(tp)
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004155 if (diff_buf_idx_tp(buf, tp) != DB_COUNT)
4156 return TRUE;
4157 return FALSE;
Bram Moolenaar071d4272004-06-13 20:20:40 +00004158}
4159
4160/*
4161 * Move "count" times in direction "dir" to the next diff block.
4162 * Return FAIL if there isn't such a diff block.
4163 */
4164 int
Bram Moolenaar7454a062016-01-30 15:14:10 +01004165diff_move_to(int dir, long count)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004166{
4167 int idx;
4168 linenr_T lnum = curwin->w_cursor.lnum;
4169 diff_T *dp;
4170
4171 idx = diff_buf_idx(curbuf);
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004172 if (idx == DB_COUNT || curtab->tp_first_diff == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004173 return FAIL;
4174
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004175 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004176 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00004177
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004178 if (curtab->tp_first_diff == NULL) // no diffs today
Bram Moolenaar071d4272004-06-13 20:20:40 +00004179 return FAIL;
4180
4181 while (--count >= 0)
4182 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004183 // Check if already before first diff.
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004184 if (dir == BACKWARD && lnum <= curtab->tp_first_diff->df_lnum[idx])
Bram Moolenaar071d4272004-06-13 20:20:40 +00004185 break;
4186
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004187 for (dp = curtab->tp_first_diff; ; dp = dp->df_next)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004188 {
4189 if (dp == NULL)
4190 break;
4191 if ((dir == FORWARD && lnum < dp->df_lnum[idx])
4192 || (dir == BACKWARD
4193 && (dp->df_next == NULL
4194 || lnum <= dp->df_next->df_lnum[idx])))
4195 {
4196 lnum = dp->df_lnum[idx];
4197 break;
4198 }
4199 }
4200 }
4201
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004202 // don't end up past the end of the file
Bram Moolenaar071d4272004-06-13 20:20:40 +00004203 if (lnum > curbuf->b_ml.ml_line_count)
4204 lnum = curbuf->b_ml.ml_line_count;
4205
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004206 // When the cursor didn't move at all we fail.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004207 if (lnum == curwin->w_cursor.lnum)
4208 return FAIL;
4209
4210 setpcmark();
4211 curwin->w_cursor.lnum = lnum;
4212 curwin->w_cursor.col = 0;
4213
4214 return OK;
4215}
4216
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004217/*
4218 * Return the line number in the current window that is closest to "lnum1" in
4219 * "buf1" in diff mode.
4220 */
4221 static linenr_T
4222diff_get_corresponding_line_int(
Bram Moolenaar7454a062016-01-30 15:14:10 +01004223 buf_T *buf1,
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004224 linenr_T lnum1)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004225{
4226 int idx1;
4227 int idx2;
4228 diff_T *dp;
4229 int baseline = 0;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004230
4231 idx1 = diff_buf_idx(buf1);
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004232 idx2 = diff_buf_idx(curbuf);
Bram Moolenaar860cae12010-06-05 23:22:07 +02004233 if (idx1 == DB_COUNT || idx2 == DB_COUNT || curtab->tp_first_diff == NULL)
4234 return lnum1;
4235
4236 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004237 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar860cae12010-06-05 23:22:07 +02004238
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004239 if (curtab->tp_first_diff == NULL) // no diffs today
Bram Moolenaar860cae12010-06-05 23:22:07 +02004240 return lnum1;
4241
Bram Moolenaaraeea7212020-04-02 18:50:46 +02004242 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004243 {
4244 if (dp->df_lnum[idx1] > lnum1)
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004245 return lnum1 - baseline;
4246 if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1)
Bram Moolenaar860cae12010-06-05 23:22:07 +02004247 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004248 // Inside the diffblock
Bram Moolenaar860cae12010-06-05 23:22:07 +02004249 baseline = lnum1 - dp->df_lnum[idx1];
4250 if (baseline > dp->df_count[idx2])
4251 baseline = dp->df_count[idx2];
4252
4253 return dp->df_lnum[idx2] + baseline;
4254 }
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004255 if ( (dp->df_lnum[idx1] == lnum1)
4256 && (dp->df_count[idx1] == 0)
4257 && (dp->df_lnum[idx2] <= curwin->w_cursor.lnum)
4258 && ((dp->df_lnum[idx2] + dp->df_count[idx2])
4259 > curwin->w_cursor.lnum))
Bram Moolenaar860cae12010-06-05 23:22:07 +02004260 /*
4261 * Special case: if the cursor is just after a zero-count
4262 * block (i.e. all filler) and the target cursor is already
4263 * inside the corresponding block, leave the target cursor
4264 * unmoved. This makes repeated CTRL-W W operations work
4265 * as expected.
4266 */
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004267 return curwin->w_cursor.lnum;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004268 baseline = (dp->df_lnum[idx1] + dp->df_count[idx1])
4269 - (dp->df_lnum[idx2] + dp->df_count[idx2]);
4270 }
4271
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004272 // If we get here then the cursor is after the last diff
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004273 return lnum1 - baseline;
4274}
Bram Moolenaar860cae12010-06-05 23:22:07 +02004275
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004276/*
4277 * Return the line number in the current window that is closest to "lnum1" in
4278 * "buf1" in diff mode. Checks the line number to be valid.
4279 */
4280 linenr_T
4281diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1)
4282{
4283 linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1);
4284
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004285 // don't end up past the end of the file
Bram Moolenaar025e3e02016-10-18 14:50:18 +02004286 if (lnum > curbuf->b_ml.ml_line_count)
4287 return curbuf->b_ml.ml_line_count;
4288 return lnum;
Bram Moolenaar860cae12010-06-05 23:22:07 +02004289}
Bram Moolenaar860cae12010-06-05 23:22:07 +02004290
Bram Moolenaar071d4272004-06-13 20:20:40 +00004291/*
4292 * For line "lnum" in the current window find the equivalent lnum in window
4293 * "wp", compensating for inserted/deleted lines.
4294 */
4295 linenr_T
Bram Moolenaar7454a062016-01-30 15:14:10 +01004296diff_lnum_win(linenr_T lnum, win_T *wp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004297{
4298 diff_T *dp;
4299 int idx;
4300 int i;
4301 linenr_T n;
4302
4303 idx = diff_buf_idx(curbuf);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004304 if (idx == DB_COUNT) // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00004305 return (linenr_T)0;
4306
Bram Moolenaar49d7bf12006-02-17 21:45:41 +00004307 if (curtab->tp_diff_invalid)
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004308 ex_diffupdate(NULL); // update after a big change
Bram Moolenaar071d4272004-06-13 20:20:40 +00004309
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004310 // search for a change that includes "lnum" in the list of diffblocks.
Bram Moolenaaraeea7212020-04-02 18:50:46 +02004311 FOR_ALL_DIFFBLOCKS_IN_TAB(curtab, dp)
Bram Moolenaar071d4272004-06-13 20:20:40 +00004312 if (lnum <= dp->df_lnum[idx] + dp->df_count[idx])
4313 break;
4314
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004315 // When after the last change, compute relative to the last line number.
Bram Moolenaar071d4272004-06-13 20:20:40 +00004316 if (dp == NULL)
4317 return wp->w_buffer->b_ml.ml_line_count
4318 - (curbuf->b_ml.ml_line_count - lnum);
4319
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004320 // Find index for "wp".
Bram Moolenaar071d4272004-06-13 20:20:40 +00004321 i = diff_buf_idx(wp->w_buffer);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004322 if (i == DB_COUNT) // safety check
Bram Moolenaar071d4272004-06-13 20:20:40 +00004323 return (linenr_T)0;
4324
4325 n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]);
4326 if (n > dp->df_lnum[i] + dp->df_count[i])
4327 n = dp->df_lnum[i] + dp->df_count[i];
4328 return n;
4329}
Bram Moolenaar071d4272004-06-13 20:20:40 +00004330
Bram Moolenaare828b762018-09-10 17:51:58 +02004331/*
4332 * Handle an ED style diff line.
4333 * Return FAIL if the line does not contain diff info.
4334 */
4335 static int
4336parse_diff_ed(
4337 char_u *line,
Lewis Russelld9da86e2021-12-28 13:54:41 +00004338 diffhunk_T *hunk)
Bram Moolenaare828b762018-09-10 17:51:58 +02004339{
4340 char_u *p;
4341 long f1, l1, f2, l2;
4342 int difftype;
4343
4344 // The line must be one of three formats:
4345 // change: {first}[,{last}]c{first}[,{last}]
4346 // append: {first}a{first}[,{last}]
4347 // delete: {first}[,{last}]d{first}
4348 p = line;
4349 f1 = getdigits(&p);
4350 if (*p == ',')
4351 {
4352 ++p;
4353 l1 = getdigits(&p);
4354 }
4355 else
4356 l1 = f1;
4357 if (*p != 'a' && *p != 'c' && *p != 'd')
4358 return FAIL; // invalid diff format
4359 difftype = *p++;
4360 f2 = getdigits(&p);
4361 if (*p == ',')
4362 {
4363 ++p;
4364 l2 = getdigits(&p);
4365 }
4366 else
4367 l2 = f2;
4368 if (l1 < f1 || l2 < f2)
4369 return FAIL;
4370
4371 if (difftype == 'a')
4372 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004373 hunk->lnum_orig = f1 + 1;
4374 hunk->count_orig = 0;
Bram Moolenaare828b762018-09-10 17:51:58 +02004375 }
4376 else
4377 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004378 hunk->lnum_orig = f1;
4379 hunk->count_orig = l1 - f1 + 1;
Bram Moolenaare828b762018-09-10 17:51:58 +02004380 }
4381 if (difftype == 'd')
4382 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004383 hunk->lnum_new = f2 + 1;
4384 hunk->count_new = 0;
Bram Moolenaare828b762018-09-10 17:51:58 +02004385 }
4386 else
4387 {
Lewis Russelld9da86e2021-12-28 13:54:41 +00004388 hunk->lnum_new = f2;
4389 hunk->count_new = l2 - f2 + 1;
Bram Moolenaare828b762018-09-10 17:51:58 +02004390 }
4391 return OK;
4392}
4393
4394/*
4395 * Parses unified diff with zero(!) context lines.
4396 * Return FAIL if there is no diff information in "line".
4397 */
4398 static int
4399parse_diff_unified(
4400 char_u *line,
Lewis Russelld9da86e2021-12-28 13:54:41 +00004401 diffhunk_T *hunk)
Bram Moolenaare828b762018-09-10 17:51:58 +02004402{
4403 char_u *p;
4404 long oldline, oldcount, newline, newcount;
4405
4406 // Parse unified diff hunk header:
4407 // @@ -oldline,oldcount +newline,newcount @@
4408 p = line;
4409 if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-')
4410 {
4411 oldline = getdigits(&p);
4412 if (*p == ',')
4413 {
4414 ++p;
4415 oldcount = getdigits(&p);
4416 }
4417 else
4418 oldcount = 1;
4419 if (*p++ == ' ' && *p++ == '+')
4420 {
4421 newline = getdigits(&p);
4422 if (*p == ',')
4423 {
4424 ++p;
4425 newcount = getdigits(&p);
4426 }
4427 else
4428 newcount = 1;
4429 }
4430 else
4431 return FAIL; // invalid diff format
4432
4433 if (oldcount == 0)
4434 oldline += 1;
4435 if (newcount == 0)
4436 newline += 1;
4437 if (newline == 0)
4438 newline = 1;
4439
Lewis Russelld9da86e2021-12-28 13:54:41 +00004440 hunk->lnum_orig = oldline;
4441 hunk->count_orig = oldcount;
4442 hunk->lnum_new = newline;
4443 hunk->count_new = newcount;
Bram Moolenaare828b762018-09-10 17:51:58 +02004444
4445 return OK;
4446 }
4447
4448 return FAIL;
4449}
4450
4451/*
4452 * Callback function for the xdl_diff() function.
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004453 * Stores the diff output (indices) in a grow array.
Bram Moolenaare828b762018-09-10 17:51:58 +02004454 */
4455 static int
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004456xdiff_out_indices(
Lewis Russelld9da86e2021-12-28 13:54:41 +00004457 long start_a,
4458 long count_a,
4459 long start_b,
4460 long count_b,
4461 void *priv)
Bram Moolenaare828b762018-09-10 17:51:58 +02004462{
4463 diffout_T *dout = (diffout_T *)priv;
Lewis Russelld9da86e2021-12-28 13:54:41 +00004464 diffhunk_T *p = ALLOC_ONE(diffhunk_T);
Bram Moolenaare828b762018-09-10 17:51:58 +02004465
Lewis Russelld9da86e2021-12-28 13:54:41 +00004466 if (p == NULL)
4467 return -1;
Bram Moolenaarf080d702018-10-31 22:57:26 +01004468
4469 if (ga_grow(&dout->dout_ga, 1) == FAIL)
Bram Moolenaarfebb78f2021-12-29 11:59:53 +00004470 {
4471 vim_free(p);
Bram Moolenaarf080d702018-10-31 22:57:26 +01004472 return -1;
Bram Moolenaarfebb78f2021-12-29 11:59:53 +00004473 }
Lewis Russelld9da86e2021-12-28 13:54:41 +00004474
4475 p->lnum_orig = start_a + 1;
4476 p->count_orig = count_a;
4477 p->lnum_new = start_b + 1;
4478 p->count_new = count_b;
4479 ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
Bram Moolenaare828b762018-09-10 17:51:58 +02004480 return 0;
4481}
4482
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004483/*
4484 * Callback function for the xdl_diff() function.
4485 * Stores the unified diff output in a grow array.
4486 */
4487 static int
4488xdiff_out_unified(
4489 void *priv,
4490 mmbuffer_t *mb,
4491 int nbuf)
4492{
4493 diffout_T *dout = (diffout_T *)priv;
4494 int i;
4495
4496 for (i = 0; i < nbuf; i++)
4497 ga_concat_len(&dout->dout_ga, (char_u *)mb[i].ptr, mb[i].size);
4498
4499 return 0;
4500}
4501
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004502#endif // FEAT_DIFF
4503
4504#if defined(FEAT_EVAL) || defined(PROTO)
4505
4506/*
4507 * "diff_filler()" function
4508 */
4509 void
4510f_diff_filler(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4511{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004512# ifdef FEAT_DIFF
Yegappan Lakshmanan4490ec42021-07-27 22:00:44 +02004513 if (in_vim9script() && check_for_lnum_arg(argvars, 0) == FAIL)
4514 return;
4515
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004516 rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004517# endif
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004518}
4519
4520/*
4521 * "diff_hlID()" function
4522 */
4523 void
4524f_diff_hlID(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4525{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004526# ifdef FEAT_DIFF
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004527 linenr_T lnum;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004528 static linenr_T prev_lnum = 0;
4529 static varnumber_T changedtick = 0;
4530 static int fnum = 0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004531 static int prev_diff_flags = 0;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004532 static int change_start = 0;
4533 static int change_end = 0;
4534 static hlf_T hlID = (hlf_T)0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004535 int cache_results = TRUE;
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004536 int filler_lines;
4537 int col;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004538 diffline_T diffline;
4539
4540 CLEAR_FIELD(diffline);
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004541
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004542 if (in_vim9script()
Yegappan Lakshmanancd917202021-07-21 19:09:09 +02004543 && (check_for_lnum_arg(argvars,0) == FAIL
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004544 || check_for_number_arg(argvars, 1) == FAIL))
4545 return;
4546
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004547 if (diff_flags & ALL_INLINE_DIFF)
4548 {
4549 // Remember the results if using simple since it's recalculated per
4550 // call. Otherwise just call diff_find_change() every time since
4551 // internally the result is cached interally.
4552 cache_results = FALSE;
4553 }
4554
Yegappan Lakshmanana9a7c0c2021-07-17 19:11:07 +02004555 lnum = tv_get_lnum(argvars);
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004556 if (lnum < 0) // ignore type error in {lnum} arg
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004557 lnum = 0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004558 if (!cache_results
4559 || lnum != prev_lnum
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004560 || changedtick != CHANGEDTICK(curbuf)
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004561 || fnum != curbuf->b_fnum
4562 || diff_flags != prev_diff_flags)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004563 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004564 // New line, buffer, change: need to get the values.
Jonathon7c7a4e62025-01-12 09:58:00 +01004565 int linestatus = 0;
4566 filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus);
4567 if (filler_lines < 0 || linestatus < 0)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004568 {
Jonathon7c7a4e62025-01-12 09:58:00 +01004569 if (filler_lines == -1 || linestatus == -1)
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004570 {
4571 change_start = MAXCOL;
4572 change_end = -1;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004573 if (diff_find_change(curwin, lnum, &diffline))
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004574 hlID = HLF_ADD; // added line
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004575 else
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004576 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004577 hlID = HLF_CHD; // changed line
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004578 if (diffline.num_changes > 0 && cache_results)
4579 {
4580 change_start = diffline.changes[0].dc_start[diffline.bufidx];
4581 change_end = diffline.changes[0].dc_end[diffline.bufidx];
4582 }
4583 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004584 }
4585 else
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004586 hlID = HLF_ADD; // added line
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004587 }
4588 else
4589 hlID = (hlf_T)0;
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004590
4591 if (cache_results)
4592 {
4593 prev_lnum = lnum;
4594 changedtick = CHANGEDTICK(curbuf);
4595 fnum = curbuf->b_fnum;
4596 prev_diff_flags = diff_flags;
4597 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004598 }
4599
4600 if (hlID == HLF_CHD || hlID == HLF_TXD)
4601 {
Bram Moolenaar5d18efe2019-12-01 21:11:22 +01004602 col = tv_get_number(&argvars[1]) - 1; // ignore type error in {col}
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004603 if (cache_results)
4604 {
4605 if (col >= change_start && col < change_end)
4606 hlID = HLF_TXD; // changed text
4607 else
4608 hlID = HLF_CHD; // changed line
4609 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004610 else
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004611 {
4612 hlID = HLF_CHD;
4613 for (int i = 0; i < diffline.num_changes; i++)
4614 {
4615 int added = diff_change_parse(&diffline, &diffline.changes[i],
4616 &change_start, &change_end);
4617 if (col >= change_start && col < change_end)
4618 {
4619 hlID = added ? HLF_TXA : HLF_TXD;
4620 break;
4621 }
4622 if (col < change_start)
4623 // the remaining changes are past this column and not relevant
4624 break;
4625 }
4626 }
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004627 }
4628 rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID;
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004629# endif
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004630}
4631
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004632# ifdef FEAT_DIFF
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004633/*
4634 * Parse the diff options passed in "optarg" to the diff() function and return
4635 * the options in "diffopts" and the diff algorithm in "diffalgo".
4636 */
4637 static int
4638parse_diff_optarg(
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004639 typval_T *opts,
4640 int *diffopts,
4641 long *diffalgo,
4642 dio_outfmt_T *diff_output_fmt,
4643 int *diff_ctxlen)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004644{
4645 dict_T *d = opts->vval.v_dict;
4646
4647 char_u *algo = dict_get_string(d, "algorithm", FALSE);
4648 if (algo != NULL)
4649 {
4650 if (STRNCMP(algo, "myers", 5) == 0)
4651 *diffalgo = 0;
4652 else if (STRNCMP(algo, "minimal", 7) == 0)
4653 *diffalgo = XDF_NEED_MINIMAL;
4654 else if (STRNCMP(algo, "patience", 8) == 0)
4655 *diffalgo = XDF_PATIENCE_DIFF;
4656 else if (STRNCMP(algo, "histogram", 9) == 0)
4657 *diffalgo = XDF_HISTOGRAM_DIFF;
4658 }
4659
4660 char_u *output_fmt = dict_get_string(d, "output", FALSE);
4661 if (output_fmt != NULL)
4662 {
4663 if (STRNCMP(output_fmt, "unified", 7) == 0)
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004664 *diff_output_fmt = DIO_OUTPUT_UNIFIED;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004665 else if (STRNCMP(output_fmt, "indices", 7) == 0)
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004666 *diff_output_fmt = DIO_OUTPUT_INDICES;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004667 else
4668 {
4669 semsg(_(e_unsupported_diff_output_format_str), output_fmt);
4670 return FAIL;
4671 }
4672 }
4673
Yegappan Lakshmanana0010a12024-02-12 20:21:26 +01004674 *diff_ctxlen = dict_get_number_def(d, "context", 0);
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004675 if (*diff_ctxlen < 0)
Yegappan Lakshmanana0010a12024-02-12 20:21:26 +01004676 *diff_ctxlen = 0;
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004677
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004678 if (dict_get_bool(d, "iblank", FALSE))
4679 *diffopts |= DIFF_IBLANK;
4680 if (dict_get_bool(d, "icase", FALSE))
4681 *diffopts |= DIFF_ICASE;
4682 if (dict_get_bool(d, "iwhite", FALSE))
4683 *diffopts |= DIFF_IWHITE;
4684 if (dict_get_bool(d, "iwhiteall", FALSE))
4685 *diffopts |= DIFF_IWHITEALL;
4686 if (dict_get_bool(d, "iwhiteeol", FALSE))
4687 *diffopts |= DIFF_IWHITEEOL;
4688 if (dict_get_bool(d, "indent-heuristic", FALSE))
4689 *diffalgo |= XDF_INDENT_HEURISTIC;
4690
4691 return OK;
4692}
4693
4694/*
4695 * Concatenate the List of strings in "l" and store the result in
4696 * "din->din_mmfile.ptr" and the length in "din->din_mmfile.size".
4697 */
4698 static void
4699list_to_diffin(list_T *l, diffin_T *din, int icase)
4700{
4701 garray_T ga;
4702 listitem_T *li;
4703 char_u *str;
4704
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004705 ga_init2(&ga, 1, 2048);
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004706
4707 FOR_ALL_LIST_ITEMS(l, li)
4708 {
4709 str = tv_get_string(&li->li_tv);
4710 if (icase)
4711 {
4712 str = strlow_save(str);
4713 if (str == NULL)
4714 continue;
4715 }
4716 ga_concat(&ga, str);
Yee Cheng Chin9943d472025-03-26 19:41:02 +01004717 ga_append(&ga, NL);
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004718 if (icase)
4719 vim_free(str);
4720 }
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004721
4722 din->din_mmfile.ptr = (char *)ga.ga_data;
4723 din->din_mmfile.size = ga.ga_len;
4724}
4725
4726/*
4727 * Get the start and end indices from the diff "hunk".
4728 */
4729 static dict_T *
4730get_diff_hunk_indices(diffhunk_T *hunk)
4731{
4732 dict_T *hunk_dict;
4733
4734 hunk_dict = dict_alloc();
4735 if (hunk_dict == NULL)
4736 return NULL;
4737
4738 dict_add_number(hunk_dict, "from_idx", hunk->lnum_orig - 1);
4739 dict_add_number(hunk_dict, "from_count", hunk->count_orig);
4740 dict_add_number(hunk_dict, "to_idx", hunk->lnum_new - 1);
4741 dict_add_number(hunk_dict, "to_count", hunk->count_new);
4742
4743 return hunk_dict;
4744}
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004745# endif
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004746
4747/*
4748 * "diff()" function
4749 */
4750 void
4751f_diff(typval_T *argvars UNUSED, typval_T *rettv UNUSED)
4752{
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004753# ifdef FEAT_DIFF
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004754 diffio_T dio;
4755
4756 if (check_for_nonnull_list_arg(argvars, 0) == FAIL
4757 || check_for_nonnull_list_arg(argvars, 1) == FAIL
4758 || check_for_opt_nonnull_dict_arg(argvars, 2) == FAIL)
4759 return;
4760
4761 CLEAR_FIELD(dio);
4762 dio.dio_internal = TRUE;
4763 ga_init2(&dio.dio_diff.dout_ga, sizeof(char *), 1000);
4764
4765 list_T *orig_list = argvars[0].vval.v_list;
4766 list_T *new_list = argvars[1].vval.v_list;
4767
4768 // Save the 'diffopt' option value and restore it after getting the diff.
4769 int save_diff_flags = diff_flags;
4770 long save_diff_algorithm = diff_algorithm;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004771 diff_flags = DIFF_INTERNAL;
4772 diff_algorithm = 0;
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004773 dio.dio_outfmt = DIO_OUTPUT_UNIFIED;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004774 if (argvars[2].v_type != VAR_UNKNOWN)
4775 if (parse_diff_optarg(&argvars[2], &diff_flags, &diff_algorithm,
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004776 &dio.dio_outfmt, &dio.dio_ctxlen) == FAIL)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004777 return;
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004778
4779 // Concatenate the List of strings into a single string using newline
4780 // separator. Internal diff library expects a single string.
4781 list_to_diffin(orig_list, &dio.dio_orig, diff_flags & DIFF_ICASE);
4782 list_to_diffin(new_list, &dio.dio_new, diff_flags & DIFF_ICASE);
4783
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004784 // If 'diffexpr' is set, then the internal diff is not used. Set
4785 // 'diffexpr' to an empty string temporarily.
4786 int restore_diffexpr = FALSE;
4787 char_u cc = *p_dex;
4788 if (*p_dex != NUL)
4789 {
4790 restore_diffexpr = TRUE;
4791 *p_dex = NUL;
4792 }
4793
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004794 // Compute the diff
4795 int diff_status = diff_file(&dio);
4796
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004797 // restore 'diffexpr'
4798 if (restore_diffexpr)
4799 *p_dex = cc;
4800
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004801 if (diff_status == FAIL)
4802 goto done;
4803
4804 int hunk_idx = 0;
4805 dict_T *hunk_dict;
4806
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004807 if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004808 {
4809 if (rettv_list_alloc(rettv) != OK)
4810 goto done;
4811 list_T *l = rettv->vval.v_list;
4812
4813 // Process each diff hunk
4814 diffhunk_T *hunk = NULL;
4815 while (hunk_idx < dio.dio_diff.dout_ga.ga_len)
4816 {
4817 hunk = ((diffhunk_T **)dio.dio_diff.dout_ga.ga_data)[hunk_idx++];
4818
4819 hunk_dict = get_diff_hunk_indices(hunk);
4820 if (hunk_dict == NULL)
4821 goto done;
4822
4823 list_append_dict(l, hunk_dict);
4824 }
4825 }
4826 else
4827 {
4828 ga_append(&dio.dio_diff.dout_ga, NUL);
4829 rettv->v_type = VAR_STRING;
4830 rettv->vval.v_string =
4831 vim_strsave((char_u *)dio.dio_diff.dout_ga.ga_data);
4832 }
4833
4834done:
4835 clear_diffin(&dio.dio_new);
Yegappan Lakshmananbe156a32024-02-11 17:08:29 +01004836 if (dio.dio_outfmt == DIO_OUTPUT_INDICES)
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004837 clear_diffout(&dio.dio_diff);
4838 else
4839 ga_clear(&dio.dio_diff.dout_ga);
4840 clear_diffin(&dio.dio_orig);
4841 // Restore the 'diffopt' option value.
4842 diff_flags = save_diff_flags;
4843 diff_algorithm = save_diff_algorithm;
Yegappan Lakshmanan60937032024-02-03 17:41:54 +01004844# endif
Yegappan Lakshmananfa378352024-02-01 22:05:27 +01004845}
4846
Bram Moolenaaraf7645d2019-09-05 22:33:28 +02004847#endif