Merge branch '4642_fix_overflow'
[midnight-commander.git] / src / diffviewer / ydiff.c
blob40802da79c94214f42191a518c500b56f0a01a82
1 /*
2 File difference viewer
4 Copyright (C) 2007-2025
5 Free Software Foundation, Inc.
7 Written by:
8 Daniel Borca <dborca@yahoo.com>, 2007
9 Slava Zanko <slavazanko@gmail.com>, 2010, 2013
10 Andrew Borodin <aborodin@vmail.ru>, 2010-2022
11 Ilia Maslakov <il.smind@gmail.com>, 2010
13 This file is part of the Midnight Commander.
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <https://www.gnu.org/licenses/>.
29 #include <config.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stddef.h> // ptrdiff_t
34 #include <stdlib.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
39 #include "lib/global.h"
40 #include "lib/tty/tty.h"
41 #include "lib/tty/color.h"
42 #include "lib/tty/key.h"
43 #include "lib/skin.h" // EDITOR_NORMAL_COLOR
44 #include "lib/vfs/vfs.h"
45 #include "lib/util.h"
46 #include "lib/widget.h"
47 #include "lib/strutil.h"
48 #ifdef HAVE_CHARSET
49 # include "lib/charsets.h"
50 #endif
51 #include "lib/event.h" // mc_event_raise()
53 #include "src/filemanager/cmd.h" // edit_file_at_line()
54 #include "src/filemanager/panel.h"
55 #include "src/filemanager/layout.h" // Needed for get_current_index and get_other_panel
57 #include "src/execute.h" // toggle_subshell()
58 #include "src/keymap.h"
59 #include "src/setup.h"
60 #include "src/history.h"
61 #ifdef HAVE_CHARSET
62 # include "src/selcodepage.h"
63 #endif
65 #include "ydiff.h"
66 #include "internal.h"
68 /*** global variables ****************************************************************************/
70 /*** file scope macro definitions ****************************************************************/
72 #define FILE_READ_BUF 4096
73 #define FILE_FLAG_TEMP (1 << 0)
75 #define ADD_CH '+'
76 #define DEL_CH '-'
77 #define CHG_CH '*'
78 #define EQU_CH ' '
80 #define HDIFF_ENABLE 1
81 #define HDIFF_MINCTX 5
82 #define HDIFF_DEPTH 10
84 #define FILE_DIRTY(fs) \
85 do \
86 { \
87 (fs)->pos = 0; \
88 (fs)->len = 0; \
89 } \
90 while (0)
92 /*** file scope type declarations ****************************************************************/
94 typedef enum
96 FROM_LEFT_TO_RIGHT,
97 FROM_RIGHT_TO_LEFT
98 } action_direction_t;
100 /*** forward declarations (file scope functions) *************************************************/
102 /*** file scope variables ************************************************************************/
104 /* --------------------------------------------------------------------------------------------- */
105 /*** file scope functions ************************************************************************/
106 /* --------------------------------------------------------------------------------------------- */
108 static inline int
109 TAB_SKIP (int ts, int pos)
111 if (ts > 0 && ts < 9)
112 return ts - pos % ts;
113 else
114 return 8 - pos % 8;
117 /* --------------------------------------------------------------------------------------------- */
120 * Fill buffer by spaces
122 * @param buf buffer
123 * @param n number of spaces
124 * @param zero_terminate add a nul after @n spaces
126 static void
127 fill_by_space (char *buf, size_t n, gboolean zero_terminate)
129 memset (buf, ' ', n);
130 if (zero_terminate)
131 buf[n] = '\0';
134 /* --------------------------------------------------------------------------------------------- */
136 static gboolean
137 rewrite_backup_content (const vfs_path_t *from_file_name_vpath, const char *to_file_name)
139 FILE *backup_fd;
140 char *contents;
141 gsize length;
142 const char *from_file_name;
144 from_file_name = vfs_path_get_last_path_str (from_file_name_vpath);
145 if (!g_file_get_contents (from_file_name, &contents, &length, NULL))
146 return FALSE;
148 backup_fd = fopen (to_file_name, "w");
149 if (backup_fd == NULL)
151 g_free (contents);
152 return FALSE;
155 length = fwrite ((const void *) contents, length, 1, backup_fd);
157 fflush (backup_fd);
158 fclose (backup_fd);
159 g_free (contents);
160 return TRUE;
163 /* buffered I/O ************************************************************* */
166 * Try to open a temporary file.
167 * @note the name is not altered if this function fails
169 * @param[out] name address of a pointer to store the temporary name
170 * @return file descriptor on success, negative on error
173 static int
174 open_temp (void **name)
176 int fd;
177 vfs_path_t *diff_file_name = NULL;
179 fd = mc_mkstemps (&diff_file_name, "mcdiff", NULL);
180 if (fd == -1)
182 message (D_ERROR, MSG_ERROR, _ ("Cannot create temporary diff file\n%s"),
183 unix_error_string (errno));
184 return -1;
187 *name = vfs_path_free (diff_file_name, FALSE);
188 return fd;
191 /* --------------------------------------------------------------------------------------------- */
194 * Allocate file structure and associate file descriptor to it.
196 * @param fd file descriptor
197 * @return file structure
200 static FBUF *
201 dview_fdopen (int fd)
203 FBUF *fs;
205 if (fd < 0)
206 return NULL;
208 fs = g_try_malloc (sizeof (FBUF));
209 if (fs == NULL)
210 return NULL;
212 fs->buf = g_try_malloc (FILE_READ_BUF);
213 if (fs->buf == NULL)
215 g_free (fs);
216 return NULL;
219 fs->fd = fd;
220 FILE_DIRTY (fs);
221 fs->flags = 0;
222 fs->data = NULL;
224 return fs;
227 /* --------------------------------------------------------------------------------------------- */
230 * Free file structure without closing the file.
232 * @param fs file structure
233 * @return 0 on success, non-zero on error
236 static int
237 dview_ffree (FBUF *fs)
239 int rv = 0;
241 if ((fs->flags & FILE_FLAG_TEMP) != 0)
243 rv = unlink (fs->data);
244 g_free (fs->data);
246 g_free (fs->buf);
247 g_free (fs);
248 return rv;
251 /* --------------------------------------------------------------------------------------------- */
254 * Open a binary temporary file in R/W mode.
255 * @note the file will be deleted when closed
257 * @return file structure
259 static FBUF *
260 dview_ftemp (void)
262 int fd;
263 FBUF *fs;
265 fs = dview_fdopen (0);
266 if (fs == NULL)
267 return NULL;
269 fd = open_temp (&fs->data);
270 if (fd < 0)
272 dview_ffree (fs);
273 return NULL;
276 fs->fd = fd;
277 fs->flags = FILE_FLAG_TEMP;
278 return fs;
281 /* --------------------------------------------------------------------------------------------- */
284 * Open a binary file in specified mode.
286 * @param filename file name
287 * @param flags open mode, a combination of O_RDONLY, O_WRONLY, O_RDWR
289 * @return file structure
292 static FBUF *
293 dview_fopen (const char *filename, int flags)
295 int fd;
296 FBUF *fs;
298 fs = dview_fdopen (0);
299 if (fs == NULL)
300 return NULL;
302 fd = open (filename, flags);
303 if (fd < 0)
305 dview_ffree (fs);
306 return NULL;
309 fs->fd = fd;
310 return fs;
313 /* --------------------------------------------------------------------------------------------- */
316 * Read a line of bytes from file until newline or EOF.
317 * @note does not stop on null-byte
318 * @note buf will not be null-terminated
320 * @param buf destination buffer
321 * @param size size of buffer
322 * @param fs file structure
324 * @return number of bytes read
327 static size_t
328 dview_fgets (char *buf, size_t size, FBUF *fs)
330 size_t j = 0;
334 int i;
335 gboolean stop = FALSE;
337 for (i = fs->pos; j < size && i < fs->len && !stop; i++, j++)
339 buf[j] = fs->buf[i];
340 if (buf[j] == '\n')
341 stop = TRUE;
343 fs->pos = i;
345 if (j == size || stop)
346 break;
348 fs->pos = 0;
349 fs->len = read (fs->fd, fs->buf, FILE_READ_BUF);
351 while (fs->len > 0);
353 return j;
356 /* --------------------------------------------------------------------------------------------- */
359 * Seek into file.
360 * @note avoids thrashing read cache when possible
362 * @param fs file structure
363 * @param off offset
364 * @param whence seek directive: SEEK_SET, SEEK_CUR or SEEK_END
366 * @return position in file, starting from beginning
369 static off_t
370 dview_fseek (FBUF *fs, off_t off, int whence)
372 off_t rv;
374 if (fs->len != 0 && whence != SEEK_END)
376 rv = lseek (fs->fd, 0, SEEK_CUR);
377 if (rv != -1)
379 if (whence == SEEK_CUR)
381 whence = SEEK_SET;
382 off += rv - fs->len + fs->pos;
384 if (off - rv >= -fs->len && off - rv <= 0)
386 fs->pos = fs->len + off - rv;
387 return off;
392 rv = lseek (fs->fd, off, whence);
393 if (rv != -1)
394 FILE_DIRTY (fs);
395 return rv;
398 /* --------------------------------------------------------------------------------------------- */
401 * Seek to the beginning of file, thrashing read cache.
403 * @param fs file structure
405 * @return 0 if success, non-zero on error
408 static off_t
409 dview_freset (FBUF *fs)
411 off_t rv;
413 rv = lseek (fs->fd, 0, SEEK_SET);
414 if (rv != -1)
415 FILE_DIRTY (fs);
416 return rv;
419 /* --------------------------------------------------------------------------------------------- */
422 * Write bytes to file.
423 * @note thrashes read cache
425 * @param fs file structure
426 * @param buf source buffer
427 * @param size size of buffer
429 * @return number of written bytes, -1 on error
432 static ssize_t
433 dview_fwrite (FBUF *fs, const char *buf, size_t size)
435 ssize_t rv;
437 rv = write (fs->fd, buf, size);
438 if (rv >= 0)
439 FILE_DIRTY (fs);
440 return rv;
443 /* --------------------------------------------------------------------------------------------- */
446 * Truncate file to the current position.
447 * @note thrashes read cache
449 * @param fs file structure
451 * @return current file size on success, negative on error
454 static off_t
455 dview_ftrunc (FBUF *fs)
457 off_t off;
459 off = lseek (fs->fd, 0, SEEK_CUR);
460 if (off != -1)
462 int rv;
464 rv = ftruncate (fs->fd, off);
465 if (rv != 0)
466 off = -1;
467 else
468 FILE_DIRTY (fs);
470 return off;
473 /* --------------------------------------------------------------------------------------------- */
476 * Close file.
477 * @note if this is temporary file, it is deleted
479 * @param fs file structure
480 * @return 0 on success, non-zero on error
483 static int
484 dview_fclose (FBUF *fs)
486 int rv = -1;
488 if (fs != NULL)
490 rv = close (fs->fd);
491 dview_ffree (fs);
494 return rv;
497 /* --------------------------------------------------------------------------------------------- */
500 * Create pipe stream to process.
502 * @param cmd shell command line
503 * @param flags open mode, either O_RDONLY or O_WRONLY
505 * @return file structure
508 static FBUF *
509 dview_popen (const char *cmd, int flags)
511 FILE *f;
512 FBUF *fs;
513 const char *type = NULL;
515 if (flags == O_RDONLY)
516 type = "r";
517 else if (flags == O_WRONLY)
518 type = "w";
520 if (type == NULL)
521 return NULL;
523 fs = dview_fdopen (0);
524 if (fs == NULL)
525 return NULL;
527 f = popen (cmd, type);
528 if (f == NULL)
530 dview_ffree (fs);
531 return NULL;
534 fs->fd = fileno (f);
535 fs->data = f;
536 return fs;
539 /* --------------------------------------------------------------------------------------------- */
542 * Close pipe stream.
544 * @param fs structure
545 * @return 0 on success, non-zero on error
548 static int
549 dview_pclose (FBUF *fs)
551 int rv = -1;
553 if (fs != NULL)
555 rv = pclose (fs->data);
556 dview_ffree (fs);
559 return rv;
562 /* --------------------------------------------------------------------------------------------- */
565 * Get one char (byte) from string
567 * @param str ...
568 * @param ch ...
569 * @return TRUE on success, FALSE otherwise
572 static gboolean
573 dview_get_byte (const char *str, int *ch)
575 if (str == NULL)
576 return FALSE;
578 *ch = (unsigned char) (*str);
579 return TRUE;
582 /* --------------------------------------------------------------------------------------------- */
584 #ifdef HAVE_CHARSET
586 * Get utf multibyte char from string
588 * @param str ...
589 * @param ch ...
590 * @param ch_length ...
591 * @return TRUE on success, FALSE otherwise
594 static gboolean
595 dview_get_utf (const char *str, int *ch, int *ch_length)
597 if (str == NULL)
598 return FALSE;
600 *ch = g_utf8_get_char_validated (str, -1);
602 if (*ch < 0)
604 *ch = (unsigned char) (*str);
605 *ch_length = 1;
607 else
609 const char *next_ch;
611 // Calculate UTF-8 char length
612 next_ch = g_utf8_next_char (str);
613 *ch_length = next_ch - str;
616 return TRUE;
619 /* --------------------------------------------------------------------------------------------- */
621 static int
622 dview_str_utf8_offset_to_pos (const char *text, size_t length)
624 ptrdiff_t result;
626 if (text == NULL || text[0] == '\0')
627 return length;
629 if (g_utf8_validate (text, -1, NULL))
630 result = g_utf8_offset_to_pointer (text, length) - text;
631 else
633 gunichar uni;
634 char *tmpbuf, *buffer;
636 buffer = tmpbuf = g_strdup (text);
637 while (tmpbuf[0] != '\0')
639 uni = g_utf8_get_char_validated (tmpbuf, -1);
640 if ((uni != (gunichar) (-1)) && (uni != (gunichar) (-2)))
641 tmpbuf = g_utf8_next_char (tmpbuf);
642 else
644 tmpbuf[0] = '.';
645 tmpbuf++;
648 result = g_utf8_offset_to_pointer (tmpbuf, length) - tmpbuf;
649 g_free (buffer);
651 return MAX (length, (size_t) result);
653 #endif
655 /* --------------------------------------------------------------------------------------------- */
657 /* diff parse *************************************************************** */
660 * Read decimal number from string.
662 * @param[in,out] str string to parse
663 * @param[out] n extracted number
664 * @return 0 if success, otherwise non-zero
667 static int
668 scan_deci (const char **str, int *n)
670 const char *p = *str;
671 char *q;
673 errno = 0;
674 *n = strtol (p, &q, 10);
675 if (errno != 0 || p == q)
676 return -1;
677 *str = q;
678 return 0;
681 /* --------------------------------------------------------------------------------------------- */
684 * Parse line for diff statement.
686 * @param p string to parse
687 * @param ops list of diff statements
688 * @return 0 if success, otherwise non-zero
691 static int
692 scan_line (const char *p, GArray *ops)
694 DIFFCMD op;
696 int f1, f2;
697 int t1, t2;
698 int cmd;
699 gboolean range = FALSE;
701 /* handle the following cases:
702 * NUMaNUM[,NUM]
703 * NUM[,NUM]cNUM[,NUM]
704 * NUM[,NUM]dNUM
705 * where NUM is a positive integer
708 if (scan_deci (&p, &f1) != 0 || f1 < 0)
709 return -1;
711 f2 = f1;
712 if (*p == ',')
714 p++;
715 if (scan_deci (&p, &f2) != 0 || f2 < f1)
716 return -1;
718 range = TRUE;
721 cmd = *p++;
722 if (cmd == 'a')
724 if (range)
725 return -1;
727 else if (cmd != 'c' && cmd != 'd')
728 return -1;
730 if (scan_deci (&p, &t1) != 0 || t1 < 0)
731 return -1;
733 t2 = t1;
734 range = FALSE;
735 if (*p == ',')
737 p++;
738 if (scan_deci (&p, &t2) != 0 || t2 < t1)
739 return -1;
741 range = TRUE;
744 if (cmd == 'd' && range)
745 return -1;
747 op.a[0][0] = f1;
748 op.a[0][1] = f2;
749 op.cmd = cmd;
750 op.a[1][0] = t1;
751 op.a[1][1] = t2;
752 g_array_append_val (ops, op);
753 return 0;
756 /* --------------------------------------------------------------------------------------------- */
759 * Parse diff output and extract diff statements.
761 * @param f stream to read from
762 * @param ops list of diff statements to fill
763 * @return positive number indicating number of hunks, otherwise negative
766 static int
767 scan_diff (FBUF *f, GArray *ops)
769 int sz;
770 char buf[BUFSIZ];
772 while ((sz = dview_fgets (buf, sizeof (buf) - 1, f)) != 0)
774 if (isdigit (buf[0]))
776 if (buf[sz - 1] != '\n')
777 return -1;
779 buf[sz] = '\0';
780 if (scan_line (buf, ops) != 0)
781 return -1;
783 else
784 while (buf[sz - 1] != '\n' && (sz = dview_fgets (buf, sizeof (buf), f)) != 0)
788 return ops->len;
791 /* --------------------------------------------------------------------------------------------- */
794 * Invoke diff and extract diff statements.
796 * @param args extra arguments to be passed to diff
797 * @param extra more arguments to be passed to diff
798 * @param file1 first file to compare
799 * @param file2 second file to compare
800 * @param ops list of diff statements to fill
802 * @return positive number indicating number of hunks, otherwise negative
805 static int
806 dff_execute (const char *args, const char *extra, const char *file1, const char *file2, GArray *ops)
808 static const char *opt = " --old-group-format='%df%(f=l?:,%dl)d%dE\n'"
809 " --new-group-format='%dea%dF%(F=L?:,%dL)\n'"
810 " --changed-group-format='%df%(f=l?:,%dl)c%dF%(F=L?:,%dL)\n'"
811 " --unchanged-group-format=''";
813 int rv;
814 FBUF *f;
815 char *cmd;
816 int code;
817 char *file1_esc, *file2_esc;
819 // escape potential $ to avoid shell variable substitutions in popen()
820 file1_esc = str_shell_escape (file1);
821 file2_esc = str_shell_escape (file2);
822 cmd = g_strdup_printf ("diff %s %s %s %s %s", args, extra, opt, file1_esc, file2_esc);
823 g_free (file1_esc);
824 g_free (file2_esc);
826 if (cmd == NULL)
827 return -1;
829 f = dview_popen (cmd, O_RDONLY);
830 g_free (cmd);
832 if (f == NULL)
833 return -1;
835 rv = scan_diff (f, ops);
836 code = dview_pclose (f);
838 if (rv < 0 || code == -1 || !WIFEXITED (code) || WEXITSTATUS (code) == 2)
839 rv = -1;
841 return rv;
844 /* --------------------------------------------------------------------------------------------- */
846 static gboolean
847 printer_for (char ch, DFUNC printer, void *ctx, FBUF *f, int *line, off_t *off)
849 size_t sz;
850 char buf[BUFSIZ];
852 sz = dview_fgets (buf, sizeof (buf), f);
853 if (sz == 0)
854 return FALSE;
856 (*line)++;
857 printer (ctx, ch, *line, *off, sz, buf);
858 *off += sz;
860 while (buf[sz - 1] != '\n')
862 sz = dview_fgets (buf, sizeof (buf), f);
863 if (sz == 0)
865 printer (ctx, 0, 0, 0, 1, "\n");
866 break;
869 printer (ctx, 0, 0, 0, sz, buf);
870 *off += sz;
873 return TRUE;
876 /* --------------------------------------------------------------------------------------------- */
879 * Reparse and display file according to diff statements.
881 * @param ord DIFF_LEFT if 1st file is displayed , DIFF_RIGHT if 2nd file is displayed.
882 * @param filename file name to display
883 * @param ops list of diff statements
884 * @param printer printf-like function to be used for displaying
885 * @param ctx printer context
887 * @return 0 if success, otherwise non-zero
890 static int
891 dff_reparse (diff_place_t ord, const char *filename, const GArray *ops, DFUNC printer, void *ctx)
893 size_t i;
894 FBUF *f;
895 int line = 0;
896 off_t off = 0;
897 const DIFFCMD *op;
898 diff_place_t eff;
899 int add_cmd, del_cmd;
901 f = dview_fopen (filename, O_RDONLY);
902 if (f == NULL)
903 return -1;
905 if (ord != DIFF_LEFT)
906 ord = DIFF_RIGHT;
907 eff = ord;
909 if (ord != DIFF_LEFT)
911 add_cmd = 'd';
912 del_cmd = 'a';
914 else
916 add_cmd = 'a';
917 del_cmd = 'd';
919 #define F1 a[eff][0]
920 #define F2 a[eff][1]
921 #define T1 a[ord ^ 1][0]
922 #define T2 a[ord ^ 1][1]
923 for (i = 0; i < ops->len; i++)
925 int n;
927 op = &g_array_index (ops, DIFFCMD, i);
929 n = op->F1;
930 if (op->cmd != add_cmd)
931 n--;
933 while (line < n && printer_for (EQU_CH, printer, ctx, f, &line, &off))
936 if (line != n)
937 goto err;
939 if (op->cmd == add_cmd)
940 for (n = op->T2 - op->T1 + 1; n != 0; n--)
941 printer (ctx, DEL_CH, 0, 0, 1, "\n");
943 if (op->cmd == del_cmd)
945 for (n = op->F2 - op->F1 + 1;
946 n != 0 && printer_for (ADD_CH, printer, ctx, f, &line, &off); n--)
949 if (n != 0)
950 goto err;
953 if (op->cmd == 'c')
955 for (n = op->F2 - op->F1 + 1;
956 n != 0 && printer_for (CHG_CH, printer, ctx, f, &line, &off); n--)
959 if (n != 0)
960 goto err;
962 for (n = op->T2 - op->T1 - (op->F2 - op->F1); n > 0; n--)
963 printer (ctx, CHG_CH, 0, 0, 1, "\n");
966 #undef T2
967 #undef T1
968 #undef F2
969 #undef F1
971 while (printer_for (EQU_CH, printer, ctx, f, &line, &off))
974 dview_fclose (f);
975 return 0;
977 err:
978 dview_fclose (f);
979 return -1;
982 /* --------------------------------------------------------------------------------------------- */
984 /* horizontal diff ********************************************************** */
987 * Longest common substring.
989 * @param s first string
990 * @param m length of first string
991 * @param t second string
992 * @param n length of second string
993 * @param ret list of offsets for longest common substrings inside each string
994 * @param min minimum length of common substrings
996 * @return 0 if success, nonzero otherwise
999 static int
1000 lcsubstr (const char *s, int m, const char *t, int n, GArray *ret, int min)
1002 int i, j;
1003 int *Lprev, *Lcurr;
1004 int z = 0;
1006 if (m < min || n < min)
1008 // XXX early culling
1009 return 0;
1012 Lprev = g_try_new0 (int, n + 1);
1013 if (Lprev == NULL)
1014 return -1;
1016 Lcurr = g_try_new0 (int, n + 1);
1017 if (Lcurr == NULL)
1019 g_free (Lprev);
1020 return -1;
1023 for (i = 0; i < m; i++)
1025 int *L;
1027 L = Lprev;
1028 Lprev = Lcurr;
1029 Lcurr = L;
1030 #ifdef USE_MEMSET_IN_LCS
1031 memset (Lcurr, 0, (n + 1) * sizeof (*Lcurr));
1032 #endif
1033 for (j = 0; j < n; j++)
1035 #ifndef USE_MEMSET_IN_LCS
1036 Lcurr[j + 1] = 0;
1037 #endif
1038 if (s[i] == t[j])
1040 int v;
1042 v = Lprev[j] + 1;
1043 Lcurr[j + 1] = v;
1044 if (z < v)
1046 z = v;
1047 g_array_set_size (ret, 0);
1049 if (z == v && z >= min)
1051 int off0, off1;
1052 size_t k;
1054 off0 = i - z + 1;
1055 off1 = j - z + 1;
1057 for (k = 0; k < ret->len; k++)
1059 PAIR *p = (PAIR *) g_array_index (ret, PAIR, k);
1060 if ((*p)[0] == off0 || (*p)[1] >= off1)
1061 break;
1063 if (k == ret->len)
1065 PAIR p2;
1067 p2[0] = off0;
1068 p2[1] = off1;
1069 g_array_append_val (ret, p2);
1076 g_free (Lcurr);
1077 g_free (Lprev);
1078 return z;
1081 /* --------------------------------------------------------------------------------------------- */
1084 * Scan recursively for common substrings and build ranges.
1086 * @param s first string
1087 * @param t second string
1088 * @param bracket current limits for both of the strings
1089 * @param min minimum length of common substrings
1090 * @param hdiff list of horizontal diff ranges to fill
1091 * @param depth recursion depth
1093 * @return 0 if success, nonzero otherwise
1096 static gboolean
1097 hdiff_multi (const char *s, const char *t, const BRACKET bracket, int min, GArray *hdiff,
1098 unsigned int depth)
1100 BRACKET p;
1102 if (depth-- != 0)
1104 GArray *ret;
1105 BRACKET b;
1106 int len;
1108 ret = g_array_new (FALSE, TRUE, sizeof (PAIR));
1110 len = lcsubstr (s + bracket[DIFF_LEFT].off, bracket[DIFF_LEFT].len,
1111 t + bracket[DIFF_RIGHT].off, bracket[DIFF_RIGHT].len, ret, min);
1112 if (ret->len != 0)
1114 size_t k = 0;
1115 const PAIR *data = (const PAIR *) &g_array_index (ret, PAIR, 0);
1116 const PAIR *data2;
1118 b[DIFF_LEFT].off = bracket[DIFF_LEFT].off;
1119 b[DIFF_LEFT].len = (*data)[0];
1120 b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off;
1121 b[DIFF_RIGHT].len = (*data)[1];
1122 if (!hdiff_multi (s, t, b, min, hdiff, depth))
1123 return FALSE;
1125 for (k = 0; k < ret->len - 1; k++)
1127 data = (const PAIR *) &g_array_index (ret, PAIR, k);
1128 data2 = (const PAIR *) &g_array_index (ret, PAIR, k + 1);
1129 b[DIFF_LEFT].off = bracket[DIFF_LEFT].off + (*data)[0] + len;
1130 b[DIFF_LEFT].len = (*data2)[0] - (*data)[0] - len;
1131 b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off + (*data)[1] + len;
1132 b[DIFF_RIGHT].len = (*data2)[1] - (*data)[1] - len;
1133 if (!hdiff_multi (s, t, b, min, hdiff, depth))
1134 return FALSE;
1136 data = (const PAIR *) &g_array_index (ret, PAIR, k);
1137 b[DIFF_LEFT].off = bracket[DIFF_LEFT].off + (*data)[0] + len;
1138 b[DIFF_LEFT].len = bracket[DIFF_LEFT].len - (*data)[0] - len;
1139 b[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off + (*data)[1] + len;
1140 b[DIFF_RIGHT].len = bracket[DIFF_RIGHT].len - (*data)[1] - len;
1141 if (!hdiff_multi (s, t, b, min, hdiff, depth))
1142 return FALSE;
1144 g_array_free (ret, TRUE);
1145 return TRUE;
1149 p[DIFF_LEFT].off = bracket[DIFF_LEFT].off;
1150 p[DIFF_LEFT].len = bracket[DIFF_LEFT].len;
1151 p[DIFF_RIGHT].off = bracket[DIFF_RIGHT].off;
1152 p[DIFF_RIGHT].len = bracket[DIFF_RIGHT].len;
1153 g_array_append_val (hdiff, p);
1155 return TRUE;
1158 /* --------------------------------------------------------------------------------------------- */
1161 * Build list of horizontal diff ranges.
1163 * @param s first string
1164 * @param m length of first string
1165 * @param t second string
1166 * @param n length of second string
1167 * @param min minimum length of common substrings
1168 * @param hdiff list of horizontal diff ranges to fill
1169 * @param depth recursion depth
1171 * @return 0 if success, nonzero otherwise
1174 static gboolean
1175 hdiff_scan (const char *s, int m, const char *t, int n, int min, GArray *hdiff, unsigned int depth)
1177 int i;
1178 BRACKET b;
1180 // dumbscan (single horizontal diff) -- does not compress whitespace
1181 for (i = 0; i < m && i < n && s[i] == t[i]; i++)
1183 for (; m > i && n > i && s[m - 1] == t[n - 1]; m--, n--)
1186 b[DIFF_LEFT].off = i;
1187 b[DIFF_LEFT].len = m - i;
1188 b[DIFF_RIGHT].off = i;
1189 b[DIFF_RIGHT].len = n - i;
1191 // smartscan (multiple horizontal diff)
1192 return hdiff_multi (s, t, b, min, hdiff, depth);
1195 /* --------------------------------------------------------------------------------------------- */
1197 /* read line **************************************************************** */
1200 * Check if character is inside horizontal diff limits.
1202 * @param k rank of character inside line
1203 * @param hdiff horizontal diff structure
1204 * @param ord DIFF_LEFT if reading from first file, DIFF_RIGHT if reading from 2nd file
1206 * @return TRUE if inside hdiff limits, FALSE otherwise
1209 static gboolean
1210 is_inside (int k, GArray *hdiff, diff_place_t ord)
1212 size_t i;
1213 BRACKET *b;
1215 for (i = 0; i < hdiff->len; i++)
1217 int start, end;
1219 b = &g_array_index (hdiff, BRACKET, i);
1220 start = (*b)[ord].off;
1221 end = start + (*b)[ord].len;
1222 if (k >= start && k < end)
1223 return TRUE;
1225 return FALSE;
1228 /* --------------------------------------------------------------------------------------------- */
1231 * Copy 'src' to 'dst' expanding tabs.
1232 * @note The procedure returns when all bytes are consumed from 'src'
1234 * @param dst destination buffer
1235 * @param src source buffer
1236 * @param srcsize size of src buffer
1237 * @param base virtual base of this string, needed to calculate tabs
1238 * @param ts tab size
1240 * @return new virtual base
1243 static int
1244 cvt_cpy (char *dst, const char *src, size_t srcsize, int base, int ts)
1246 int i;
1248 for (i = 0; srcsize != 0; i++, src++, dst++, srcsize--)
1250 *dst = *src;
1251 if (*src == '\t')
1253 int j;
1255 j = TAB_SKIP (ts, i + base);
1256 i += j - 1;
1257 fill_by_space (dst, j, FALSE);
1258 dst += j - 1;
1261 return i + base;
1264 /* --------------------------------------------------------------------------------------------- */
1267 * Copy 'src' to 'dst' expanding tabs.
1269 * @param dst destination buffer
1270 * @param dstsize size of dst buffer
1271 * @param[in,out] _src source buffer
1272 * @param srcsize size of src buffer
1273 * @param base virtual base of this string, needed to calculate tabs
1274 * @param ts tab size
1276 * @return new virtual base
1278 * @note The procedure returns when all bytes are consumed from 'src'
1279 * or 'dstsize' bytes are written to 'dst'
1280 * @note Upon return, 'src' points to the first unwritten character in source
1283 static int
1284 cvt_ncpy (char *dst, int dstsize, const char **_src, size_t srcsize, int base, int ts)
1286 int i;
1287 const char *src = *_src;
1289 for (i = 0; i < dstsize && srcsize != 0; i++, src++, dst++, srcsize--)
1291 *dst = *src;
1292 if (*src == '\t')
1294 int j;
1296 j = TAB_SKIP (ts, i + base);
1297 if (j > dstsize - i)
1298 j = dstsize - i;
1299 i += j - 1;
1300 fill_by_space (dst, j, FALSE);
1301 dst += j - 1;
1304 *_src = src;
1305 return i + base;
1308 /* --------------------------------------------------------------------------------------------- */
1311 * Read line from memory, converting tabs to spaces and padding with spaces.
1313 * @param src buffer to read from
1314 * @param srcsize size of src buffer
1315 * @param dst buffer to read to
1316 * @param dstsize size of dst buffer, excluding trailing null
1317 * @param skip number of characters to skip
1318 * @param ts tab size
1319 * @param show_cr show trailing carriage return as ^M
1321 * @return negative on error, otherwise number of bytes except padding
1324 static int
1325 cvt_mget (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts,
1326 gboolean show_cr)
1328 int sz = 0;
1330 if (src != NULL)
1332 int i;
1333 char *tmp = dst;
1334 const int base = 0;
1336 for (i = 0; dstsize != 0 && srcsize != 0 && *src != '\n'; i++, src++, srcsize--)
1338 if (*src == '\t')
1340 int j;
1342 j = TAB_SKIP (ts, i + base);
1343 i += j - 1;
1344 while (j-- > 0)
1346 if (skip > 0)
1347 skip--;
1348 else if (dstsize != 0)
1350 dstsize--;
1351 *dst++ = ' ';
1355 else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n'))
1357 if (skip == 0 && show_cr)
1359 if (dstsize > 1)
1361 dstsize -= 2;
1362 *dst++ = '^';
1363 *dst++ = 'M';
1365 else
1367 dstsize--;
1368 *dst++ = '.';
1371 break;
1373 else if (skip > 0)
1375 #ifdef HAVE_CHARSET
1376 int ch = 0;
1377 int ch_length = 1;
1379 (void) dview_get_utf (src, &ch, &ch_length);
1381 if (ch_length > 1)
1382 skip += ch_length - 1;
1383 #endif
1385 skip--;
1387 else
1389 dstsize--;
1390 *dst++ = *src;
1393 sz = dst - tmp;
1396 fill_by_space (dst, dstsize, TRUE);
1398 return sz;
1401 /* --------------------------------------------------------------------------------------------- */
1404 * Read line from memory and build attribute array.
1406 * @param src buffer to read from
1407 * @param srcsize size of src buffer
1408 * @param dst buffer to read to
1409 * @param dstsize size of dst buffer, excluding trailing null
1410 * @param skip number of characters to skip
1411 * @param ts tab size
1412 * @param show_cr show trailing carriage return as ^M
1413 * @param hdiff horizontal diff structure
1414 * @param ord DIFF_LEFT if reading from first file, DIFF_RIGHT if reading from 2nd file
1415 * @param att buffer of attributes
1417 * @return negative on error, otherwise number of bytes except padding
1420 static int
1421 cvt_mgeta (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts,
1422 gboolean show_cr, GArray *hdiff, diff_place_t ord, char *att)
1424 int sz = 0;
1426 if (src != NULL)
1428 int i, k;
1429 char *tmp = dst;
1430 const int base = 0;
1432 for (i = 0, k = 0; dstsize != 0 && srcsize != 0 && *src != '\n'; i++, k++, src++, srcsize--)
1434 if (*src == '\t')
1436 int j;
1438 j = TAB_SKIP (ts, i + base);
1439 i += j - 1;
1440 while (j-- > 0)
1442 if (skip != 0)
1443 skip--;
1444 else if (dstsize != 0)
1446 dstsize--;
1447 *att++ = is_inside (k, hdiff, ord);
1448 *dst++ = ' ';
1452 else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n'))
1454 if (skip == 0 && show_cr)
1456 if (dstsize > 1)
1458 dstsize -= 2;
1459 *att++ = is_inside (k, hdiff, ord);
1460 *dst++ = '^';
1461 *att++ = is_inside (k, hdiff, ord);
1462 *dst++ = 'M';
1464 else
1466 dstsize--;
1467 *att++ = is_inside (k, hdiff, ord);
1468 *dst++ = '.';
1471 break;
1473 else if (skip != 0)
1475 #ifdef HAVE_CHARSET
1476 int ch = 0;
1477 int ch_length = 1;
1479 (void) dview_get_utf (src, &ch, &ch_length);
1480 if (ch_length > 1)
1481 skip += ch_length - 1;
1482 #endif
1484 skip--;
1486 else
1488 dstsize--;
1489 *att++ = is_inside (k, hdiff, ord);
1490 *dst++ = *src;
1493 sz = dst - tmp;
1496 memset (att, '\0', dstsize);
1497 fill_by_space (dst, dstsize, TRUE);
1499 return sz;
1502 /* --------------------------------------------------------------------------------------------- */
1505 * Read line from file, converting tabs to spaces and padding with spaces.
1507 * @param f file stream to read from
1508 * @param off offset of line inside file
1509 * @param dst buffer to read to
1510 * @param dstsize size of dst buffer, excluding trailing null
1511 * @param skip number of characters to skip
1512 * @param ts tab size
1513 * @param show_cr show trailing carriage return as ^M
1515 * @return negative on error, otherwise number of bytes except padding
1518 static int
1519 cvt_fget (FBUF *f, off_t off, char *dst, size_t dstsize, int skip, int ts, gboolean show_cr)
1521 int base = 0;
1522 int old_base = base;
1523 size_t amount = dstsize;
1524 size_t useful, offset;
1525 size_t i;
1526 size_t sz;
1527 int lastch = '\0';
1528 const char *q = NULL;
1529 char tmp[BUFSIZ]; // XXX capacity must be >= MAX{dstsize + 1, amount}
1530 char cvt[BUFSIZ]; // XXX capacity must be >= MAX_TAB_WIDTH * amount
1532 if (sizeof (tmp) < amount || sizeof (tmp) <= dstsize || sizeof (cvt) < 8 * amount)
1534 // abnormal, but avoid buffer overflow
1535 fill_by_space (dst, dstsize, TRUE);
1536 return 0;
1539 dview_fseek (f, off, SEEK_SET);
1541 while (skip > base)
1543 old_base = base;
1544 sz = dview_fgets (tmp, amount, f);
1545 if (sz == 0)
1546 break;
1548 base = cvt_cpy (cvt, tmp, sz, old_base, ts);
1549 if (cvt[base - old_base - 1] == '\n')
1551 q = &cvt[base - old_base - 1];
1552 base = old_base + q - cvt + 1;
1553 break;
1557 if (base < skip)
1559 fill_by_space (dst, dstsize, TRUE);
1560 return 0;
1563 useful = base - skip;
1564 offset = skip - old_base;
1566 if (useful <= dstsize)
1568 if (useful != 0)
1569 memmove (dst, cvt + offset, useful);
1571 if (q == NULL)
1573 sz = dview_fgets (tmp, dstsize - useful + 1, f);
1574 if (sz != 0)
1576 const char *ptr = tmp;
1578 useful += cvt_ncpy (dst + useful, dstsize - useful, &ptr, sz, base, ts) - base;
1579 if (ptr < tmp + sz)
1580 lastch = *ptr;
1583 sz = useful;
1585 else
1587 memmove (dst, cvt + offset, dstsize);
1588 sz = dstsize;
1589 lastch = cvt[offset + dstsize];
1592 dst[sz] = lastch;
1593 for (i = 0; i < sz && dst[i] != '\n'; i++)
1594 if (dst[i] == '\r' && dst[i + 1] == '\n')
1596 if (show_cr)
1598 if (i + 1 < dstsize)
1600 dst[i++] = '^';
1601 dst[i++] = 'M';
1603 else
1604 dst[i++] = '*';
1606 break;
1609 fill_by_space (dst, dstsize, TRUE);
1611 return sz;
1614 /* --------------------------------------------------------------------------------------------- */
1615 /* diff printers et al ****************************************************** */
1617 static void
1618 cc_free_elt (gpointer elt)
1620 DIFFLN *p = (DIFFLN *) elt;
1622 if (p != NULL)
1623 g_free (p->p);
1626 /* --------------------------------------------------------------------------------------------- */
1628 static int
1629 printer (void *ctx, int ch, int line, off_t off, size_t sz, const char *str)
1631 GArray *a = ((PRINTER_CTX *) ctx)->a;
1632 DSRC dsrc = ((PRINTER_CTX *) ctx)->dsrc;
1634 if (ch != 0)
1636 DIFFLN p;
1638 p.p = NULL;
1639 p.ch = ch;
1640 p.line = line;
1641 p.u.off = off;
1642 if (dsrc == DATA_SRC_MEM && line != 0)
1644 if (sz != 0 && str[sz - 1] == '\n')
1645 sz--;
1646 if (sz > 0)
1647 p.p = g_strndup (str, sz);
1648 p.u.len = sz;
1650 g_array_append_val (a, p);
1652 else if (dsrc == DATA_SRC_MEM)
1654 DIFFLN *p;
1656 p = &g_array_index (a, DIFFLN, a->len - 1);
1657 if (sz != 0 && str[sz - 1] == '\n')
1658 sz--;
1659 if (sz != 0)
1661 size_t new_size;
1662 char *q;
1664 new_size = p->u.len + sz;
1665 q = g_realloc (p->p, new_size);
1666 memcpy (q + p->u.len, str, sz);
1667 p->p = q;
1669 p->u.len += sz;
1671 if (dsrc == DATA_SRC_TMP && (line != 0 || ch == 0))
1673 FBUF *f = ((PRINTER_CTX *) ctx)->f;
1674 dview_fwrite (f, str, sz);
1676 return 0;
1679 /* --------------------------------------------------------------------------------------------- */
1681 static int
1682 redo_diff (WDiff *dview)
1684 FBUF *const *f = dview->f;
1685 PRINTER_CTX ctx;
1686 GArray *ops;
1687 int ndiff;
1688 int rv = 0;
1689 char extra[BUF_MEDIUM];
1691 extra[0] = '\0';
1692 if (dview->opt.quality == 2)
1693 strcat (extra, " -d");
1694 if (dview->opt.quality == 1)
1695 strcat (extra, " --speed-large-files");
1696 if (dview->opt.strip_trailing_cr)
1697 strcat (extra, " --strip-trailing-cr");
1698 if (dview->opt.ignore_tab_expansion)
1699 strcat (extra, " -E");
1700 if (dview->opt.ignore_space_change)
1701 strcat (extra, " -b");
1702 if (dview->opt.ignore_all_space)
1703 strcat (extra, " -w");
1704 if (dview->opt.ignore_case)
1705 strcat (extra, " -i");
1707 if (dview->dsrc != DATA_SRC_MEM)
1709 dview_freset (f[DIFF_LEFT]);
1710 dview_freset (f[DIFF_RIGHT]);
1713 ops = g_array_new (FALSE, FALSE, sizeof (DIFFCMD));
1714 ndiff = dff_execute (dview->args, extra, dview->file[DIFF_LEFT], dview->file[DIFF_RIGHT], ops);
1715 if (ndiff < 0)
1717 if (ops != NULL)
1718 g_array_free (ops, TRUE);
1719 return -1;
1722 ctx.dsrc = dview->dsrc;
1723 ctx.a = dview->a[DIFF_LEFT];
1724 ctx.f = f[DIFF_LEFT];
1725 rv |= dff_reparse (DIFF_LEFT, dview->file[DIFF_LEFT], ops, printer, &ctx);
1727 ctx.a = dview->a[DIFF_RIGHT];
1728 ctx.f = f[DIFF_RIGHT];
1729 rv |= dff_reparse (DIFF_RIGHT, dview->file[DIFF_RIGHT], ops, printer, &ctx);
1731 if (ops != NULL)
1732 g_array_free (ops, TRUE);
1734 if (rv != 0 || dview->a[DIFF_LEFT]->len != dview->a[DIFF_RIGHT]->len)
1735 return -1;
1737 if (dview->dsrc == DATA_SRC_TMP)
1739 dview_ftrunc (f[DIFF_LEFT]);
1740 dview_ftrunc (f[DIFF_RIGHT]);
1743 if (dview->dsrc == DATA_SRC_MEM && HDIFF_ENABLE)
1745 size_t i;
1747 dview->hdiff = g_ptr_array_new ();
1749 for (i = 0; i < dview->a[DIFF_LEFT]->len; i++)
1751 GArray *h = NULL;
1752 const DIFFLN *p;
1753 const DIFFLN *q;
1755 p = &g_array_index (dview->a[DIFF_LEFT], DIFFLN, i);
1756 q = &g_array_index (dview->a[DIFF_RIGHT], DIFFLN, i);
1757 if (p->line != 0 && q->line != 0 && p->ch == CHG_CH)
1759 gboolean runresult;
1761 h = g_array_new (FALSE, FALSE, sizeof (BRACKET));
1763 runresult =
1764 hdiff_scan (p->p, p->u.len, q->p, q->u.len, HDIFF_MINCTX, h, HDIFF_DEPTH);
1765 if (!runresult)
1767 g_array_free (h, TRUE);
1768 h = NULL;
1772 g_ptr_array_add (dview->hdiff, h);
1775 return ndiff;
1778 /* --------------------------------------------------------------------------------------------- */
1780 static void
1781 destroy_hdiff (WDiff *dview)
1783 if (dview->hdiff != NULL)
1785 int i;
1786 int len;
1788 len = dview->a[DIFF_LEFT]->len;
1790 for (i = 0; i < len; i++)
1792 GArray *h;
1794 h = (GArray *) g_ptr_array_index (dview->hdiff, i);
1795 if (h != NULL)
1796 g_array_free (h, TRUE);
1798 g_ptr_array_free (dview->hdiff, TRUE);
1799 dview->hdiff = NULL;
1802 mc_search_free (dview->search.handle);
1803 dview->search.handle = NULL;
1804 MC_PTR_FREE (dview->search.last_string);
1807 /* --------------------------------------------------------------------------------------------- */
1808 /* stuff ******************************************************************** */
1810 static int
1811 get_digits (unsigned int n)
1813 int d = 1;
1815 while ((n /= 10) != 0)
1816 d++;
1817 return d;
1820 /* --------------------------------------------------------------------------------------------- */
1822 static int
1823 get_line_numbers (const GArray *a, size_t pos, int *linenum, int *lineofs)
1825 const DIFFLN *p;
1827 *linenum = 0;
1828 *lineofs = 0;
1830 if (a->len != 0)
1832 if (pos >= a->len)
1833 pos = a->len - 1;
1835 p = &g_array_index (a, DIFFLN, pos);
1837 if (p->line == 0)
1839 int n;
1841 for (n = pos; n > 0; n--)
1843 p--;
1844 if (p->line != 0)
1845 break;
1847 *lineofs = pos - n + 1;
1850 *linenum = p->line;
1852 return 0;
1855 /* --------------------------------------------------------------------------------------------- */
1857 static int
1858 calc_nwidth (const GArray *const *a)
1860 int l1, o1;
1861 int l2, o2;
1863 get_line_numbers (a[DIFF_LEFT], a[DIFF_LEFT]->len - 1, &l1, &o1);
1864 get_line_numbers (a[DIFF_RIGHT], a[DIFF_RIGHT]->len - 1, &l2, &o2);
1865 if (l1 < l2)
1866 l1 = l2;
1867 return get_digits (l1);
1870 /* --------------------------------------------------------------------------------------------- */
1872 static int
1873 find_prev_hunk (const GArray *a, int pos)
1875 #if 1
1876 for (; pos > 0 && ((DIFFLN *) &g_array_index (a, DIFFLN, pos))->ch != EQU_CH; pos--)
1878 for (; pos > 0 && ((DIFFLN *) &g_array_index (a, DIFFLN, pos))->ch == EQU_CH; pos--)
1880 for (; pos > 0 && ((DIFFLN *) &g_array_index (a, DIFFLN, pos))->ch != EQU_CH; pos--)
1882 if (pos > 0 && (size_t) pos < a->len)
1883 pos++;
1884 #else
1885 for (; pos > 0 && ((DIFFLN *) &g_array_index (a, DIFFLN, pos - 1))->ch == EQU_CH; pos--)
1887 for (; pos > 0 && ((DIFFLN *) &g_array_index (a, DIFFLN, pos - 1))->ch != EQU_CH; pos--)
1889 #endif
1891 return pos;
1894 /* --------------------------------------------------------------------------------------------- */
1896 static size_t
1897 find_next_hunk (const GArray *a, size_t pos)
1899 for (; pos < a->len && ((DIFFLN *) &g_array_index (a, DIFFLN, pos))->ch != EQU_CH; pos++)
1901 for (; pos < a->len && ((DIFFLN *) &g_array_index (a, DIFFLN, pos))->ch == EQU_CH; pos++)
1903 return pos;
1906 /* --------------------------------------------------------------------------------------------- */
1908 * Find start and end lines of the current hunk.
1910 * @param dview WDiff widget
1911 * @return boolean and
1912 * start_line1 first line of current hunk (file[0])
1913 * end_line1 last line of current hunk (file[0])
1914 * start_line1 first line of current hunk (file[0])
1915 * end_line1 last line of current hunk (file[0])
1918 static int
1919 get_current_hunk (WDiff *dview, int *start_line1, int *end_line1, int *start_line2, int *end_line2)
1921 const GArray *a0 = dview->a[DIFF_LEFT];
1922 const GArray *a1 = dview->a[DIFF_RIGHT];
1923 size_t pos;
1924 int ch;
1925 int res = 0;
1927 // Is file empty?
1928 if (a0->len == 0)
1929 return 0;
1931 *start_line1 = 1;
1932 *start_line2 = 1;
1933 *end_line1 = 1;
1934 *end_line2 = 1;
1936 pos = dview->skip_rows;
1937 ch = ((DIFFLN *) &g_array_index (a0, DIFFLN, pos))->ch;
1938 if (ch != EQU_CH)
1940 switch (ch)
1942 case ADD_CH:
1943 res = DIFF_DEL;
1944 break;
1945 case DEL_CH:
1946 res = DIFF_ADD;
1947 break;
1948 case CHG_CH:
1949 res = DIFF_CHG;
1950 break;
1951 default:
1952 break;
1955 for (; pos > 0 && ((DIFFLN *) &g_array_index (a0, DIFFLN, pos))->ch != EQU_CH; pos--)
1957 if (pos > 0)
1959 *start_line1 = ((DIFFLN *) &g_array_index (a0, DIFFLN, pos))->line + 1;
1960 *start_line2 = ((DIFFLN *) &g_array_index (a1, DIFFLN, pos))->line + 1;
1963 for (pos = dview->skip_rows;
1964 pos < a0->len && ((DIFFLN *) &g_array_index (a0, DIFFLN, pos))->ch != EQU_CH; pos++)
1966 int l0, l1;
1968 l0 = ((DIFFLN *) &g_array_index (a0, DIFFLN, pos))->line;
1969 l1 = ((DIFFLN *) &g_array_index (a1, DIFFLN, pos))->line;
1970 if (l0 > 0)
1971 *end_line1 = MAX (*start_line1, l0);
1972 if (l1 > 0)
1973 *end_line2 = MAX (*start_line2, l1);
1976 return res;
1979 /* --------------------------------------------------------------------------------------------- */
1981 * Remove hunk from file.
1983 * @param dview WDiff widget
1984 * @param merge_file file stream for writing data
1985 * @param from1 first line of hunk
1986 * @param to1 last line of hunk
1987 * @param merge_direction in what direction files should be merged
1990 static void
1991 dview_remove_hunk (WDiff *dview, FILE *merge_file, int from1, int to1,
1992 action_direction_t merge_direction)
1994 int line;
1995 char buf[BUF_10K];
1996 FILE *f0;
1998 if (merge_direction == FROM_RIGHT_TO_LEFT)
1999 f0 = fopen (dview->file[DIFF_RIGHT], "r");
2000 else
2001 f0 = fopen (dview->file[DIFF_LEFT], "r");
2003 for (line = 0; fgets (buf, sizeof (buf), f0) != NULL && line < from1 - 1; line++)
2004 fputs (buf, merge_file);
2006 while (fgets (buf, sizeof (buf), f0) != NULL)
2008 line++;
2009 if (line >= to1)
2010 fputs (buf, merge_file);
2012 fclose (f0);
2015 /* --------------------------------------------------------------------------------------------- */
2017 * Add hunk to file.
2019 * @param dview WDiff widget
2020 * @param merge_file file stream for writing data
2021 * @param from1 first line of source hunk
2022 * @param from2 first line of destination hunk
2023 * @param to1 last line of source hunk
2024 * @param merge_direction in what direction files should be merged
2027 static void
2028 dview_add_hunk (WDiff *dview, FILE *merge_file, int from1, int from2, int to2,
2029 action_direction_t merge_direction)
2031 int line;
2032 char buf[BUF_10K];
2033 FILE *f0, *f1;
2035 if (merge_direction == FROM_RIGHT_TO_LEFT)
2037 f0 = fopen (dview->file[DIFF_RIGHT], "r");
2038 f1 = fopen (dview->file[DIFF_LEFT], "r");
2040 else
2042 f0 = fopen (dview->file[DIFF_LEFT], "r");
2043 f1 = fopen (dview->file[DIFF_RIGHT], "r");
2046 for (line = 0; fgets (buf, sizeof (buf), f0) != NULL && line < from1 - 1; line++)
2047 fputs (buf, merge_file);
2048 for (line = 0; fgets (buf, sizeof (buf), f1) != NULL && line <= to2;)
2050 line++;
2051 if (line >= from2)
2052 fputs (buf, merge_file);
2054 while (fgets (buf, sizeof (buf), f0) != NULL)
2055 fputs (buf, merge_file);
2057 fclose (f0);
2058 fclose (f1);
2061 /* --------------------------------------------------------------------------------------------- */
2063 * Replace hunk in file.
2065 * @param dview WDiff widget
2066 * @param merge_file file stream for writing data
2067 * @param from1 first line of source hunk
2068 * @param to1 last line of source hunk
2069 * @param from2 first line of destination hunk
2070 * @param to2 last line of destination hunk
2071 * @param merge_direction in what direction files should be merged
2074 static void
2075 dview_replace_hunk (WDiff *dview, FILE *merge_file, int from1, int to1, int from2, int to2,
2076 action_direction_t merge_direction)
2078 int line1, line2;
2079 char buf[BUF_10K];
2080 FILE *f0, *f1;
2082 if (merge_direction == FROM_RIGHT_TO_LEFT)
2084 f0 = fopen (dview->file[DIFF_RIGHT], "r");
2085 f1 = fopen (dview->file[DIFF_LEFT], "r");
2087 else
2089 f0 = fopen (dview->file[DIFF_LEFT], "r");
2090 f1 = fopen (dview->file[DIFF_RIGHT], "r");
2093 for (line1 = 0; fgets (buf, sizeof (buf), f0) != NULL && line1 < from1 - 1; line1++)
2094 fputs (buf, merge_file);
2095 for (line2 = 0; fgets (buf, sizeof (buf), f1) != NULL && line2 <= to2;)
2097 line2++;
2098 if (line2 >= from2)
2099 fputs (buf, merge_file);
2101 while (fgets (buf, sizeof (buf), f0) != NULL)
2103 line1++;
2104 if (line1 > to1)
2105 fputs (buf, merge_file);
2107 fclose (f0);
2108 fclose (f1);
2111 /* --------------------------------------------------------------------------------------------- */
2113 * Merge hunk.
2115 * @param dview WDiff widget
2116 * @param merge_direction in what direction files should be merged
2119 static void
2120 do_merge_hunk (WDiff *dview, action_direction_t merge_direction)
2122 int from1, to1, from2, to2;
2123 int hunk;
2124 diff_place_t n_merge = (merge_direction == FROM_RIGHT_TO_LEFT) ? DIFF_RIGHT : DIFF_LEFT;
2126 if (merge_direction == FROM_RIGHT_TO_LEFT)
2127 hunk = get_current_hunk (dview, &from2, &to2, &from1, &to1);
2128 else
2129 hunk = get_current_hunk (dview, &from1, &to1, &from2, &to2);
2131 if (hunk > 0)
2133 int merge_file_fd;
2134 FILE *merge_file;
2135 vfs_path_t *merge_file_name_vpath = NULL;
2137 if (!dview->merged[n_merge])
2139 dview->merged[n_merge] = mc_util_make_backup_if_possible (dview->file[n_merge], "~~~");
2140 if (!dview->merged[n_merge])
2142 message (D_ERROR, MSG_ERROR, _ ("Cannot create backup file\n%s%s\n%s"),
2143 dview->file[n_merge], "~~~", unix_error_string (errno));
2144 return;
2148 merge_file_fd = mc_mkstemps (&merge_file_name_vpath, "mcmerge", NULL);
2149 if (merge_file_fd == -1)
2151 message (D_ERROR, MSG_ERROR, _ ("Cannot create temporary merge file\n%s"),
2152 unix_error_string (errno));
2153 return;
2156 merge_file = fdopen (merge_file_fd, "w");
2158 switch (hunk)
2160 case DIFF_DEL:
2161 if (merge_direction == FROM_RIGHT_TO_LEFT)
2162 dview_add_hunk (dview, merge_file, from1, from2, to2, FROM_RIGHT_TO_LEFT);
2163 else
2164 dview_remove_hunk (dview, merge_file, from1, to1, FROM_LEFT_TO_RIGHT);
2165 break;
2166 case DIFF_ADD:
2167 if (merge_direction == FROM_RIGHT_TO_LEFT)
2168 dview_remove_hunk (dview, merge_file, from1, to1, FROM_RIGHT_TO_LEFT);
2169 else
2170 dview_add_hunk (dview, merge_file, from1, from2, to2, FROM_LEFT_TO_RIGHT);
2171 break;
2172 case DIFF_CHG:
2173 dview_replace_hunk (dview, merge_file, from1, to1, from2, to2, merge_direction);
2174 break;
2175 default:
2176 break;
2178 fflush (merge_file);
2179 fclose (merge_file);
2181 int res;
2183 res = rewrite_backup_content (merge_file_name_vpath, dview->file[n_merge]);
2184 (void) res;
2186 mc_unlink (merge_file_name_vpath);
2187 vfs_path_free (merge_file_name_vpath, TRUE);
2191 /* --------------------------------------------------------------------------------------------- */
2192 /* view routines and callbacks ********************************************** */
2194 static void
2195 dview_compute_split (WDiff *dview, int i)
2197 dview->bias += i;
2198 if (dview->bias < 2 - dview->half1)
2199 dview->bias = 2 - dview->half1;
2200 if (dview->bias > dview->half2 - 2)
2201 dview->bias = dview->half2 - 2;
2204 /* --------------------------------------------------------------------------------------------- */
2206 static void
2207 dview_compute_areas (WDiff *dview)
2209 Widget *w = WIDGET (dview);
2211 dview->height = w->rect.lines - 1;
2212 dview->half1 = w->rect.cols / 2;
2213 dview->half2 = w->rect.cols - dview->half1;
2215 dview_compute_split (dview, 0);
2218 /* --------------------------------------------------------------------------------------------- */
2220 static void
2221 dview_reread (WDiff *dview)
2223 int ndiff;
2225 destroy_hdiff (dview);
2226 if (dview->a[DIFF_LEFT] != NULL)
2227 g_array_free (dview->a[DIFF_LEFT], TRUE);
2228 if (dview->a[DIFF_RIGHT] != NULL)
2229 g_array_free (dview->a[DIFF_RIGHT], TRUE);
2231 dview->a[DIFF_LEFT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
2232 g_array_set_clear_func (dview->a[DIFF_LEFT], cc_free_elt);
2233 dview->a[DIFF_RIGHT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
2234 g_array_set_clear_func (dview->a[DIFF_RIGHT], cc_free_elt);
2236 ndiff = redo_diff (dview);
2237 if (ndiff >= 0)
2238 dview->ndiff = ndiff;
2241 /* --------------------------------------------------------------------------------------------- */
2243 #ifdef HAVE_CHARSET
2244 static void
2245 dview_set_codeset (WDiff *dview)
2247 const char *encoding_id = NULL;
2249 dview->utf8 = TRUE;
2250 encoding_id = get_codepage_id (mc_global.source_codepage >= 0 ? mc_global.source_codepage
2251 : mc_global.display_codepage);
2252 if (encoding_id != NULL)
2254 GIConv conv;
2256 conv = str_crt_conv_from (encoding_id);
2257 if (conv != INVALID_CONV)
2259 if (dview->converter != str_cnv_from_term)
2260 str_close_conv (dview->converter);
2261 dview->converter = conv;
2263 dview->utf8 = (gboolean) str_isutf8 (encoding_id);
2267 /* --------------------------------------------------------------------------------------------- */
2269 static void
2270 dview_select_encoding (WDiff *dview)
2272 if (do_select_codepage ())
2273 dview_set_codeset (dview);
2274 dview_reread (dview);
2275 tty_touch_screen ();
2276 repaint_screen ();
2278 #endif
2280 /* --------------------------------------------------------------------------------------------- */
2282 static void
2283 dview_load_options (WDiff *dview)
2285 gboolean show_numbers;
2286 int tab_size;
2288 dview->display_symbols =
2289 mc_config_get_bool (mc_global.main_config, "DiffView", "show_symbols", FALSE);
2290 show_numbers = mc_config_get_bool (mc_global.main_config, "DiffView", "show_numbers", FALSE);
2291 if (show_numbers)
2292 dview->display_numbers = 1;
2293 tab_size = mc_config_get_int (mc_global.main_config, "DiffView", "tab_size", 8);
2294 if (tab_size > 0 && tab_size < 9)
2295 dview->tab_size = tab_size;
2296 else
2297 dview->tab_size = 8;
2299 dview->opt.quality = mc_config_get_int (mc_global.main_config, "DiffView", "diff_quality", 0);
2301 dview->opt.strip_trailing_cr =
2302 mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_tws", FALSE);
2303 dview->opt.ignore_all_space =
2304 mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_all_space", FALSE);
2305 dview->opt.ignore_space_change =
2306 mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_space_change", FALSE);
2307 dview->opt.ignore_tab_expansion =
2308 mc_config_get_bool (mc_global.main_config, "DiffView", "diff_tab_expansion", FALSE);
2309 dview->opt.ignore_case =
2310 mc_config_get_bool (mc_global.main_config, "DiffView", "diff_ignore_case", FALSE);
2312 dview->new_frame = TRUE;
2315 /* --------------------------------------------------------------------------------------------- */
2317 static void
2318 dview_save_options (WDiff *dview)
2320 mc_config_set_bool (mc_global.main_config, "DiffView", "show_symbols", dview->display_symbols);
2321 mc_config_set_bool (mc_global.main_config, "DiffView", "show_numbers",
2322 dview->display_numbers != 0);
2323 mc_config_set_int (mc_global.main_config, "DiffView", "tab_size", dview->tab_size);
2325 mc_config_set_int (mc_global.main_config, "DiffView", "diff_quality", dview->opt.quality);
2327 mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_tws",
2328 dview->opt.strip_trailing_cr);
2329 mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_all_space",
2330 dview->opt.ignore_all_space);
2331 mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_space_change",
2332 dview->opt.ignore_space_change);
2333 mc_config_set_bool (mc_global.main_config, "DiffView", "diff_tab_expansion",
2334 dview->opt.ignore_tab_expansion);
2335 mc_config_set_bool (mc_global.main_config, "DiffView", "diff_ignore_case",
2336 dview->opt.ignore_case);
2339 /* --------------------------------------------------------------------------------------------- */
2341 static void
2342 dview_diff_options (WDiff *dview)
2344 const char *quality_str[] = {
2345 N_ ("No&rmal"),
2346 N_ ("&Fastest (Assume large files)"),
2347 N_ ("&Minimal (Find a smaller set of change)"),
2350 quick_widget_t quick_widgets[] = {
2351 // clang-format off
2352 QUICK_START_GROUPBOX (N_ ("Diff algorithm")),
2353 QUICK_RADIO (3, (const char **) quality_str, (int *) &dview->opt.quality, NULL),
2354 QUICK_STOP_GROUPBOX,
2355 QUICK_START_GROUPBOX (N_ ("Diff extra options")),
2356 QUICK_CHECKBOX (N_ ("&Ignore case"), &dview->opt.ignore_case, NULL),
2357 QUICK_CHECKBOX (N_ ("Ignore tab &expansion"), &dview->opt.ignore_tab_expansion, NULL),
2358 QUICK_CHECKBOX (N_ ("Ignore &space change"), &dview->opt.ignore_space_change, NULL),
2359 QUICK_CHECKBOX (N_ ("Ignore all &whitespace"), &dview->opt.ignore_all_space, NULL),
2360 QUICK_CHECKBOX (N_ ("Strip &trailing carriage return"), &dview->opt.strip_trailing_cr,
2361 NULL),
2362 QUICK_STOP_GROUPBOX,
2363 QUICK_BUTTONS_OK_CANCEL,
2364 QUICK_END,
2365 // clang-format on
2368 WRect r = { -1, -1, 0, 56 };
2370 quick_dialog_t qdlg = {
2371 .rect = r,
2372 .title = N_ ("Diff Options"),
2373 .help = "[Diff Options]",
2374 .widgets = quick_widgets,
2375 .callback = NULL,
2376 .mouse_callback = NULL,
2379 if (quick_dialog (&qdlg) != B_CANCEL)
2380 dview_reread (dview);
2383 /* --------------------------------------------------------------------------------------------- */
2385 static int
2386 dview_init (WDiff *dview, const char *args, const char *file1, const char *file2,
2387 const char *label1, const char *label2, DSRC dsrc)
2389 FBUF *f[DIFF_COUNT];
2391 f[DIFF_LEFT] = NULL;
2392 f[DIFF_RIGHT] = NULL;
2394 if (dsrc == DATA_SRC_TMP)
2396 f[DIFF_LEFT] = dview_ftemp ();
2397 if (f[DIFF_LEFT] == NULL)
2398 return -1;
2400 f[DIFF_RIGHT] = dview_ftemp ();
2401 if (f[DIFF_RIGHT] == NULL)
2403 dview_fclose (f[DIFF_LEFT]);
2404 return -1;
2407 else if (dsrc == DATA_SRC_ORG)
2409 f[DIFF_LEFT] = dview_fopen (file1, O_RDONLY);
2410 if (f[DIFF_LEFT] == NULL)
2411 return -1;
2413 f[DIFF_RIGHT] = dview_fopen (file2, O_RDONLY);
2414 if (f[DIFF_RIGHT] == NULL)
2416 dview_fclose (f[DIFF_LEFT]);
2417 return -1;
2421 dview->view_quit = FALSE;
2423 dview->bias = 0;
2424 dview->new_frame = TRUE;
2425 dview->skip_rows = 0;
2426 dview->skip_cols = 0;
2427 dview->display_symbols = FALSE;
2428 dview->display_numbers = 0;
2429 dview->show_cr = TRUE;
2430 dview->tab_size = 8;
2431 dview->ord = DIFF_LEFT;
2432 dview->full = FALSE;
2434 dview->search.handle = NULL;
2435 dview->search.last_string = NULL;
2436 dview->search.last_found_line = -1;
2437 dview->search.last_accessed_num_line = -1;
2439 dview_load_options (dview);
2441 dview->args = args;
2442 dview->file[DIFF_LEFT] = file1;
2443 dview->file[DIFF_RIGHT] = file2;
2444 dview->label[DIFF_LEFT] = g_strdup (label1);
2445 dview->label[DIFF_RIGHT] = g_strdup (label2);
2446 dview->f[DIFF_LEFT] = f[0];
2447 dview->f[DIFF_RIGHT] = f[1];
2448 dview->merged[DIFF_LEFT] = FALSE;
2449 dview->merged[DIFF_RIGHT] = FALSE;
2450 dview->hdiff = NULL;
2451 dview->dsrc = dsrc;
2452 #ifdef HAVE_CHARSET
2453 dview->converter = str_cnv_from_term;
2454 dview_set_codeset (dview);
2455 #endif
2456 dview->a[DIFF_LEFT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
2457 g_array_set_clear_func (dview->a[DIFF_LEFT], cc_free_elt);
2458 dview->a[DIFF_RIGHT] = g_array_new (FALSE, FALSE, sizeof (DIFFLN));
2459 g_array_set_clear_func (dview->a[DIFF_RIGHT], cc_free_elt);
2461 return 0;
2464 /* --------------------------------------------------------------------------------------------- */
2466 static void
2467 dview_fini (WDiff *dview)
2469 if (dview->dsrc != DATA_SRC_MEM)
2471 dview_fclose (dview->f[DIFF_RIGHT]);
2472 dview_fclose (dview->f[DIFF_LEFT]);
2475 #ifdef HAVE_CHARSET
2476 if (dview->converter != str_cnv_from_term)
2477 str_close_conv (dview->converter);
2478 #endif
2480 destroy_hdiff (dview);
2481 if (dview->a[DIFF_LEFT] != NULL)
2483 g_array_free (dview->a[DIFF_LEFT], TRUE);
2484 dview->a[DIFF_LEFT] = NULL;
2486 if (dview->a[DIFF_RIGHT] != NULL)
2488 g_array_free (dview->a[DIFF_RIGHT], TRUE);
2489 dview->a[DIFF_RIGHT] = NULL;
2492 g_free (dview->label[DIFF_LEFT]);
2493 g_free (dview->label[DIFF_RIGHT]);
2496 /* --------------------------------------------------------------------------------------------- */
2498 static int
2499 dview_display_file (const WDiff *dview, diff_place_t ord, int r, int c, int height, int width)
2501 size_t i, k;
2502 int j;
2503 char buf[BUFSIZ];
2504 FBUF *f = dview->f[ord];
2505 int skip = dview->skip_cols;
2506 gboolean display_symbols = dview->display_symbols;
2507 int display_numbers = dview->display_numbers;
2508 gboolean show_cr = dview->show_cr;
2509 int tab_size = 8;
2510 const DIFFLN *p;
2511 int nwidth = display_numbers;
2512 int xwidth;
2514 xwidth = display_numbers;
2515 if (display_symbols)
2516 xwidth++;
2517 if (dview->tab_size > 0 && dview->tab_size < 9)
2518 tab_size = dview->tab_size;
2520 if (xwidth != 0)
2522 if (xwidth > width && display_symbols)
2524 xwidth--;
2525 display_symbols = FALSE;
2527 if (xwidth > width && display_numbers != 0)
2529 xwidth = width;
2530 display_numbers = width;
2533 xwidth++;
2534 c += xwidth;
2535 width -= xwidth;
2536 if (width < 0)
2537 width = 0;
2540 if ((int) sizeof (buf) <= width || (int) sizeof (buf) <= nwidth)
2542 // abnormal, but avoid buffer overflow
2543 return -1;
2546 for (i = dview->skip_rows, j = 0; i < dview->a[ord]->len && j < height; j++, i++)
2548 int ch;
2549 int next_ch = 0;
2550 int col;
2551 size_t cnt;
2553 p = (DIFFLN *) &g_array_index (dview->a[ord], DIFFLN, i);
2554 ch = p->ch;
2555 tty_setcolor (NORMAL_COLOR);
2556 if (display_symbols)
2558 tty_gotoyx (r + j, c - 2);
2559 tty_print_char (ch);
2561 if (p->line != 0)
2563 if (display_numbers != 0)
2565 tty_gotoyx (r + j, c - xwidth);
2566 g_snprintf (buf, display_numbers + 1, "%*d", nwidth, p->line);
2567 tty_print_string (str_fit_to_term (buf, nwidth, J_LEFT_FIT));
2569 if (ch == ADD_CH)
2570 tty_setcolor (DFF_ADD_COLOR);
2571 if (ch == CHG_CH)
2572 tty_setcolor (DFF_CHG_COLOR);
2573 if (f == NULL)
2575 if (i == (size_t) dview->search.last_found_line)
2576 tty_setcolor (MARKED_SELECTED_COLOR);
2577 else if (dview->hdiff != NULL && g_ptr_array_index (dview->hdiff, i) != NULL)
2579 char att[BUFSIZ];
2581 #ifdef HAVE_CHARSET
2582 if (dview->utf8)
2583 k = dview_str_utf8_offset_to_pos (p->p, width);
2584 else
2585 #endif
2586 k = width;
2588 cvt_mgeta (p->p, p->u.len, buf, k, skip, tab_size, show_cr,
2589 g_ptr_array_index (dview->hdiff, i), ord, att);
2590 tty_gotoyx (r + j, c);
2591 col = 0;
2593 for (cnt = 0; cnt < strlen (buf) && col < width; cnt++)
2595 gboolean ch_res;
2597 #ifdef HAVE_CHARSET
2598 if (dview->utf8)
2600 int ch_length = 0;
2602 ch_res = dview_get_utf (buf + cnt, &next_ch, &ch_length);
2603 if (ch_length > 1)
2604 cnt += ch_length - 1;
2605 if (!g_unichar_isprint (next_ch))
2606 next_ch = '.';
2608 else
2609 #endif
2610 ch_res = dview_get_byte (buf + cnt, &next_ch);
2612 if (ch_res)
2614 tty_setcolor (att[cnt] ? DFF_CHH_COLOR : DFF_CHG_COLOR);
2615 #ifdef HAVE_CHARSET
2616 if (mc_global.utf8_display)
2618 if (!dview->utf8)
2620 next_ch = convert_from_8bit_to_utf_c ((unsigned char) next_ch,
2621 dview->converter);
2624 else if (dview->utf8)
2625 next_ch = convert_from_utf_to_current_c (next_ch, dview->converter);
2626 else
2627 next_ch = convert_to_display_c (next_ch);
2628 #endif
2629 tty_print_anychar (next_ch);
2630 col++;
2633 continue;
2636 if (ch == CHG_CH)
2637 tty_setcolor (DFF_CHH_COLOR);
2639 #ifdef HAVE_CHARSET
2640 if (dview->utf8)
2641 k = dview_str_utf8_offset_to_pos (p->p, width);
2642 else
2643 #endif
2644 k = width;
2645 cvt_mget (p->p, p->u.len, buf, k, skip, tab_size, show_cr);
2647 else
2648 cvt_fget (f, p->u.off, buf, width, skip, tab_size, show_cr);
2650 else
2652 if (display_numbers != 0)
2654 tty_gotoyx (r + j, c - xwidth);
2655 fill_by_space (buf, display_numbers, TRUE);
2656 tty_print_string (buf);
2658 if (ch == DEL_CH)
2659 tty_setcolor (DFF_DEL_COLOR);
2660 if (ch == CHG_CH)
2661 tty_setcolor (DFF_CHD_COLOR);
2662 fill_by_space (buf, width, TRUE);
2664 tty_gotoyx (r + j, c);
2665 // tty_print_nstring (buf, width);
2666 col = 0;
2667 for (cnt = 0; cnt < strlen (buf) && col < width; cnt++)
2669 gboolean ch_res;
2671 #ifdef HAVE_CHARSET
2672 if (dview->utf8)
2674 int ch_length = 0;
2676 ch_res = dview_get_utf (buf + cnt, &next_ch, &ch_length);
2677 if (ch_length > 1)
2678 cnt += ch_length - 1;
2679 if (!g_unichar_isprint (next_ch))
2680 next_ch = '.';
2682 else
2683 #endif
2684 ch_res = dview_get_byte (buf + cnt, &next_ch);
2686 if (ch_res)
2688 #ifdef HAVE_CHARSET
2689 if (mc_global.utf8_display)
2691 if (!dview->utf8)
2692 next_ch =
2693 convert_from_8bit_to_utf_c ((unsigned char) next_ch, dview->converter);
2695 else if (dview->utf8)
2696 next_ch = convert_from_utf_to_current_c (next_ch, dview->converter);
2697 else
2698 next_ch = convert_to_display_c (next_ch);
2699 #endif
2701 tty_print_anychar (next_ch);
2702 col++;
2706 tty_setcolor (NORMAL_COLOR);
2707 k = width;
2708 if (width < xwidth - 1)
2709 k = xwidth - 1;
2710 fill_by_space (buf, k, TRUE);
2711 for (; j < height; j++)
2713 if (xwidth != 0)
2715 tty_gotoyx (r + j, c - xwidth);
2716 // tty_print_nstring (buf, xwidth - 1);
2717 tty_print_string (str_fit_to_term (buf, xwidth - 1, J_LEFT_FIT));
2719 tty_gotoyx (r + j, c);
2720 // tty_print_nstring (buf, width);
2721 tty_print_string (str_fit_to_term (buf, width, J_LEFT_FIT));
2724 return 0;
2727 /* --------------------------------------------------------------------------------------------- */
2729 static void
2730 dview_status (const WDiff *dview, diff_place_t ord, int width, int c)
2732 const char *buf;
2733 int filename_width;
2734 int linenum, lineofs;
2735 vfs_path_t *vpath;
2736 char *path;
2738 tty_setcolor (STATUSBAR_COLOR);
2740 tty_gotoyx (0, c);
2741 get_line_numbers (dview->a[ord], dview->skip_rows, &linenum, &lineofs);
2743 filename_width = width - 24;
2744 if (filename_width < 8)
2745 filename_width = 8;
2747 vpath = vfs_path_from_str (dview->label[ord]);
2748 path = vfs_path_to_str_flags (vpath, 0, VPF_STRIP_HOME | VPF_STRIP_PASSWORD);
2749 vfs_path_free (vpath, TRUE);
2750 buf = str_term_trim (path, filename_width);
2751 if (ord == DIFF_LEFT)
2752 tty_printf ("%s%-*s %6d+%-4d Col %-4d ", dview->merged[ord] ? "* " : " ", filename_width,
2753 buf, linenum, lineofs, dview->skip_cols);
2754 else
2755 tty_printf ("%s%-*s %6d+%-4d Dif %-4d ", dview->merged[ord] ? "* " : " ", filename_width,
2756 buf, linenum, lineofs, dview->ndiff);
2757 g_free (path);
2760 /* --------------------------------------------------------------------------------------------- */
2762 static void
2763 dview_redo (WDiff *dview)
2765 if (dview->display_numbers != 0)
2767 int old;
2769 old = dview->display_numbers;
2770 dview->display_numbers = calc_nwidth ((const GArray *const *) dview->a);
2771 dview->new_frame = (old != dview->display_numbers);
2773 dview_reread (dview);
2776 /* --------------------------------------------------------------------------------------------- */
2778 static void
2779 dview_update (WDiff *dview)
2781 int height = dview->height;
2782 int width1, width2;
2783 int last;
2785 last = dview->a[DIFF_LEFT]->len - 1;
2787 if (dview->skip_rows > last)
2788 dview->skip_rows = dview->search.last_accessed_num_line = last;
2789 if (dview->skip_rows < 0)
2790 dview->skip_rows = dview->search.last_accessed_num_line = 0;
2791 if (dview->skip_cols < 0)
2792 dview->skip_cols = 0;
2794 if (height < 2)
2795 return;
2797 // use an actual length of dview->a
2798 if (dview->display_numbers != 0)
2799 dview->display_numbers = calc_nwidth ((const GArray *const *) dview->a);
2801 width1 = dview->half1 + dview->bias;
2802 width2 = dview->half2 - dview->bias;
2803 if (dview->full)
2805 width1 = COLS;
2806 width2 = 0;
2809 if (dview->new_frame)
2811 int xwidth;
2813 tty_setcolor (NORMAL_COLOR);
2814 xwidth = dview->display_numbers;
2815 if (dview->display_symbols)
2816 xwidth++;
2817 if (width1 > 1)
2818 tty_draw_box (1, 0, height, width1, FALSE);
2819 if (width2 > 1)
2820 tty_draw_box (1, width1, height, width2, FALSE);
2822 if (xwidth != 0)
2824 xwidth++;
2825 if (xwidth < width1 - 1)
2827 tty_gotoyx (1, xwidth);
2828 tty_print_alt_char (ACS_TTEE, FALSE);
2829 tty_gotoyx (height, xwidth);
2830 tty_print_alt_char (ACS_BTEE, FALSE);
2831 tty_draw_vline (2, xwidth, ACS_VLINE, height - 2);
2833 if (xwidth < width2 - 1)
2835 tty_gotoyx (1, width1 + xwidth);
2836 tty_print_alt_char (ACS_TTEE, FALSE);
2837 tty_gotoyx (height, width1 + xwidth);
2838 tty_print_alt_char (ACS_BTEE, FALSE);
2839 tty_draw_vline (2, width1 + xwidth, ACS_VLINE, height - 2);
2842 dview->new_frame = FALSE;
2845 if (width1 > 2)
2847 dview_status (dview, dview->ord, width1, 0);
2848 dview_display_file (dview, dview->ord, 2, 1, height - 2, width1 - 2);
2850 if (width2 > 2)
2852 diff_place_t ord;
2854 ord = dview->ord == DIFF_LEFT ? DIFF_RIGHT : DIFF_LEFT;
2855 dview_status (dview, ord, width2, width1);
2856 dview_display_file (dview, ord, 2, width1 + 1, height - 2, width2 - 2);
2860 /* --------------------------------------------------------------------------------------------- */
2862 static void
2863 dview_edit (WDiff *dview, diff_place_t ord)
2865 Widget *h;
2866 gboolean h_modal;
2867 int linenum, lineofs;
2869 if (dview->dsrc == DATA_SRC_TMP)
2871 error_dialog (_ ("Edit"), _ ("Edit is disabled"));
2872 return;
2875 h = WIDGET (WIDGET (dview)->owner);
2876 h_modal = widget_get_state (h, WST_MODAL);
2878 get_line_numbers (dview->a[ord], dview->skip_rows, &linenum, &lineofs);
2880 // disallow edit file in several editors
2881 widget_set_state (h, WST_MODAL, TRUE);
2884 vfs_path_t *tmp_vpath;
2886 tmp_vpath = vfs_path_from_str (dview->file[ord]);
2887 edit_file_at_line (tmp_vpath, use_internal_edit, linenum);
2888 vfs_path_free (tmp_vpath, TRUE);
2891 widget_set_state (h, WST_MODAL, h_modal);
2892 dview_redo (dview);
2893 dview_update (dview);
2896 /* --------------------------------------------------------------------------------------------- */
2898 static void
2899 dview_goto_cmd (WDiff *dview, diff_place_t ord)
2901 static gboolean first_run = TRUE;
2903 static const char *title[2] = {
2904 N_ ("Goto line (left)"),
2905 N_ ("Goto line (right)"),
2908 int newline;
2909 char *input;
2911 input = input_dialog (_ (title[ord]), _ ("Enter line:"), MC_HISTORY_YDIFF_GOTO_LINE,
2912 first_run ? NULL : INPUT_LAST_TEXT, INPUT_COMPLETE_NONE);
2913 if (input != NULL)
2915 const char *s = input;
2917 if (scan_deci (&s, &newline) == 0 && *s == '\0')
2919 size_t i = 0;
2921 if (newline > 0)
2922 for (; i < dview->a[ord]->len; i++)
2924 const DIFFLN *p;
2926 p = &g_array_index (dview->a[ord], DIFFLN, i);
2927 if (p->line == newline)
2928 break;
2931 dview->skip_rows = dview->search.last_accessed_num_line = (ssize_t) i;
2934 g_free (input);
2937 first_run = FALSE;
2940 /* --------------------------------------------------------------------------------------------- */
2942 static void
2943 dview_labels (WDiff *dview)
2945 Widget *d = WIDGET (dview);
2946 WButtonBar *b;
2948 b = buttonbar_find (DIALOG (d->owner));
2950 buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), d->keymap, d);
2951 buttonbar_set_label (b, 2, Q_ ("ButtonBar|Save"), d->keymap, d);
2952 buttonbar_set_label (b, 4, Q_ ("ButtonBar|Edit"), d->keymap, d);
2953 buttonbar_set_label (b, 5, Q_ ("ButtonBar|Merge"), d->keymap, d);
2954 buttonbar_set_label (b, 7, Q_ ("ButtonBar|Search"), d->keymap, d);
2955 buttonbar_set_label (b, 9, Q_ ("ButtonBar|Options"), d->keymap, d);
2956 buttonbar_set_label (b, 10, Q_ ("ButtonBar|Quit"), d->keymap, d);
2959 /* --------------------------------------------------------------------------------------------- */
2961 static gboolean
2962 dview_save (WDiff *dview)
2964 gboolean res = TRUE;
2966 if (dview->merged[DIFF_LEFT])
2968 res = mc_util_unlink_backup_if_possible (dview->file[DIFF_LEFT], "~~~");
2969 dview->merged[DIFF_LEFT] = !res;
2971 if (dview->merged[DIFF_RIGHT])
2973 res = mc_util_unlink_backup_if_possible (dview->file[DIFF_RIGHT], "~~~");
2974 dview->merged[DIFF_RIGHT] = !res;
2976 return res;
2979 /* --------------------------------------------------------------------------------------------- */
2981 static void
2982 dview_do_save (WDiff *dview)
2984 (void) dview_save (dview);
2987 /* --------------------------------------------------------------------------------------------- */
2990 * Check if it's OK to close the diff viewer. If there are unsaved changes,
2991 * ask user.
2993 static gboolean
2994 dview_ok_to_exit (WDiff *dview)
2996 gboolean res = TRUE;
2997 int act;
2999 if (!dview->merged[DIFF_LEFT] && !dview->merged[DIFF_RIGHT])
3000 return res;
3002 act = query_dialog (_ ("Quit"),
3003 !mc_global.midnight_shutdown
3004 ? _ ("File(s) was modified. Save with exit?")
3005 : _ ("Midnight Commander is being shut down.\nSave modified file(s)?"),
3006 D_NORMAL, 2, _ ("&Yes"), _ ("&No"));
3008 // Esc is No
3009 if (mc_global.midnight_shutdown || (act == -1))
3010 act = 1;
3012 switch (act)
3014 case -1: // Esc
3015 res = FALSE;
3016 break;
3017 case 0: // Yes
3018 (void) dview_save (dview);
3019 res = TRUE;
3020 break;
3021 case 1: // No
3022 if (mc_util_restore_from_backup_if_possible (dview->file[DIFF_LEFT], "~~~"))
3023 res = mc_util_unlink_backup_if_possible (dview->file[DIFF_LEFT], "~~~");
3024 if (mc_util_restore_from_backup_if_possible (dview->file[DIFF_RIGHT], "~~~"))
3025 res = mc_util_unlink_backup_if_possible (dview->file[DIFF_RIGHT], "~~~");
3026 MC_FALLTHROUGH;
3027 default:
3028 res = TRUE;
3029 break;
3031 return res;
3034 /* --------------------------------------------------------------------------------------------- */
3036 static cb_ret_t
3037 dview_execute_cmd (WDiff *dview, long command)
3039 cb_ret_t res = MSG_HANDLED;
3041 switch (command)
3043 case CK_ShowSymbols:
3044 dview->display_symbols = !dview->display_symbols;
3045 dview->new_frame = TRUE;
3046 break;
3047 case CK_ShowNumbers:
3048 dview->display_numbers ^= calc_nwidth ((const GArray *const *) dview->a);
3049 dview->new_frame = TRUE;
3050 break;
3051 case CK_SplitFull:
3052 dview->full = !dview->full;
3053 dview->new_frame = TRUE;
3054 break;
3055 case CK_SplitEqual:
3056 if (!dview->full)
3058 dview->bias = 0;
3059 dview->new_frame = TRUE;
3061 break;
3062 case CK_SplitMore:
3063 if (!dview->full)
3065 dview_compute_split (dview, 1);
3066 dview->new_frame = TRUE;
3068 break;
3070 case CK_SplitLess:
3071 if (!dview->full)
3073 dview_compute_split (dview, -1);
3074 dview->new_frame = TRUE;
3076 break;
3077 case CK_Tab2:
3078 dview->tab_size = 2;
3079 break;
3080 case CK_Tab3:
3081 dview->tab_size = 3;
3082 break;
3083 case CK_Tab4:
3084 dview->tab_size = 4;
3085 break;
3086 case CK_Tab8:
3087 dview->tab_size = 8;
3088 break;
3089 case CK_Swap:
3090 dview->ord ^= 1;
3091 break;
3092 case CK_Redo:
3093 dview_redo (dview);
3094 break;
3095 case CK_HunkNext:
3096 dview->skip_rows = dview->search.last_accessed_num_line =
3097 find_next_hunk (dview->a[DIFF_LEFT], dview->skip_rows);
3098 break;
3099 case CK_HunkPrev:
3100 dview->skip_rows = dview->search.last_accessed_num_line =
3101 find_prev_hunk (dview->a[DIFF_LEFT], dview->skip_rows);
3102 break;
3103 case CK_Goto:
3104 dview_goto_cmd (dview, DIFF_RIGHT);
3105 break;
3106 case CK_Edit:
3107 dview_edit (dview, dview->ord);
3108 break;
3109 case CK_Merge:
3110 do_merge_hunk (dview, FROM_LEFT_TO_RIGHT);
3111 dview_redo (dview);
3112 break;
3113 case CK_MergeOther:
3114 do_merge_hunk (dview, FROM_RIGHT_TO_LEFT);
3115 dview_redo (dview);
3116 break;
3117 case CK_EditOther:
3118 dview_edit (dview, dview->ord ^ 1);
3119 break;
3120 case CK_Search:
3121 dview_search_cmd (dview);
3122 break;
3123 case CK_SearchContinue:
3124 dview_continue_search_cmd (dview);
3125 break;
3126 case CK_Top:
3127 dview->skip_rows = dview->search.last_accessed_num_line = 0;
3128 break;
3129 case CK_Bottom:
3130 dview->skip_rows = dview->search.last_accessed_num_line = dview->a[DIFF_LEFT]->len - 1;
3131 break;
3132 case CK_Up:
3133 if (dview->skip_rows > 0)
3135 dview->skip_rows--;
3136 dview->search.last_accessed_num_line = dview->skip_rows;
3138 break;
3139 case CK_Down:
3140 dview->skip_rows++;
3141 dview->search.last_accessed_num_line = dview->skip_rows;
3142 break;
3143 case CK_PageDown:
3144 if (dview->height > 2)
3146 dview->skip_rows += dview->height - 2;
3147 dview->search.last_accessed_num_line = dview->skip_rows;
3149 break;
3150 case CK_PageUp:
3151 if (dview->height > 2)
3153 dview->skip_rows -= dview->height - 2;
3154 dview->search.last_accessed_num_line = dview->skip_rows;
3156 break;
3157 case CK_Left:
3158 dview->skip_cols--;
3159 break;
3160 case CK_Right:
3161 dview->skip_cols++;
3162 break;
3163 case CK_LeftQuick:
3164 dview->skip_cols -= 8;
3165 break;
3166 case CK_RightQuick:
3167 dview->skip_cols += 8;
3168 break;
3169 case CK_Home:
3170 dview->skip_cols = 0;
3171 break;
3172 case CK_Shell:
3173 toggle_subshell ();
3174 break;
3175 case CK_Quit:
3176 dview->view_quit = TRUE;
3177 break;
3178 case CK_Save:
3179 dview_do_save (dview);
3180 break;
3181 case CK_Options:
3182 dview_diff_options (dview);
3183 break;
3184 #ifdef HAVE_CHARSET
3185 case CK_SelectCodepage:
3186 dview_select_encoding (dview);
3187 break;
3188 #endif
3189 case CK_Cancel:
3190 // don't close diffviewer due to SIGINT
3191 break;
3192 default:
3193 res = MSG_NOT_HANDLED;
3195 return res;
3198 /* --------------------------------------------------------------------------------------------- */
3200 static cb_ret_t
3201 dview_handle_key (WDiff *dview, int key)
3203 long command;
3205 #ifdef HAVE_CHARSET
3206 key = convert_from_input_c (key);
3207 #endif
3209 command = widget_lookup_key (WIDGET (dview), key);
3210 if (command == CK_IgnoreKey)
3211 return MSG_NOT_HANDLED;
3213 return dview_execute_cmd (dview, command);
3216 /* --------------------------------------------------------------------------------------------- */
3218 static cb_ret_t
3219 dview_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
3221 WDiff *dview = (WDiff *) w;
3222 WDialog *h = DIALOG (w->owner);
3223 cb_ret_t i;
3225 switch (msg)
3227 case MSG_INIT:
3228 dview_labels (dview);
3229 dview_update (dview);
3230 return MSG_HANDLED;
3232 case MSG_DRAW:
3233 dview->new_frame = TRUE;
3234 dview_update (dview);
3235 return MSG_HANDLED;
3237 case MSG_KEY:
3238 i = dview_handle_key (dview, parm);
3239 if (dview->view_quit)
3240 dlg_close (h);
3241 else
3242 dview_update (dview);
3243 return i;
3245 case MSG_ACTION:
3246 i = dview_execute_cmd (dview, parm);
3247 if (dview->view_quit)
3248 dlg_close (h);
3249 else
3250 dview_update (dview);
3251 return i;
3253 case MSG_RESIZE:
3254 widget_default_callback (w, NULL, MSG_RESIZE, 0, data);
3255 dview_compute_areas (dview);
3256 return MSG_HANDLED;
3258 case MSG_DESTROY:
3259 dview_save_options (dview);
3260 dview_fini (dview);
3261 return MSG_HANDLED;
3263 default:
3264 return widget_default_callback (w, sender, msg, parm, data);
3268 /* --------------------------------------------------------------------------------------------- */
3270 static void
3271 dview_mouse_callback (Widget *w, mouse_msg_t msg, mouse_event_t *event)
3273 WDiff *dview = (WDiff *) w;
3275 (void) event;
3277 switch (msg)
3279 case MSG_MOUSE_SCROLL_UP:
3280 case MSG_MOUSE_SCROLL_DOWN:
3281 if (msg == MSG_MOUSE_SCROLL_UP)
3282 dview->skip_rows -= 2;
3283 else
3284 dview->skip_rows += 2;
3286 dview->search.last_accessed_num_line = dview->skip_rows;
3287 dview_update (dview);
3288 break;
3290 default:
3291 break;
3295 /* --------------------------------------------------------------------------------------------- */
3297 static cb_ret_t
3298 dview_dialog_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
3300 WDiff *dview;
3301 WDialog *h = DIALOG (w);
3303 switch (msg)
3305 case MSG_ACTION:
3306 // Handle shortcuts.
3308 /* Note: the buttonbar sends messages directly to the the WDiff, not to
3309 * here, which is why we can pass NULL in the following call. */
3310 return dview_execute_cmd (NULL, parm);
3312 case MSG_VALIDATE:
3313 dview = (WDiff *) widget_find_by_type (CONST_WIDGET (h), dview_callback);
3314 // don't stop the dialog before final decision
3315 widget_set_state (w, WST_ACTIVE, TRUE);
3316 if (dview_ok_to_exit (dview))
3317 dlg_close (h);
3318 return MSG_HANDLED;
3320 default:
3321 return dlg_default_callback (w, sender, msg, parm, data);
3325 /* --------------------------------------------------------------------------------------------- */
3327 static char *
3328 dview_get_title (const WDialog *h, size_t len)
3330 const WDiff *dview;
3331 const char *modified = " (*) ";
3332 const char *notmodified = " ";
3333 size_t len1;
3334 GString *title;
3336 dview = (const WDiff *) widget_find_by_type (CONST_WIDGET (h), dview_callback);
3337 len1 = (len - str_term_width1 (_ ("Diff:")) - strlen (modified) - 3) / 2;
3339 title = g_string_sized_new (len);
3340 g_string_append (title, _ ("Diff:"));
3341 g_string_append (title, dview->merged[DIFF_LEFT] ? modified : notmodified);
3342 g_string_append (title, str_term_trim (dview->label[DIFF_LEFT], len1));
3343 g_string_append (title, " | ");
3344 g_string_append (title, dview->merged[DIFF_RIGHT] ? modified : notmodified);
3345 g_string_append (title, str_term_trim (dview->label[DIFF_RIGHT], len1));
3347 return g_string_free (title, FALSE);
3350 /* --------------------------------------------------------------------------------------------- */
3352 static int
3353 diff_view (const char *file1, const char *file2, const char *label1, const char *label2)
3355 int error;
3356 WDiff *dview;
3357 Widget *w;
3358 WDialog *dview_dlg;
3359 Widget *dw;
3360 WRect r;
3361 WGroup *g;
3363 // Create dialog and widgets, put them on the dialog
3364 dview_dlg = dlg_create (FALSE, 0, 0, 1, 1, WPOS_FULLSCREEN, FALSE, NULL, dview_dialog_callback,
3365 NULL, "[Diff Viewer]", NULL);
3366 dw = WIDGET (dview_dlg);
3367 widget_want_tab (dw, TRUE);
3368 r = dw->rect;
3370 g = GROUP (dview_dlg);
3372 dview = g_new0 (WDiff, 1);
3373 w = WIDGET (dview);
3374 r.lines--;
3375 widget_init (w, &r, dview_callback, dview_mouse_callback);
3376 w->options |= WOP_SELECTABLE;
3377 w->keymap = diff_map;
3378 group_add_widget_autopos (g, w, WPOS_KEEP_ALL, NULL);
3380 w = WIDGET (buttonbar_new ());
3381 group_add_widget_autopos (g, w, w->pos_flags, NULL);
3383 dview_dlg->get_title = dview_get_title;
3385 error =
3386 dview_init (dview, "-a", file1, file2, label1, label2, DATA_SRC_MEM); // XXX binary diff?
3387 if (error >= 0)
3388 error = redo_diff (dview);
3389 if (error >= 0)
3391 dview->ndiff = error;
3392 dview_compute_areas (dview);
3393 error = 0;
3396 if (error == 0)
3397 dlg_run (dview_dlg);
3399 if (error != 0 || widget_get_state (dw, WST_CLOSED))
3400 widget_destroy (dw);
3402 return error == 0 ? 1 : 0;
3405 /*** public functions ****************************************************************************/
3406 /* --------------------------------------------------------------------------------------------- */
3408 #define GET_FILE_AND_STAMP(n) \
3409 do \
3411 use_copy##n = 0; \
3412 real_file##n = file##n; \
3413 if (!vfs_file_is_local (file##n)) \
3415 real_file##n = mc_getlocalcopy (file##n); \
3416 if (real_file##n != NULL) \
3418 use_copy##n = 1; \
3419 if (mc_stat (real_file##n, &st##n) != 0) \
3420 use_copy##n = -1; \
3424 while (0)
3426 #define UNGET_FILE(n) \
3427 do \
3429 if (use_copy##n != 0) \
3431 gboolean changed = FALSE; \
3432 if (use_copy##n > 0) \
3434 time_t mtime; \
3435 mtime = st##n.st_mtime; \
3436 if (mc_stat (real_file##n, &st##n) == 0) \
3437 changed = (mtime != st##n.st_mtime); \
3439 mc_ungetlocalcopy (file##n, real_file##n, changed); \
3440 vfs_path_free (real_file##n, TRUE); \
3443 while (0)
3445 gboolean
3446 dview_diff_cmd (const void *f0, const void *f1)
3448 int rv = 0;
3449 vfs_path_t *file0 = NULL;
3450 vfs_path_t *file1 = NULL;
3451 gboolean is_dir0 = FALSE;
3452 gboolean is_dir1 = FALSE;
3454 switch (mc_global.mc_run_mode)
3456 case MC_RUN_FULL:
3458 // run from panels
3459 const WPanel *panel0 = (const WPanel *) f0;
3460 const WPanel *panel1 = (const WPanel *) f1;
3461 const file_entry_t *fe0, *fe1;
3463 fe0 = panel_current_entry (panel0);
3464 if (fe0 == NULL)
3466 message (D_ERROR, MSG_ERROR, "%s", _ ("File name is empty!"));
3467 goto ret;
3470 file0 = vfs_path_append_new (panel0->cwd_vpath, fe0->fname->str, (char *) NULL);
3471 is_dir0 = S_ISDIR (fe0->st.st_mode);
3472 if (is_dir0)
3474 message (D_ERROR, MSG_ERROR, _ ("\"%s\" is a directory"),
3475 path_trunc (fe0->fname->str, 30));
3476 goto ret;
3479 fe1 = panel_current_entry (panel1);
3480 if (fe1 == NULL)
3482 message (D_ERROR, MSG_ERROR, "%s", _ ("File name is empty!"));
3483 goto ret;
3485 file1 = vfs_path_append_new (panel1->cwd_vpath, fe1->fname->str, (char *) NULL);
3486 is_dir1 = S_ISDIR (fe1->st.st_mode);
3487 if (is_dir1)
3489 message (D_ERROR, MSG_ERROR, _ ("\"%s\" is a directory"),
3490 path_trunc (fe1->fname->str, 30));
3491 goto ret;
3493 break;
3496 case MC_RUN_DIFFVIEWER:
3498 // run from command line
3499 const char *p0 = (const char *) f0;
3500 const char *p1 = (const char *) f1;
3501 struct stat st;
3503 file0 = vfs_path_from_str (p0);
3504 if (mc_stat (file0, &st) == 0)
3506 is_dir0 = S_ISDIR (st.st_mode);
3507 if (is_dir0)
3509 message (D_ERROR, MSG_ERROR, _ ("\"%s\" is a directory"), path_trunc (p0, 30));
3510 goto ret;
3513 else
3515 message (D_ERROR, MSG_ERROR, _ ("Cannot stat \"%s\"\n%s"), path_trunc (p0, 30),
3516 unix_error_string (errno));
3517 goto ret;
3520 file1 = vfs_path_from_str (p1);
3521 if (mc_stat (file1, &st) == 0)
3523 is_dir1 = S_ISDIR (st.st_mode);
3524 if (is_dir1)
3526 message (D_ERROR, MSG_ERROR, _ ("\"%s\" is a directory"), path_trunc (p1, 30));
3527 goto ret;
3530 else
3532 message (D_ERROR, MSG_ERROR, _ ("Cannot stat \"%s\"\n%s"), path_trunc (p1, 30),
3533 unix_error_string (errno));
3534 goto ret;
3536 break;
3539 default:
3540 // this should not happened
3541 message (D_ERROR, MSG_ERROR, _ ("Diff viewer: invalid mode"));
3542 return FALSE;
3545 if (rv == 0)
3547 rv = -1;
3548 if (file0 != NULL && file1 != NULL)
3550 int use_copy0, use_copy1;
3551 struct stat st0, st1;
3552 vfs_path_t *real_file0, *real_file1;
3554 GET_FILE_AND_STAMP (0);
3555 GET_FILE_AND_STAMP (1);
3557 if (real_file0 != NULL && real_file1 != NULL)
3558 rv = diff_view (vfs_path_as_str (real_file0), vfs_path_as_str (real_file1),
3559 vfs_path_as_str (file0), vfs_path_as_str (file1));
3561 UNGET_FILE (1);
3562 UNGET_FILE (0);
3566 if (rv == 0)
3567 message (D_ERROR, MSG_ERROR, _ ("Two files are needed to compare"));
3569 ret:
3570 vfs_path_free (file1, TRUE);
3571 vfs_path_free (file0, TRUE);
3573 return (rv != 0);
3576 #undef GET_FILE_AND_STAMP
3577 #undef UNGET_FILE
3579 /* --------------------------------------------------------------------------------------------- */