diff --git a/configure.ac b/configure.ac index c5e1dabaa..f220f69b4 100644 --- a/configure.ac +++ b/configure.ac @@ -482,6 +482,21 @@ else edit_msg="no" fi +dnl +dnl Diff viewer support. +dnl +AC_ARG_WITH(diff_viewer, + [ --with-diff-viewer Compile with diff viewer [[yes]]]) + +if test x$with_diff_viewer != xno; then + AC_DEFINE(USE_DIFF_VIEW, 1, [Define to enable diff viewer]) + use_diff=yes + diff_msg="yes" + AC_MSG_NOTICE([using diff viewer]) +else + diff_msg="no" +fi + dnl Check if the OS is supported by the console saver. cons_saver="" @@ -555,6 +570,7 @@ fi AM_CONDITIONAL(USE_MAINTAINER_MODE, [test x"$USE_MAINTAINER_MODE" = xyes]) AM_CONDITIONAL(USE_SCREEN_SLANG, [test x"$with_screen" = xslang]) AM_CONDITIONAL(USE_EDIT, [test -n "$use_edit"]) +AM_CONDITIONAL(USE_DIFF, [test -n "$use_diff"]) AM_CONDITIONAL(ENABLE_VFS_NET, [test x"$use_net_code" = xtrue]) AM_CONDITIONAL(USE_SAMBA_FS, [test -n "$use_smbfs"]) AM_CONDITIONAL(ENABLE_MCSERVER, [test x"$enable_mcserver" = "xyes"]) @@ -582,6 +598,7 @@ src/Makefile src/consaver/Makefile src/editor/Makefile src/viewer/Makefile +src/diffviewer/Makefile lib/Makefile lib/filehighlight/Makefile @@ -662,6 +679,7 @@ Configuration: X11 events support: ${textmode_x11_support} With subshell support: ${subshell} Internal editor: ${edit_msg} + Diff viewer: ${diff_msg} Support for charset: ${charset_msg} Search type: ${SEARCH_TYPE} " diff --git a/lib/skin.h b/lib/skin.h index 7d1c561f9..05212f45e 100644 --- a/lib/skin.h +++ b/lib/skin.h @@ -71,7 +71,18 @@ #define BUTTONBAR_HOTKEY_COLOR mc_skin_color__cache[34] #define BUTTONBAR_BUTTON_COLOR mc_skin_color__cache[35] -#define MC_SKIN_COLOR_CACHE_COUNT 36 +/* Diff colors */ +#define DFF_ADD_COLOR mc_skin_color__cache[36] +#define DFF_CHG_COLOR mc_skin_color__cache[37] +#define DFF_CHH_COLOR mc_skin_color__cache[38] +#define DFF_CHD_COLOR mc_skin_color__cache[39] +#define DFF_DEL_COLOR mc_skin_color__cache[40] +#define DFF_FOLDER_COLOR mc_skin_color__cache[41] +#define DFF_ERROR_COLOR mc_skin_color__cache[42] + + + +#define MC_SKIN_COLOR_CACHE_COUNT 43 /*** enums ***************************************************************************************/ diff --git a/lib/skin/colors.c b/lib/skin/colors.c index e05c15d5c..b9df0695f 100644 --- a/lib/skin/colors.c +++ b/lib/skin/colors.c @@ -240,6 +240,14 @@ mc_skin_color_cache_init (void) BOOK_MARK_FOUND_COLOR = mc_skin_color_get ("editor", "bookmarkfound"); BUTTONBAR_HOTKEY_COLOR = mc_skin_color_get ("buttonbar", "hotkey"); BUTTONBAR_BUTTON_COLOR = mc_skin_color_get ("buttonbar", "button"); + + DFF_ADD_COLOR = mc_skin_color_get ("diffviewer", "added"); + DFF_CHG_COLOR = mc_skin_color_get ("diffviewer", "changedline"); + DFF_CHH_COLOR = mc_skin_color_get ("diffviewer", "changednew"); + DFF_CHD_COLOR = mc_skin_color_get ("diffviewer", "changed"); + DFF_DEL_COLOR = mc_skin_color_get ("diffviewer", "removed"); + DFF_FOLDER_COLOR = mc_skin_color_get ("diffviewer", "folder"); + DFF_ERROR_COLOR = mc_skin_color_get ("diffviewer", "error"); } /* --------------------------------------------------------------------------------------------- */ diff --git a/misc/mc.keymap.default b/misc/mc.keymap.default index 536beb727..4049a009a 100644 --- a/misc/mc.keymap.default +++ b/misc/mc.keymap.default @@ -255,6 +255,7 @@ CmdCopyCurrentPathname = p CmdCopyOtherPathname = ctrl-p CmdCopyCurrentTagged = t CmdCopyOtherTagged = ctrl-t +CmdDiffView = ctrl-y [panel] PanelStartSearch = ctrl-s; alt-s @@ -357,3 +358,39 @@ HelpNextLink = tab HelpPrevLink = alt-tab HelpNextNode = n HelpPrevNode = p + +[diffviewer] +DiffDisplaySymbols = alt-s; s +DiffDisplayNumbers = alt-n; l +DiffFull = f +DiffEqual = equal +DiffSplitMore = gt +DiffSplitLess = lt +DiffSetTab2 = 2 +DiffSetTab3 = 3 +DiffSetTab4 = 4 +DiffSetTab8 = 8 +DiffSwapPanel = ctrl-u +DiffRedo = ctrl-r +DiffNextHunk = n; enter; space +DiffPrevHunk = p; backspace +DiffGoto = g; shift-g +DiffEditCurrent = f4 +DiffEditOther = f14 +DiffMergeCurrentHunk = f5 +DiffSearch = f7 +DiffContinueSearch = f17 +DiffBOF = ctrl-home +DiffEOF = ctrl-end +DiffDown = down +DiffUp = up +DiffQuickLeft = ctrl-left +DiffQuickRight = ctrl-right +DiffLeft = left +DiffRight = right +DiffPageDown = pgdn +DiffPageUp = pgup +DiffHome = home +DiffEnd = end +DiffQuit = q; shift-q; ctrl-g; esc +ShowCommandLine = ctrl-o diff --git a/misc/mc.keymap.emacs b/misc/mc.keymap.emacs index 009d22b93..4c305cb2e 100644 --- a/misc/mc.keymap.emacs +++ b/misc/mc.keymap.emacs @@ -259,6 +259,7 @@ CmdCopyCurrentPathname = p CmdCopyOtherPathname = ctrl-p CmdCopyCurrentTagged = t CmdCopyOtherTagged = ctrl-t +CmdDiffView = ctrl-y [panel] PanelStartSearch = ctrl-s; alt-s @@ -361,3 +362,40 @@ HelpNextLink = tab HelpPrevLink = alt-tab HelpNextNode = n HelpPrevNode = p + +[diffviewer] +DiffDisplaySymbols = alt-s; s +DiffDisplayNumbers = alt-n; l +DiffFull = f +DiffEqual = equal +DiffSplitMore = gt +DiffSplitLess = lt +DiffSetTab2 = 2 +DiffSetTab3 = 3 +DiffSetTab4 = 4 +DiffSetTab8 = 8 +DiffSwapPanel = ctrl-u +DiffRedo = ctrl-r +DiffNextHunk = n; enter; space +DiffPrevHunk = p; backspace +DiffGoto = g; shift-g +DiffSave = f2 +DiffEditCurrent = f4 +DiffEditOther = f14 +DiffMergeCurrentHunk = f5 +DiffSearch = f7 +DiffContinueSearch = f17 +DiffBOF = ctrl-home +DiffEOF = ctrl-end +DiffDown = down +DiffUp = up +DiffQuickLeft = ctrl-left +DiffQuickRight = ctrl-right +DiffLeft = left +DiffRight = right +DiffPageDown = pgdn +DiffPageUp = pgup +DiffHome = home +DiffEnd = end +DiffQuit = q; shift-q; ctrl-g; esc +ShowCommandLine = ctrl-o diff --git a/misc/skins/darkfar.ini b/misc/skins/darkfar.ini index adca0e277..60f460a78 100644 --- a/misc/skins/darkfar.ini +++ b/misc/skins/darkfar.ini @@ -87,6 +87,15 @@ [viewer] viewunderline=brightred;black +[diffviewer] + added=white;green + changedline=blue;cyan + changednew=red;cyan + changed=white;cyan + removed=white;red + folder=blue;black + error=red;white + [buttonbar] hotkey=red;white button=black;white diff --git a/misc/skins/default.ini b/misc/skins/default.ini index f73213253..608672855 100644 --- a/misc/skins/default.ini +++ b/misc/skins/default.ini @@ -91,6 +91,15 @@ [viewer] viewunderline=brightred;blue +[diffviewer] + added=white;green + changedline=blue;cyan + changednew=red;cyan + changed=white;cyan + removed=white;red + folder=blue;black + error=red;white + [widget-common] sort-sign-up = ' sort-sign-down = , diff --git a/misc/skins/double-lines.ini b/misc/skins/double-lines.ini index e389fee99..5f837a268 100644 --- a/misc/skins/double-lines.ini +++ b/misc/skins/double-lines.ini @@ -87,6 +87,15 @@ [viewer] viewunderline=brightred;blue +[diffviewer] + added=white;green + changedline=blue;cyan + changednew=red;cyan + changed=white;cyan + removed=white;red + folder=blue;black + error=red;white + [widget-common] sort-sign-up = ' sort-sign-down = , diff --git a/misc/skins/featured.ini b/misc/skins/featured.ini index 9adcf1bc5..7cfc8a85d 100644 --- a/misc/skins/featured.ini +++ b/misc/skins/featured.ini @@ -93,6 +93,15 @@ [viewer] viewunderline=brightred;blue +[diffviewer] + added=white;green + changedline=blue;cyan + changednew=red;cyan + changed=white;cyan + removed=white;red + folder=blue;black + error=red;white + [widget-common] sort-sign-up = ↓ sort-sign-down = ↑ diff --git a/misc/skins/gotar.ini b/misc/skins/gotar.ini index c2d7fc4a9..3e93670ea 100644 --- a/misc/skins/gotar.ini +++ b/misc/skins/gotar.ini @@ -87,6 +87,15 @@ [viewer] viewunderline=brightgreen;black +[diffviewer] + added=white;green + changedline=blue;cyan + changednew=red;cyan + changed=white;cyan + removed=white;red + folder=blue;black + error=red;white + [buttonbar] hotkey=lightgray;black button=white;blue diff --git a/src/Makefile.am b/src/Makefile.am index 4dce0cc8f..6d87a3952 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,6 +4,9 @@ if USE_EDIT SUBDIRS += editor endif +if USE_DIFF +SUBDIRS += diffviewer +endif AM_CPPFLAGS = -DDATADIR=\""$(pkgdatadir)/"\" \ -DLOCALEDIR=\""$(localedir)"\" \ @@ -36,16 +39,22 @@ if USE_EDIT EDITLIB = editor/libedit.la endif +if USE_DIFF +DIFFLIB = diffviewer/libdiffviewer.la +endif + SRC_charset = charsets.c charsets.h selcodepage.c selcodepage.h if CHARSET SRC_USE_charset=$(SRC_charset) endif + mc_LDADD = \ ../lib/libmc.la \ - $(EDITLIB) \ viewer/libmcviewer.la \ + $(EDITLIB) \ + $(DIFFLIB) \ $(INTLLIBS) $(MCLIBS) $(SLANGLIB) $(LIBICONV) \ $(GLIB_LIBS) $(PCRE_LIBS) diff --git a/src/cmd.c b/src/cmd.c index f07834c6d..15d8e8811 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -946,6 +946,14 @@ compare_dirs_cmd (void) } } +#ifdef USE_DIFF_VIEW +void +diff_view_cmd (void) +{ + view_diff_cmd (NULL); +} +#endif + void history_cmd (void) { diff --git a/src/cmd.h b/src/cmd.h index d289d29df..7c25b3b48 100644 --- a/src/cmd.h +++ b/src/cmd.h @@ -43,6 +43,7 @@ void edit_mc_menu_cmd (void); void edit_fhl_cmd (void); void quick_chdir_cmd (void); void compare_dirs_cmd (void); +void diff_view_cmd (void); void history_cmd (void); void tree_cmd (void); void link_cmd (void); diff --git a/src/cmddef.h b/src/cmddef.h index e25deb222..deb11457c 100644 --- a/src/cmddef.h +++ b/src/cmddef.h @@ -367,6 +367,7 @@ #define CK_HelpCmd 7072 #define CK_MenuCmd 7073 #define CK_TogglePanelsSplit 7074 +#define CK_DiffViewCmd 7075 /* panels */ #define CK_PanelChdirOtherPanel 8001 @@ -409,6 +410,40 @@ #define CK_PanelSortOrderBySize 8038 #define CK_PanelSortOrderByMTime 8039 +/* diff viewer */ +#define CK_DiffDisplaySymbols 9001 +#define CK_DiffDisplayNumbers 9002 +#define CK_DiffFull 9003 +#define CK_DiffEqual 9004 +#define CK_DiffSplitMore 9005 +#define CK_DiffSplitLess 9006 +#define CK_DiffShowDiff 9008 +#define CK_DiffSetTab2 9009 +#define CK_DiffSetTab3 9010 +#define CK_DiffSetTab4 9011 +#define CK_DiffSetTab8 9012 +#define CK_DiffSwapPanel 9013 +#define CK_DiffRedo 9014 +#define CK_DiffNextHunk 9015 +#define CK_DiffPrevHunk 9016 +#define CK_DiffGoto 9017 +#define CK_DiffEditCurrent 9018 +#define CK_DiffEditOther 9019 +#define CK_DiffSearch 9020 +#define CK_DiffEOF 9021 +#define CK_DiffBOF 9022 +#define CK_DiffDown 9023 +#define CK_DiffUp 9024 +#define CK_DiffLeft 9025 +#define CK_DiffRight 9026 +#define CK_DiffQuickLeft 9027 +#define CK_DiffQuickRight 9028 +#define CK_DiffPageDown 9029 +#define CK_DiffPageUp 9030 +#define CK_DiffHome 9031 +#define CK_DiffEnd 9032 +#define CK_DiffQuit 9033 + /* Process a block through a shell command: CK_Pipe_Block(i) executes shell_cmd[i]. shell_cmd[i] must process the file ~/cooledit.block and output ~/cooledit.block diff --git a/src/diffviewer/Makefile.am b/src/diffviewer/Makefile.am new file mode 100644 index 000000000..7d1342801 --- /dev/null +++ b/src/diffviewer/Makefile.am @@ -0,0 +1,9 @@ +noinst_LTLIBRARIES = libdiffviewer.la + +libdiffviewer_la_SOURCES = \ + ydiff.c \ + ydiff.h + +libdiffviewer_la_CFLAGS = -I$(top_srcdir) \ + $(GLIB_CFLAGS) $(PCRE_CFLAGS) \ + -DDATADIR=\""$(pkgdatadir)/"\" -DLOCALEDIR=\""$(localedir)"\" diff --git a/src/diffviewer/ydiff.c b/src/diffviewer/ydiff.c new file mode 100644 index 000000000..5ebfac72f --- /dev/null +++ b/src/diffviewer/ydiff.c @@ -0,0 +1,2974 @@ +/* + Copyright (C) 2007 Free Software Foundation, Inc. + Written by: + Daniel Borca , 2007. + + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + + +#include +#include +#include +#include +#include +#include + +#include "src/cmddef.h" +#include "src/keybind.h" +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "src/cmd.h" +#include "src/dialog.h" +#include "src/widget.h" +#include "lib/tty/color.h" +#include "src/help.h" +#include "lib/tty/key.h" +#include "src/wtools.h" +#include "src/charsets.h" +#include "src/history.h" +#include "src/panel.h" /* Needed for current_panel and other_panel */ +#include "src/layout.h" /* Needed for get_current_index and get_other_panel */ +#include "lib/skin.h" /* EDITOR_NORMAL_COLOR */ +#include "lib/vfs/mc-vfs/vfs.h" /* mc_opendir, mc_readdir, mc_closedir, */ + /* mc_open, mc_close, mc_read, mc_stat */ +#include "src/main.h" /* mc_run_mode */ +#include "ydiff.h" + +#ifdef USE_DIFF_VIEW + +const global_keymap_t *diff_map; + +typedef struct { + int len, max; + void *data; + int error; + int eltsize; + int growth; +} ARRAY; + +#define FILE_READ_BUF 4096 +#define FILE_FLAG_TEMP (1 << 0) + +#define OPTX 50 +#define OPTY 16 + +#define SEARCH_DLG_WIDTH 58 +#define SEARCH_DLG_HEIGHT 14 + +typedef struct { + int fd; + int pos; + int len; + char *buf; + int flags; + void *data; +} FBUF; + +#define ADD_CH '+' +#define DEL_CH '-' +#define CHG_CH '*' +#define EQU_CH ' ' + +typedef struct { + int a[2][2]; + int cmd; +} DIFFCMD; + +typedef int (*DFUNC) (void *ctx, int ch, int line, off_t off, size_t sz, const char *str); + +#define HDIFF_ENABLE 1 +#define HDIFF_MINCTX 5 +#define HDIFF_DEPTH 10 + +typedef struct { + int off; + int len; +} BRACKET[2]; + +typedef int PAIR[2]; + +#define TAB_SKIP(ts, pos) ((ts) - (pos) % (ts)) + +typedef enum { + DATA_SRC_MEM = 0, + DATA_SRC_TMP = 1, + DATA_SRC_ORG = 2 +} DSRC; + +typedef struct { + int ch; + int line; + union { + off_t off; + size_t len; + } u; + void *p; +} DIFFLN; + +typedef struct { + FBUF *f; + ARRAY *a; + DSRC dsrc; +} PRINTER_CTX; + +typedef struct { + Widget widget; + + const char *args; /* Args passed to diff */ + const char *file[2]; /* filenames */ + const char *label[2]; + FBUF *f[2]; + ARRAY a[2]; + ARRAY **hdiff; + int ndiff; /* number of hunks */ + DSRC dsrc; /* data source: memory or temporary file */ + + int view_quit:1; /* Quit flag */ + + int height; + int half1; + int half2; + int width1; + int width2; + int bias; + int new_frame; + int skip_rows; + int skip_cols; + int display_symbols; + int display_numbers; + int show_cr; + int tab_size; + int ord; + int full; + int last_found; + + struct { + int quality; + int strip_trailing_cr; + int ignore_tab_expansion; + int ignore_space_change; + int ignore_all_space; + int ignore_case; + } opt; +} WDiff; + +static const char *wholechars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"; + +#define error_dialog(h, s) query_dialog(h, s, D_ERROR, 1, _("&Dismiss")) + +/* array *********************************************************************/ + + +/** + * Initialize array. + * + * \param a array, must be non-NULL + * \param eltsize element size + * \param growth growth constant + */ +static void +arr_init (ARRAY * a, int eltsize, int growth) +{ + a->len = a->max = 0; + a->data = NULL; + a->error = 0; + a->eltsize = eltsize; + a->growth = growth; +} + + +/** + * Reset array length without dealocating storage. + * + * \param a array, must be non-NULL + */ +static void +arr_reset (ARRAY * a) +{ + if (a->error) { + return; + } + a->len = 0; +} + + +/** + * Enlarge array. + * + * \param a array, must be non-NULL + * + * \return new element, or NULL if error + */ +static void * +arr_enlarge (ARRAY * a) +{ + void *p; + if (a->error) { + return NULL; + } + if (a->len == a->max) { + int max = a->max + a->growth; + p = realloc (a->data, max * a->eltsize); + if (p == NULL) { + a->error = 1; + return NULL; + } + a->max = max; + a->data = p; + } + p = (void *) ((char *) a->data + a->eltsize * a->len++); + return p; +} + + +/** + * Free array. + * + * \param a array, must be non-NULL + * \param func function to be called on each element + */ +static void +arr_free (ARRAY * a, void (*func) (void *)) +{ + if (func != NULL) { + int i; + for (i = 0; i < a->len; i++) { + func ((void *) ((char *) a->data + a->eltsize * i)); + } + } + free (a->data); + arr_init (a, a->eltsize, a->growth); +} + + +/* buffered I/O **************************************************************/ + + +#define FILE_DIRTY(fs) \ + do { \ + (fs)->pos = 0; \ + (fs)->len = 0; \ + } while (0) + + +/** + * Try to open a temporary file. + * + * \param[out] name address of a pointer to store the temporary name + * + * \return file descriptor on success, negative on error + * + * \note the name is not altered if this function fails + */ + +static int +open_temp (void **name) +{ + int fd; + char *diff_file_name = NULL; + + fd = mc_mkstemps (&diff_file_name, "mcdiff", NULL); + if (fd == -1) { + message (D_ERROR, MSG_ERROR, + _(" Cannot create temporary diff file \n %s "), + unix_error_string (errno)); + return -1; + } + *name = diff_file_name; + return fd; +} + + +/** + * Alocate file structure and associate file descriptor to it. + * + * \param fd file descriptor + * + * \return file structure + */ +static FBUF * +f_dopen (int fd) +{ + FBUF *fs; + + if (fd < 0) { + return NULL; + } + + fs = malloc (sizeof (FBUF)); + if (fs == NULL) { + return NULL; + } + + fs->buf = malloc (FILE_READ_BUF); + if (fs->buf == NULL) { + free (fs); + return NULL; + } + + fs->fd = fd; + FILE_DIRTY (fs); + fs->flags = 0; + fs->data = NULL; + + return fs; +} + + +/** + * Free file structure without closing the file. + * + * \param fs file structure + * + * \return 0 on success, non-zero on error + */ +static int +f_free (FBUF * fs) +{ + int rv = 0; + if (fs->flags & FILE_FLAG_TEMP) { + rv = unlink (fs->data); + free (fs->data); + } + free (fs->buf); + free (fs); + return rv; +} + + +/** + * Open a binary temporary file in R/W mode. + * + * \return file structure + * + * \note the file will be deleted when closed + */ +static FBUF * +f_temp (void) +{ + int fd; + FBUF *fs; + + fs = f_dopen (0); + if (fs == NULL) { + return NULL; + } + + fd = open_temp (&fs->data); + if (fd < 0) { + f_free (fs); + return NULL; + } + + fs->fd = fd; + fs->flags = FILE_FLAG_TEMP; + return fs; +} + + +/** + * Open a binary file in specified mode. + * + * \param filename file name + * \param flags open mode, a combination of O_RDONLY, O_WRONLY, O_RDWR + * + * \return file structure + */ +static FBUF * +f_open (const char *filename, int flags) +{ + int fd; + FBUF *fs; + + fs = f_dopen (0); + if (fs == NULL) { + return NULL; + } + + fd = open (filename, flags); + if (fd < 0) { + f_free (fs); + return NULL; + } + + fs->fd = fd; + return fs; +} + + +/** + * Read a line of bytes from file until newline or EOF. + * + * \param buf destination buffer + * \param size size of buffer + * \param fs file structure + * + * \return number of bytes read + * + * \note does not stop on null-byte + * \note buf will not be null-terminated + */ +static size_t +f_gets (char *buf, size_t size, FBUF * fs) +{ + size_t j = 0; + + do { + int i; + int stop = 0; + + for (i = fs->pos; j < size && i < fs->len && !stop; i++, j++) { + buf[j] = fs->buf[i]; + if (buf[j] == '\n') { + stop = 1; + } + } + fs->pos = i; + + if (j == size || stop) { + break; + } + + fs->pos = 0; + fs->len = read (fs->fd, fs->buf, FILE_READ_BUF); + } while (fs->len > 0); + + return j; +} + + +/** + * Read one character from file. + * + * \param fs file structure + * + * \return character + */ +static int +f_getc (FBUF * fs) +{ + do { + if (fs->pos < fs->len) { + return (unsigned char) fs->buf[fs->pos++]; + } + + fs->pos = 0; + fs->len = read (fs->fd, fs->buf, FILE_READ_BUF); + } while (fs->len > 0); + + return -1; +} + + +/** + * Seek into file. + * + * \param fs file structure + * \param off offset + * \param whence seek directive: SEEK_SET, SEEK_CUR or SEEK_END + * + * \return position in file, starting from begginning + * + * \note avoids thrashing read cache when possible + */ +static off_t +f_seek (FBUF * fs, off_t off, int whence) +{ + off_t rv; + + if (fs->len && whence != SEEK_END) { + rv = lseek (fs->fd, 0, SEEK_CUR); + if (rv != -1) { + if (whence == SEEK_CUR) { + whence = SEEK_SET; + off += rv - fs->len + fs->pos; + } + if (off - rv >= -fs->len && off - rv <= 0) { + fs->pos = fs->len + off - rv; + return off; + } + } + } + + rv = lseek (fs->fd, off, whence); + if (rv != -1) { + FILE_DIRTY (fs); + } + return rv; +} + + +/** + * Seek to the beginning of file, thrashing read cache. + * + * \param fs file structure + * + * \return 0 if success, non-zero on error + */ +static off_t +f_reset (FBUF * fs) +{ + off_t rv = lseek (fs->fd, 0, SEEK_SET); + if (rv != -1) { + FILE_DIRTY (fs); + } + return rv; +} + + +/** + * Write bytes to file. + * + * \param fs file structure + * \param buf source buffer + * \param size size of buffer + * + * \return number of written bytes, -1 on error + * + * \note thrashes read cache + */ +static ssize_t +f_write (FBUF * fs, const char *buf, size_t size) +{ + ssize_t rv = write (fs->fd, buf, size); + if (rv >= 0) { + FILE_DIRTY (fs); + } + return rv; +} + + +/** + * Truncate file to the current position. + * + * \param fs file structure + * + * \return current file size on success, negative on error + * + * \note thrashes read cache + */ +static off_t +f_trunc (FBUF * fs) +{ + off_t off = lseek (fs->fd, 0, SEEK_CUR); + if (off != -1) { + int rv = ftruncate (fs->fd, off); + if (rv != 0) { + off = -1; + } else { + FILE_DIRTY (fs); + } + } + return off; +} + + +/** + * Close file. + * + * \param fs file structure + * + * \return 0 on success, non-zero on error + * + * \note if this is temporary file, it is deleted + */ +static int +f_close (FBUF * fs) +{ + int rv = close (fs->fd); + f_free (fs); + return rv; +} + + +/** + * Create pipe stream to process. + * + * \param cmd shell command line + * \param flags open mode, either O_RDONLY or O_WRONLY + * + * \return file structure + */ +static FBUF * +p_open (const char *cmd, int flags) +{ + FILE *f; + FBUF *fs; + const char *type = NULL; + + if (flags == O_RDONLY) { + type = "r"; + } + if (flags == O_WRONLY) { + type = "w"; + } + + if (type == NULL) { + return NULL; + } + + fs = f_dopen (0); + if (fs == NULL) { + return NULL; + } + + f = popen (cmd, type); + if (f == NULL) { + f_free (fs); + return NULL; + } + + fs->fd = fileno (f); + fs->data = f; + return fs; +} + + +/** + * Close pipe stream. + * + * \param fs structure + * + * \return 0 on success, non-zero on error + */ +static int +p_close (FBUF * fs) +{ + int rv = pclose (fs->data); + f_free (fs); + return rv; +} + + +/* diff parse ****************************************************************/ + + +/** + * Read decimal number from string. + * + * \param[in,out] str string to parse + * \param[out] n extracted number + * + * \return 0 if success, otherwise non-zero + */ +static int +scan_deci (const char **str, int *n) +{ + const char *p = *str; + char *q; + errno = 0; + *n = strtol (p, &q, 10); + if (errno || p == q) { + return -1; + } + *str = q; + return 0; +} + + +/** + * Parse line for diff statement. + * + * \param p string to parse + * \param ops list of diff statements + * + * \return 0 if success, otherwise non-zero + */ +static int +scan_line (const char *p, ARRAY * ops) +{ + DIFFCMD *op; + + int f1, f2; + int t1, t2; + int cmd; + + int range; + + /* handle the following cases: + * NUMaNUM[,NUM] + * NUM[,NUM]cNUM[,NUM] + * NUM[,NUM]dNUM + * where NUM is a positive integer + */ + + if (scan_deci (&p, &f1) != 0 || f1 < 0) { + return -1; + } + f2 = f1; + range = 0; + if (*p == ',') { + p++; + if (scan_deci (&p, &f2) != 0 || f2 < f1) { + return -1; + } + range = 1; + } + + cmd = *p++; + if (cmd == 'a') { + if (range) { + return -1; + } + } else if (cmd != 'c' && cmd != 'd') { + return -1; + } + + if (scan_deci (&p, &t1) != 0 || t1 < 0) { + return -1; + } + t2 = t1; + range = 0; + if (*p == ',') { + p++; + if (scan_deci (&p, &t2) != 0 || t2 < t1) { + return -1; + } + range = 1; + } + + if (cmd == 'd') { + if (range) { + return -1; + } + } + + op = arr_enlarge (ops); + if (op == NULL) { + return -1; + } + op->a[0][0] = f1; + op->a[0][1] = f2; + op->cmd = cmd; + op->a[1][0] = t1; + op->a[1][1] = t2; + return 0; +} + + +/** + * Parse diff output and extract diff statements. + * + * \param f stream to read from + * \param ops list of diff statements to fill + * + * \return positive number indicating number of hunks, otherwise negative + */ +static int +scan_diff (FBUF * f, ARRAY * ops) +{ + int sz; + char buf[BUFSIZ]; + + while ((sz = f_gets (buf, sizeof (buf) - 1, f))) { + if (isdigit (buf[0])) { + if (buf[sz - 1] != '\n') { + return -1; + } + buf[sz] = '\0'; + if (scan_line (buf, ops) != 0) { + return -1; + } + continue; + } + while (buf[sz - 1] != '\n' && (sz = f_gets (buf, sizeof (buf), f))) { + } + } + + return ops->len; +} + + +/** + * Invoke diff and extract diff statements. + * + * \param args extra arguments to be passed to diff + * \param extra more arguments to be passed to diff + * \param file1 first file to compare + * \param file2 second file to compare + * \param ops list of diff statements to fill + * + * \return positive number indicating number of hunks, otherwise negative + */ +static int +dff_execute (const char *args, const char *extra, const char *file1, const char *file2, ARRAY * ops) +{ + static const char *opt = + " --old-group-format='%df%(f=l?:,%dl)d%dE\n'" + " --new-group-format='%dea%dF%(F=L?:,%dL)\n'" + " --changed-group-format='%df%(f=l?:,%dl)c%dF%(F=L?:,%dL)\n'" + " --unchanged-group-format=''"; + + int rv; + FBUF *f; + char *cmd; + int code; + + cmd = + malloc (14 + strlen (args) + strlen (extra) + strlen (opt) + strlen (file1) + + strlen (file2)); + if (cmd == NULL) { + return -1; + } + sprintf (cmd, "diff %s %s %s \"%s\" \"%s\"", args, extra, opt, file1, file2); + + f = p_open (cmd, O_RDONLY); + free (cmd); + if (f == NULL) { + return -1; + } + + arr_init (ops, sizeof (DIFFCMD), 64); + rv = scan_diff (f, ops); + code = p_close (f); + + if (rv < 0 || code == -1 || !WIFEXITED (code) || WEXITSTATUS (code) == 2) { + arr_free (ops, NULL); + return -1; + } + + return rv; +} + + +/** + * Reparse and display file according to diff statements. + * + * \param ord 0 if displaying first file, 1 if displaying 2nd file + * \param filename file name to display + * \param ops list of diff statements + * \param printer printf-like function to be used for displaying + * \param ctx printer context + * + * \return 0 if success, otherwise non-zero + */ +static int +dff_reparse (int ord, const char *filename, const ARRAY * ops, DFUNC printer, void *ctx) +{ + int i; + FBUF *f; + size_t sz; + char buf[BUFSIZ]; + int line = 0; + off_t off = 0; + const DIFFCMD *op; + int eff, tee; + int add_cmd; + int del_cmd; + + f = f_open (filename, O_RDONLY); + if (f == NULL) { + return -1; + } + + ord &= 1; + eff = ord; + tee = ord ^ 1; + + add_cmd = 'a'; + del_cmd = 'd'; + if (ord) { + add_cmd = 'd'; + del_cmd = 'a'; + } +#define F1 a[eff][0] +#define F2 a[eff][1] +#define T1 a[tee][0] +#define T2 a[tee][1] + for (op = ops->data, i = 0; i < ops->len; i++, op++) { + int n = op->F1 - (op->cmd != add_cmd); + while (line < n && (sz = f_gets (buf, sizeof (buf), f))) { + line++; + printer (ctx, EQU_CH, line, off, sz, buf); + off += sz; + while (buf[sz - 1] != '\n') { + if (!(sz = f_gets (buf, sizeof (buf), f))) { + printer (ctx, 0, 0, 0, 1, "\n"); + break; + } + printer (ctx, 0, 0, 0, sz, buf); + off += sz; + } + } + if (line != n) { + goto err; + } + + if (op->cmd == add_cmd) { + n = op->T2 - op->T1 + 1; + while (n) { + printer (ctx, DEL_CH, 0, 0, 1, "\n"); + n--; + } + } + if (op->cmd == del_cmd) { + n = op->F2 - op->F1 + 1; + while (n && (sz = f_gets (buf, sizeof (buf), f))) { + line++; + printer (ctx, ADD_CH, line, off, sz, buf); + off += sz; + while (buf[sz - 1] != '\n') { + if (!(sz = f_gets (buf, sizeof (buf), f))) { + printer (ctx, 0, 0, 0, 1, "\n"); + break; + } + printer (ctx, 0, 0, 0, sz, buf); + off += sz; + } + n--; + } + if (n) { + goto err; + } + } + if (op->cmd == 'c') { + n = op->F2 - op->F1 + 1; + while (n && (sz = f_gets (buf, sizeof (buf), f))) { + line++; + printer (ctx, CHG_CH, line, off, sz, buf); + off += sz; + while (buf[sz - 1] != '\n') { + if (!(sz = f_gets (buf, sizeof (buf), f))) { + printer (ctx, 0, 0, 0, 1, "\n"); + break; + } + printer (ctx, 0, 0, 0, sz, buf); + off += sz; + } + n--; + } + if (n) { + goto err; + } + n = op->T2 - op->T1 - (op->F2 - op->F1); + while (n > 0) { + printer (ctx, CHG_CH, 0, 0, 1, "\n"); + n--; + } + } + } +#undef T2 +#undef T1 +#undef F2 +#undef F1 + + while ((sz = f_gets (buf, sizeof (buf), f))) { + line++; + printer (ctx, EQU_CH, line, off, sz, buf); + off += sz; + while (buf[sz - 1] != '\n') { + if (!(sz = f_gets (buf, sizeof (buf), f))) { + printer (ctx, 0, 0, 0, 1, "\n"); + break; + } + printer (ctx, 0, 0, 0, sz, buf); + off += sz; + } + } + + f_close (f); + return 0; + + err: + f_close (f); + return -1; +} + + +/* horizontal diff ***********************************************************/ + + +/** + * Longest common substring. + * + * \param s first string + * \param m length of first string + * \param t second string + * \param n length of second string + * \param ret list of offsets for longest common substrings inside each string + * \param min minimum length of common substrings + * + * \return 0 if success, nonzero otherwise + */ +static int +lcsubstr (const char *s, int m, const char *t, int n, ARRAY * ret, int min) +{ + int i, j; + + int *Lprev, *Lcurr; + + int z = 0; + + arr_init (ret, sizeof (PAIR), 4); + + if (m < min || n < min) { + /* XXX early culling */ + return 0; + } + + Lprev = calloc (n + 1, sizeof (int)); + if (Lprev == NULL) { + goto err_0; + } + Lcurr = calloc (n + 1, sizeof (int)); + if (Lcurr == NULL) { + goto err_1; + } + + for (i = 0; i < m; i++) { + int *L = Lprev; + Lprev = Lcurr; + Lcurr = L; +#ifdef USE_MEMSET_IN_LCS + memset (Lcurr, 0, (n + 1) * sizeof (int)); +#endif + for (j = 0; j < n; j++) { +#ifndef USE_MEMSET_IN_LCS + Lcurr[j + 1] = 0; +#endif + if (s[i] == t[j]) { + int v = Lprev[j] + 1; + Lcurr[j + 1] = v; + if (z < v) { + z = v; + arr_reset (ret); + } + if (z == v && z >= min) { + int off0 = i - z + 1; + int off1 = j - z + 1; + int k; + PAIR *p; + for (p = ret->data, k = 0; k < ret->len; k++, p++) { + if ((*p)[0] == off0) { + break; + } + if ((*p)[1] == off1) { + break; + } + } + if (k == ret->len) { + p = arr_enlarge (ret); + if (p == NULL) { + goto err_2; + } + (*p)[0] = off0; + (*p)[1] = off1; + } + } + } + } + } + + free (Lcurr); + free (Lprev); + return z; + + err_2: + free (Lcurr); + err_1: + free (Lprev); + err_0: + arr_free (ret, NULL); + return -1; +} + + +/** + * Scan recursively for common substrings and build ranges. + * + * \param s first string + * \param t second string + * \param bracket current limits for both of the strings + * \param min minimum length of common substrings + * \param hdiff list of horizontal diff ranges to fill + * \param depth recursion depth + * + * \return 0 if success, nonzero otherwise + */ +static int +hdiff_multi (const char *s, const char *t, const BRACKET bracket, int min, ARRAY * hdiff, + unsigned int depth) +{ + BRACKET *p; + + if (depth--) { + ARRAY ret; + BRACKET b; + int len = lcsubstr (s + bracket[0].off, bracket[0].len, + t + bracket[1].off, bracket[1].len, &ret, min); + if (ret.len) { + int k = 0; + const PAIR *data = ret.data; + + b[0].off = bracket[0].off; + b[0].len = data[k][0]; + b[1].off = bracket[1].off; + b[1].len = data[k][1]; + hdiff_multi (s, t, b, min, hdiff, depth); + + for (k = 0; k < ret.len - 1; k++) { + b[0].off = bracket[0].off + data[k][0] + len; + b[0].len = data[k + 1][0] - data[k][0] - len; + b[1].off = bracket[1].off + data[k][1] + len; + b[1].len = data[k + 1][1] - data[k][1] - len; + hdiff_multi (s, t, b, min, hdiff, depth); + } + + b[0].off = bracket[0].off + data[k][0] + len; + b[0].len = bracket[0].len - data[k][0] - len; + b[1].off = bracket[1].off + data[k][1] + len; + b[1].len = bracket[1].len - data[k][1] - len; + hdiff_multi (s, t, b, min, hdiff, depth); + + arr_free (&ret, NULL); + return 0; + } + } + + p = arr_enlarge (hdiff); + if (p == NULL) { + return -1; + } + (*p)[0].off = bracket[0].off; + (*p)[0].len = bracket[0].len; + (*p)[1].off = bracket[1].off; + (*p)[1].len = bracket[1].len; + + return 0; +} + + +/** + * Build list of horizontal diff ranges. + * + * \param s first string + * \param m length of first string + * \param t second string + * \param n length of second string + * \param min minimum length of common substrings + * \param hdiff list of horizontal diff ranges to fill + * \param depth recursion depth + * + * \return 0 if success, nonzero otherwise + */ +static int +hdiff_scan (const char *s, int m, const char *t, int n, int min, ARRAY * hdiff, unsigned int depth) +{ + int i; + BRACKET b; + + /* dumbscan (single horizontal diff) -- does not compress whitespace */ + + for (i = 0; i < m && i < n && s[i] == t[i]; i++) { + } + for (; m > i && n > i && s[m - 1] == t[n - 1]; m--, n--) { + } + b[0].off = i; + b[0].len = m - i; + b[1].off = i; + b[1].len = n - i; + + /* smartscan (multiple horizontal diff) */ + + arr_init (hdiff, sizeof (BRACKET), 4); + hdiff_multi (s, t, b, min, hdiff, depth); + if (hdiff->error) { + arr_free (hdiff, NULL); + return -1; + } + + return 0; +} + + +/* read line *****************************************************************/ + + +/** + * Check if character is inside horizontal diff limits. + * + * \param k rank of character inside line + * \param hdiff horizontal diff structure + * \param ord 0 if reading from first file, 1 if reading from 2nd file + * + * \return TRUE if inside hdiff limits, FALSE otherwise + */ +static int +is_inside (int k, ARRAY * hdiff, int ord) +{ + int i; + BRACKET *b; + for (b = hdiff->data, i = 0; i < hdiff->len; i++, b++) { + int start = (*b)[ord].off; + int end = start + (*b)[ord].len; + if (k >= start && k < end) { + return 1; + } + } + return 0; +} + + +/** + * Copy `src' to `dst' expanding tabs. + * + * \param dst destination buffer + * \param src source buffer + * \param srcsize size of src buffer + * \param base virtual base of this string, needed to calculate tabs + * \param ts tab size + * + * \return new virtual base + * + * \note The procedure returns when all bytes are consumed from `src' + */ +static int +cvt_cpy (char *dst, const char *src, size_t srcsize, int base, int ts) +{ + int i; + for (i = 0; srcsize; i++, src++, dst++, srcsize--) { + *dst = *src; + if (*src == '\t') { + int j = TAB_SKIP (ts, i + base); + i += j - 1; + while (j-- > 0) { + *dst++ = ' '; + } + dst--; + } + } + return i + base; +} + + +/** + * Copy `src' to `dst' expanding tabs. + * + * \param dst destination buffer + * \param dstsize size of dst buffer + * \param[in,out] _src source buffer + * \param srcsize size of src buffer + * \param base virtual base of this string, needed to calculate tabs + * \param ts tab size + * + * \return new virtual base + * + * \note The procedure returns when all bytes are consumed from `src' + * or `dstsize' bytes are written to `dst' + * \note Upon return, `src' points to the first unwritten character in source + */ +static int +cvt_ncpy (char *dst, int dstsize, const char **_src, size_t srcsize, int base, int ts) +{ + int i; + const char *src = *_src; + for (i = 0; i < dstsize && srcsize; i++, src++, dst++, srcsize--) { + *dst = *src; + if (*src == '\t') { + int j = TAB_SKIP (ts, i + base); + if (j > dstsize - i) { + j = dstsize - i; + } + i += j - 1; + while (j-- > 0) { + *dst++ = ' '; + } + dst--; + } + } + *_src = src; + return i + base; +} + + +/** + * Read line from memory, converting tabs to spaces and padding with spaces. + * + * \param src buffer to read from + * \param srcsize size of src buffer + * \param dst buffer to read to + * \param dstsize size of dst buffer, excluding trailing null + * \param skip number of characters to skip + * \param ts tab size + * \param show_cr show trailing carriage return as ^M + * + * \return negative on error, otherwise number of bytes except padding + */ +static int +cvt_mget (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts, int show_cr) +{ + int sz = 0; + if (src != NULL) { + int i; + char *tmp = dst; + const int base = 0; + for (i = 0; dstsize && srcsize && *src != '\n'; i++, src++, srcsize--) { + if (*src == '\t') { + int j = TAB_SKIP (ts, i + base); + i += j - 1; + while (j-- > 0) { + if (skip) { + skip--; + } else if (dstsize) { + dstsize--; + *dst++ = ' '; + } + } + } else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n')) { + if (!skip && show_cr) { + if (dstsize > 1) { + dstsize -= 2; + *dst++ = '^'; + *dst++ = 'M'; + } else { + dstsize--; + *dst++ = '.'; + } + } + break; + } else { + if (skip) { + skip--; + } else { + dstsize--; + *dst++ = is_printable (*src) ? *src : '.'; + } + } + } + sz = dst - tmp; + } + while (dstsize) { + dstsize--; + *dst++ = ' '; + } + *dst = '\0'; + return sz; +} + + +/** + * Read line from memory and build attribute array. + * + * \param src buffer to read from + * \param srcsize size of src buffer + * \param dst buffer to read to + * \param dstsize size of dst buffer, excluding trailing null + * \param skip number of characters to skip + * \param ts tab size + * \param show_cr show trailing carriage return as ^M + * \param hdiff horizontal diff structure + * \param ord 0 if reading from first file, 1 if reading from 2nd file + * \param att buffer of attributes + * + * \return negative on error, otherwise number of bytes except padding + */ +static int +cvt_mgeta (const char *src, size_t srcsize, char *dst, int dstsize, int skip, int ts, int show_cr, + ARRAY * hdiff, int ord, char *att) +{ + int sz = 0; + if (src != NULL) { + int i, k; + char *tmp = dst; + const int base = 0; + for (i = 0, k = 0; dstsize && srcsize && *src != '\n'; i++, k++, src++, srcsize--) { + if (*src == '\t') { + int j = TAB_SKIP (ts, i + base); + i += j - 1; + while (j-- > 0) { + if (skip) { + skip--; + } else if (dstsize) { + dstsize--; + *att++ = is_inside (k, hdiff, ord); + *dst++ = ' '; + } + } + } else if (src[0] == '\r' && (srcsize == 1 || src[1] == '\n')) { + if (!skip && show_cr) { + if (dstsize > 1) { + dstsize -= 2; + *att++ = is_inside (k, hdiff, ord); + *dst++ = '^'; + *att++ = is_inside (k, hdiff, ord); + *dst++ = 'M'; + } else { + dstsize--; + *att++ = is_inside (k, hdiff, ord); + *dst++ = '.'; + } + } + break; + } else { + if (skip) { + skip--; + } else { + dstsize--; + *att++ = is_inside (k, hdiff, ord); + *dst++ = is_printable (*src) ? *src : '.'; + } + } + } + sz = dst - tmp; + } + while (dstsize) { + dstsize--; + *att++ = 0; + *dst++ = ' '; + } + *dst = '\0'; + return sz; +} + + +/** + * Read line from file, converting tabs to spaces and padding with spaces. + * + * \param f file stream to read from + * \param off offset of line inside file + * \param dst buffer to read to + * \param dstsize size of dst buffer, excluding trailing null + * \param skip number of characters to skip + * \param ts tab size + * \param show_cr show trailing carriage return as ^M + * + * \return negative on error, otherwise number of bytes except padding + */ +static int +cvt_fget (FBUF * f, off_t off, char *dst, int dstsize, int skip, int ts, int show_cr) +{ + int base = 0; + int old_base = base; + const int amount = dstsize; + + int useful; + int offset; + + ssize_t i; + size_t sz; + + int lastch = '\0'; + + const char *q = NULL; + char tmp[BUFSIZ]; /* XXX capacity must be >= max{dstsize + 1, amount} */ + char cvt[BUFSIZ]; /* XXX capacity must be >= MAX_TAB_WIDTH * amount */ + + if ((int) sizeof (tmp) < amount || (int) sizeof (tmp) <= dstsize + || (int) sizeof (cvt) < 8 * amount) { + /* abnormal, but avoid buffer overflow */ + memset (dst, ' ', dstsize); + dst[dstsize] = '\0'; + return 0; + } + + f_seek (f, off, SEEK_SET); + + while (skip > base) { + old_base = base; + if (!(sz = f_gets (tmp, amount, f))) { + break; + } + base = cvt_cpy (cvt, tmp, sz, old_base, ts); + if (cvt[base - old_base - 1] == '\n') { + q = &cvt[base - old_base - 1]; + base = old_base + q - cvt + 1; + break; + } + } + + useful = base - skip; + offset = skip - old_base; + + if (useful < 0) { + memset (dst, ' ', dstsize); + dst[dstsize] = '\0'; + return 0; + } + + if (useful <= dstsize) { + if (useful) { + memmove (dst, cvt + offset, useful); + } + if (q == NULL && (sz = f_gets (tmp, dstsize - useful + 1, f))) { + const char *ptr = tmp; + useful += cvt_ncpy (dst + useful, dstsize - useful, &ptr, sz, base, ts) - base; + if (ptr < tmp + sz) { + lastch = *ptr; + } + } + sz = useful; + } else { + memmove (dst, cvt + offset, dstsize); + sz = dstsize; + lastch = cvt[offset + dstsize]; + } + + dst[sz] = lastch; + for (i = 0; i < sz && dst[i] != '\n'; i++) { + if (dst[i] == '\r' && dst[i + 1] == '\n') { + if (show_cr) { + if (i + 1 < dstsize) { + dst[i++] = '^'; + dst[i++] = 'M'; + } else { + dst[i++] = '.'; + } + } + break; + } else if (!is_printable (dst[i])) { + dst[i] = '.'; + } + } + for (; i < dstsize; i++) { + dst[i] = ' '; + } + dst[i] = '\0'; + return sz; +} + + +/* diff printers et al *******************************************************/ + + +static void +cc_free_elt (void *elt) +{ + DIFFLN *p = elt; + if (p->p) { + free (p->p); + } +} + + +static int +printer (void *ctx, int ch, int line, off_t off, size_t sz, const char *str) +{ + DIFFLN *p; + ARRAY *a = ((PRINTER_CTX *) ctx)->a; + DSRC dsrc = ((PRINTER_CTX *) ctx)->dsrc; + if (a->error) { + return -1; + } + if (ch) { + p = arr_enlarge (a); + if (p == NULL) { + return -1; + } + p->p = NULL; + p->ch = ch; + p->line = line; + p->u.off = off; + if (dsrc == DATA_SRC_MEM && line) { + if (sz && str[sz - 1] == '\n') { + sz--; + } + if (sz) { + p->p = malloc (sz); + if (p->p == NULL) { + a->error = 1; + return -1; + } + memcpy (p->p, str, sz); + } + p->u.len = sz; + } + } else if (dsrc == DATA_SRC_MEM) { + if (!a->len) { + a->error = 1; + return -1; + } + p = (DIFFLN *) a->data + a->len - 1; + if (sz && str[sz - 1] == '\n') { + sz--; + } + if (sz) { + size_t new_size = p->u.len + sz; + char *q = realloc (p->p, new_size); + if (q == NULL) { + a->error = 1; + return -1; + } + memcpy (q + p->u.len, str, sz); + p->p = q; + } + p->u.len += sz; + } + if (dsrc == DATA_SRC_TMP && (line || !ch)) { + FBUF *f = ((PRINTER_CTX *) ctx)->f; + f_write (f, str, sz); + } + return 0; +} + + +static int +redo_diff (WDiff * dview) +{ + FBUF *const *f = dview->f; + ARRAY *a = dview->a; + + PRINTER_CTX ctx; + ARRAY ops; + int ndiff; + int rv; + + char extra[256]; + + extra[0] = '\0'; + if (dview->opt.quality == 2) { + strcat (extra, " -d"); + } + if (dview->opt.quality == 1) { + strcat (extra, " --speed-large-files"); + } + if (dview->opt.strip_trailing_cr) { + strcat (extra, " --strip-trailing-cr"); + } + if (dview->opt.ignore_tab_expansion) { + strcat (extra, " -E"); + } + if (dview->opt.ignore_space_change) { + strcat (extra, " -b"); + } + if (dview->opt.ignore_all_space) { + strcat (extra, " -w"); + } + if (dview->opt.ignore_case) { + strcat (extra, " -i"); + } + + if (dview->dsrc != DATA_SRC_MEM) { + f_reset (f[0]); + f_reset (f[1]); + } + + ndiff = dff_execute (dview->args, extra, dview->file[0], dview->file[1], &ops); + if (ndiff < 0) { + return -1; + } + + ctx.dsrc = dview->dsrc; + + rv = 0; + + arr_init (&a[0], sizeof (DIFFLN), 256); + ctx.a = &a[0]; + ctx.f = f[0]; + rv |= dff_reparse (0, dview->file[0], &ops, printer, &ctx); + + arr_init (&a[1], sizeof (DIFFLN), 256); + ctx.a = &a[1]; + ctx.f = f[1]; + rv |= dff_reparse (1, dview->file[1], &ops, printer, &ctx); + + arr_free (&ops, NULL); + + if (rv || a[0].error || a[1].error || a[0].len != a[1].len) { + arr_free (&a[0], cc_free_elt); + arr_free (&a[1], cc_free_elt); + return -1; + } + + if (dview->dsrc == DATA_SRC_TMP) { + f_trunc (f[0]); + f_trunc (f[1]); + } + + if (dview->dsrc == DATA_SRC_MEM && HDIFF_ENABLE) { + dview->hdiff = malloc (a[0].len * sizeof (ARRAY *)); + if (dview->hdiff != NULL) { + int i; + const DIFFLN *p; + const DIFFLN *q; + for (p = a[0].data, q = a[1].data, i = 0; i < a[0].len; i++, q++, p++) { + ARRAY *h = NULL; + if (p->line && q->line && p->ch == CHG_CH) { + h = malloc (sizeof (ARRAY)); + if (h != NULL) { + int rv = hdiff_scan (p->p, p->u.len, q->p, q->u.len, HDIFF_MINCTX, h, + HDIFF_DEPTH); + if (rv != 0) { + free (h); + h = NULL; + } + } + } + dview->hdiff[i] = h; + } + } + } + + return ndiff; +} + + +static void +destroy_hdiff (WDiff * dview) +{ + if (dview->hdiff != NULL) { + int i; + int len = dview->a[0].len; + for (i = 0; i < len; i++) { + ARRAY *h = dview->hdiff[i]; + if (h != NULL) { + arr_free (h, NULL); + free (h); + } + } + free (dview->hdiff); + dview->hdiff = NULL; + } +} + + +/* stuff *********************************************************************/ + + +static int +get_digits (unsigned int n) +{ + int d = 1; + while (n /= 10) { + d++; + } + return d; +} + + +static int +get_line_numbers (const ARRAY * a, int pos, int *linenum, int *lineofs) +{ + const DIFFLN *p; + + *linenum = 0; + *lineofs = 0; + + if (a->len) { + if (pos >= a->len) { + pos = a->len - 1; + } + + p = (DIFFLN *) a->data + pos; + + if (!p->line) { + int n; + for (n = pos; n > 0; n--) { + p--; + if (p->line) { + break; + } + } + *lineofs = pos - n + 1; + } + + *linenum = p->line; + } + return 0; +} + + +static int +calc_nwidth (const ARRAY * const a) +{ + int l1, o1; + int l2, o2; + get_line_numbers (&a[0], a[0].len - 1, &l1, &o1); + get_line_numbers (&a[1], a[1].len - 1, &l2, &o2); + if (l1 < l2) { + l1 = l2; + } + return get_digits (l1); +} + + +static int +find_prev_hunk (const ARRAY * a, int pos) +{ +#if 1 + while (pos > 0 && ((DIFFLN *) a->data)[pos].ch != EQU_CH) { + pos--; + } + while (pos > 0 && ((DIFFLN *) a->data)[pos].ch == EQU_CH) { + pos--; + } +#else + while (pos > 0 && ((DIFFLN *) a->data)[pos - 1].ch == EQU_CH) { + pos--; + } + while (pos > 0 && ((DIFFLN *) a->data)[pos - 1].ch != EQU_CH) { + pos--; + } +#endif + + return pos; +} + + +static int +find_next_hunk (const ARRAY * a, int pos) +{ + while (pos < a->len && ((DIFFLN *) a->data)[pos].ch != EQU_CH) { + pos++; + } + while (pos < a->len && ((DIFFLN *) a->data)[pos].ch == EQU_CH) { + pos++; + } + + return pos; +} + + +/* view routines and callbacks ***********************************************/ + +static void +view_compute_split (WDiff * dview, int i) +{ + dview->bias += i; + if (dview->bias < 2 - dview->half1) { + dview->bias = 2 - dview->half1; + } + if (dview->bias > dview->half2 - 2) { + dview->bias = dview->half2 - 2; + } +} + + +static void +view_compute_areas (WDiff * dview) +{ + dview->height = LINES - 2; + dview->half1 = COLS / 2; + dview->half2 = COLS - dview->half1; + + view_compute_split (dview, 0); +} + + +static int +view_init (WDiff * dview, const char *args, const char *file1, const char *file2, const char *label1, + const char *label2, DSRC dsrc) +{ + int ndiff; + FBUF *f[2]; + + f[0] = NULL; + f[1] = NULL; + + if (dsrc == DATA_SRC_TMP) { + f[0] = f_temp (); + if (f[0] == NULL) { + goto err_2; + } + f[1] = f_temp (); + if (f[1] == NULL) { + f_close (f[0]); + goto err_2; + } + } + if (dsrc == DATA_SRC_ORG) { + f[0] = f_open (file1, O_RDONLY); + if (f[0] == NULL) { + goto err_2; + } + f[1] = f_open (file2, O_RDONLY); + if (f[1] == NULL) { + f_close (f[0]); + goto err_2; + } + } + + dview->args = args; + dview->file[0] = file1; + dview->file[1] = file2; + dview->label[0] = label1; + dview->label[1] = label2; + dview->f[0] = f[0]; + dview->f[1] = f[1]; + dview->hdiff = NULL; + dview->dsrc = dsrc; + + ndiff = redo_diff (dview); + if (ndiff < 0) { + goto err_3; + } + + dview->ndiff = ndiff; + + dview->view_quit = 0; + + dview->bias = 0; + dview->new_frame = 1; + dview->skip_rows = 0; + dview->skip_cols = 0; + dview->display_symbols = 0; + dview->display_numbers = 0; + dview->show_cr = 1; + dview->tab_size = 8; + dview->ord = 0; + dview->full = 0; + dview->last_found = -1; + + dview->opt.quality = 0; + dview->opt.strip_trailing_cr = 0; + dview->opt.ignore_tab_expansion = 0; + dview->opt.ignore_space_change = 0; + dview->opt.ignore_all_space = 0; + dview->opt.ignore_case = 0; + + view_compute_areas (dview); + return 0; + + err_3: + if (dsrc != DATA_SRC_MEM) { + f_close (f[1]); + f_close (f[0]); + } + err_2: + return -1; +} + + +static int +view_reinit (WDiff * dview) +{ + const char *quality_str[] = { + N_("&Normal"), + N_("&Fastest"), + N_("&Minimal") + }; + + QuickWidget diffopt_widgets[] = + { + QUICK_BUTTON (6, 10, 13, OPTY, N_("&Cancel"), B_CANCEL, NULL), + QUICK_BUTTON (2, 10, 13, OPTY, N_("&OK"), B_ENTER, NULL), + QUICK_RADIO (3, OPTX, 15, OPTX, + 3, (const char **) quality_str, (int *) &dview->opt.quality), + QUICK_CHECKBOX (3, OPTX, 11, OPTY, + N_("strip trailing &CR"), &dview->opt.strip_trailing_cr), + QUICK_CHECKBOX (3, OPTX, 10, OPTY, + N_("ignore all &Whitespace"), &dview->opt.ignore_all_space), + QUICK_CHECKBOX (3, OPTX, 9, OPTY, + N_("ignore &Space change"), &dview->opt.ignore_space_change), + QUICK_CHECKBOX (3, OPTX, 8, OPTY, + N_("ignore tab &Expansion"), &dview->opt.ignore_tab_expansion), + QUICK_CHECKBOX (3, OPTX, 7, OPTY, + N_("&Ignore case"), &dview->opt.ignore_case), + QUICK_END + }; + + QuickDialog diffopt = + { + OPTX, OPTY, -1, -1, + N_("Diff Options"), "[Diff Options]", + diffopt_widgets, 0 + }; + + int ndiff = dview->ndiff; + if (quick_dialog (&diffopt) != B_CANCEL) { + destroy_hdiff (dview); + arr_free (&dview->a[1], cc_free_elt); + arr_free (&dview->a[0], cc_free_elt); + ndiff = redo_diff (dview); + if (ndiff >= 0) { + dview->ndiff = ndiff; + } + } + return ndiff; +} + + +static void +view_fini (WDiff * dview) +{ + if (dview->dsrc != DATA_SRC_MEM) { + f_close (dview->f[1]); + f_close (dview->f[0]); + } + + destroy_hdiff (dview); + arr_free (&dview->a[1], cc_free_elt); + arr_free (&dview->a[0], cc_free_elt); +} + + +static int +view_display_file (const WDiff * dview, int ord, int r, int c, int height, int width) +{ + int i, j, k; + char buf[BUFSIZ]; + FBUF *f = dview->f[ord]; + const ARRAY *a = &dview->a[ord]; + int skip = dview->skip_cols; + int display_symbols = dview->display_symbols; + int display_numbers = dview->display_numbers; + int show_cr = dview->show_cr; + int tab_size = dview->tab_size; + const DIFFLN *p; + + int nwidth = display_numbers; + int xwidth = display_symbols + display_numbers; + + if (xwidth) { + if (xwidth > width && display_symbols) { + xwidth--; + display_symbols = 0; + } + if (xwidth > width && display_numbers) { + xwidth = width; + display_numbers = width; + } + + xwidth++; + + c += xwidth; + width -= xwidth; + + if (width < 0) { + width = 0; + } + } + + if ((int) sizeof (buf) <= width || (int) sizeof (buf) <= nwidth) { + /* abnormal, but avoid buffer overflow */ + return -1; + } + + for (i = dview->skip_rows, j = 0, p = (DIFFLN *) a->data + i; i < a->len && j < height; + p++, j++, i++) { + int ch = p->ch; + tty_setcolor (NORMAL_COLOR); + if (display_symbols) { + tty_gotoyx (r + j, c - 2); + tty_print_char (ch); + } + if (p->line) { + if (display_numbers) { + tty_gotoyx (r + j, c - xwidth); + g_snprintf (buf, display_numbers + 1, "%*d", nwidth, p->line); + tty_print_string (str_fit_to_term (buf, nwidth, J_LEFT_FIT)); + } + if (ch == ADD_CH) { + tty_setcolor (DFF_ADD_COLOR); + } + if (ch == CHG_CH) { + tty_setcolor (DFF_CHG_COLOR); + } + if (f == NULL) { + if (i == dview->last_found) { + tty_setcolor (MARKED_SELECTED_COLOR); + } else { + if (dview->hdiff != NULL && dview->hdiff[i] != NULL) { + char att[BUFSIZ]; + cvt_mgeta (p->p, p->u.len, buf, width, skip, tab_size, show_cr, + dview->hdiff[i], ord, att); + tty_gotoyx (r + j, c); + for (k = 0; k < width; k++) { + tty_setcolor (att[k] ? DFF_CHH_COLOR : DFF_CHG_COLOR); + tty_print_char (buf[k]); + } + continue; + } else if (ch == CHG_CH) { + tty_setcolor (DFF_CHH_COLOR); + } + } + cvt_mget (p->p, p->u.len, buf, width, skip, tab_size, show_cr); + } else { + cvt_fget (f, p->u.off, buf, width, skip, tab_size, show_cr); + } + } else { + if (display_numbers) { + tty_gotoyx (r + j, c - xwidth); + memset (buf, ' ', display_numbers); + buf[display_numbers] = '\0'; + /* tty_print_nstring (buf, display_numbers); */ + tty_print_string (buf); + } + if (ch == DEL_CH) { + tty_setcolor (DFF_DEL_COLOR); + } + if (ch == CHG_CH) { + tty_setcolor (DFF_CHD_COLOR); + } + memset (buf, ' ', width); + buf[width] = '\0'; + } + tty_gotoyx (r + j, c); + /* tty_print_nstring (buf, width); */ + tty_print_string (buf); + } + tty_setcolor (NORMAL_COLOR); + k = width; + if (width < xwidth - 1) { + k = xwidth - 1; + } + memset (buf, ' ', k); + buf[k] = '\0'; + for (; j < height; j++) { + if (xwidth) { + tty_gotoyx (r + j, c - xwidth); + /* tty_print_nstring (buf, xwidth - 1); */ + tty_print_string (buf); + } + tty_gotoyx (r + j, c); + /* tty_print_nstring (buf, width); */ + tty_print_string (buf); + } + + return 0; +} + + +static void +view_status (const WDiff * dview, int ord, int width, int c) +{ + int skip_rows = dview->skip_rows; + int skip_cols = dview->skip_cols; + + char buf[BUFSIZ]; + int filename_width; + int linenum, lineofs; + + tty_setcolor (SELECTED_COLOR); + + tty_gotoyx (0, c); + get_line_numbers (&dview->a[ord], skip_rows, &linenum, &lineofs); + + filename_width = width - 22; + if (filename_width < 8) { + filename_width = 8; + } + if (filename_width >= (int) sizeof (buf)) { + /* abnormal, but avoid buffer overflow */ + filename_width = sizeof (buf) - 1; + } + trim (strip_home_and_password (dview->label[ord]), buf, filename_width); + if (ord == 0) { + tty_printf ("%-*s %6d+%-4d Col %-4d ", filename_width, buf, linenum, lineofs, skip_cols); + } else { + tty_printf ("%-*s %6d+%-4d Dif %-4d ", filename_width, buf, linenum, lineofs, dview->ndiff); + } +} + + +static void +view_update (WDiff * dview) +{ + int height = dview->height; + int width1; + int width2; + + int last = dview->a[0].len - 1; + + if (dview->skip_rows > last) { + dview->skip_rows = last; + } + if (dview->skip_rows < 0) { + dview->skip_rows = 0; + } + if (dview->skip_cols < 0) { + dview->skip_cols = 0; + } + + if (height < 2) { + return; + } + + width1 = dview->half1 + dview->bias; + width2 = dview->half2 - dview->bias; + if (dview->full) { + width1 = COLS; + width2 = 0; + } + + if (dview->new_frame) { + Dlg_head *h = dview->widget.parent; + + int xwidth = dview->display_symbols + dview->display_numbers; + + tty_setcolor (NORMAL_COLOR); + if (width1 > 1) { + tty_draw_box (1, 0, height, width1, FALSE); + } + if (width2 > 1) { + tty_draw_box (1, width1, height, width2, FALSE); + } + + if (xwidth) { + xwidth++; + if (xwidth < width1 - 1) { + tty_gotoyx (1, xwidth); + tty_print_alt_char (mc_tty_frm[MC_TTY_FRM_DTOPMIDDLE], FALSE); + tty_gotoyx (height, xwidth); + tty_print_alt_char (mc_tty_frm[MC_TTY_FRM_DBOTTOMMIDDLE], FALSE); + tty_draw_vline (2, xwidth, mc_tty_frm[MC_TTY_FRM_VERT], height - 2); + } + if (xwidth < width2 - 1) { + tty_gotoyx (1, width1 + xwidth); + tty_print_alt_char (mc_tty_frm[MC_TTY_FRM_DTOPMIDDLE], FALSE); + tty_gotoyx (height, width1 + xwidth); + tty_print_alt_char (mc_tty_frm[MC_TTY_FRM_DBOTTOMMIDDLE], FALSE); + tty_draw_vline (2, width1 + xwidth, mc_tty_frm[MC_TTY_FRM_VERT], height - 2); + } + } + dview->new_frame = 0; + } + + if (width1 > 2) { + view_status (dview, dview->ord, width1, 0); + view_display_file (dview, dview->ord, 2, 1, height - 2, width1 - 2); + } + if (width2 > 2) { + view_status (dview, dview->ord ^ 1, width2, width1); + view_display_file (dview, dview->ord ^ 1, 2, width1 + 1, height - 2, width2 - 2); + } +} + + +static void +view_redo (WDiff * dview) +{ + if (view_reinit (dview) < 0) { + dview->view_quit = 1; + } else if (dview->display_numbers) { + int old = dview->display_numbers; + dview->display_numbers = calc_nwidth (dview->a); + dview->new_frame = (old != dview->display_numbers); + } +} + + +#define IS_WHOLE_OR_DONT_CARE() \ + (!whole || ( \ + (i == 0 || strchr(wholechars, haystack[i - 1]) == NULL) && \ + (i + nlen == hlen || strchr(wholechars, haystack[i + nlen]) == NULL) \ + )) + + +static const unsigned char * +memmem_dummy (const unsigned char *haystack, size_t i, size_t hlen, const unsigned char *needle, + size_t nlen, int whole) +{ + for (; i + nlen <= hlen; i++) { + if (haystack[i] == needle[0]) { + size_t j; + for (j = 1; j < nlen; j++) { + if (haystack[i + j] != needle[j]) { + break; + } + } + if (j == nlen && IS_WHOLE_OR_DONT_CARE ()) { + return haystack + i; + } + } + } + + return NULL; +} + + +static const unsigned char * +memmem_dummy_nocase (const unsigned char *haystack, size_t i, size_t hlen, + const unsigned char *needle, size_t nlen, int whole) +{ + for (; i + nlen <= hlen; i++) { + if (toupper (haystack[i]) == toupper (needle[0])) { + size_t j; + for (j = 1; j < nlen; j++) { + if (toupper (haystack[i + j]) != toupper (needle[j])) { + break; + } + } + if (j == nlen && IS_WHOLE_OR_DONT_CARE ()) { + return haystack + i; + } + } + } + + return NULL; +} + + +static const unsigned char * +memmem_dummy_rev (const unsigned char *haystack, size_t i, size_t hlen, const unsigned char *needle, + size_t nlen, int whole) +{ + while (i--) { + if (haystack[i] == needle[0] && i + nlen <= hlen) { + size_t j; + for (j = 1; j < nlen; j++) { + if (haystack[i + j] != needle[j]) { + break; + } + } + if (j == nlen && IS_WHOLE_OR_DONT_CARE ()) { + return haystack + i; + } + } + } + + return NULL; +} + + +static const unsigned char * +memmem_dummy_rev_nocase (const unsigned char *haystack, size_t i, size_t hlen, + const unsigned char *needle, size_t nlen, int whole) +{ + while (i--) { + if (toupper (haystack[i]) == toupper (needle[0]) && i + nlen <= hlen) { + size_t j; + for (j = 1; j < nlen; j++) { + if (toupper (haystack[i + j]) != toupper (needle[j])) { + break; + } + } + if (j == nlen && IS_WHOLE_OR_DONT_CARE ()) { + return haystack + i; + } + } + } + + return NULL; +} + + +static const unsigned char * +search_string (const DIFFLN * p, size_t xpos, const void *needle, size_t nlen, int whole, int ccase) +{ + const unsigned char *haystack = p->p; + size_t hlen = p->u.len; + + if (xpos > hlen || nlen <= 0 || haystack == NULL || needle == NULL) { + return NULL; + } + + /* XXX I should use Boyer-Moore */ + if (ccase) { + return memmem_dummy (haystack, xpos, hlen, needle, nlen, whole); + } else { + return memmem_dummy_nocase (haystack, xpos, hlen, needle, nlen, whole); + } +} + + +static int +view_search_string (WDiff * dview, const char *needle, int ccase, int back, int whole) +{ + size_t nlen = strlen (needle); + size_t xpos = 0; + + int ord = dview->ord; + const ARRAY *a = &dview->a[ord]; + const DIFFLN *p; + + int i = dview->last_found; + + if (back) { + if (i == -1) { + i = dview->skip_rows; + } + for (--i, p = (DIFFLN *) a->data + i; i >= 0; p--, i--) { + const unsigned char *q = search_string (p, xpos, needle, nlen, whole, ccase); + if (q != NULL) { + return i; + } + } + } else { + if (i == -1) { + i = dview->skip_rows - 1; + } + for (++i, p = (DIFFLN *) a->data + i; i < a->len; p++, i++) { + const unsigned char *q = search_string (p, xpos, needle, nlen, whole, ccase); + if (q != NULL) { + return i; + } + } + } + + return -1; +} + + +static void +view_search (WDiff * dview, int again) +{ + /* XXX some statics here, to be remembered between runs */ + static char *searchopt_text = NULL; + static int searchopt_type; + static int searchopt_case; + static int searchopt_backwards; + static int searchopt_whole; + + static int compiled = 0; + + if (again < 0) { + g_free (searchopt_text); + searchopt_text = NULL; + if (compiled) { + compiled = 0; + /*XXX free search exp */ + } + return; + } + + if (dview->dsrc != DATA_SRC_MEM) { + error_dialog (_("Search"), _(" Search is disabled ")); + return; + } + + if (!again || searchopt_text == NULL) { + char *tsearchopt_text; + int tsearchopt_type = searchopt_type; + int tsearchopt_case = searchopt_case; + int tsearchopt_backwards = searchopt_backwards; + int tsearchopt_whole = searchopt_whole; + + const char *search_str[] = + { + N_("&Normal") + }; + + QuickWidget search_widgets[] = + { + /* 0 */ + QUICK_BUTTON (6, 10, 11, SEARCH_DLG_HEIGHT, N_("&Cancel"), B_CANCEL, NULL), + /* 1 */ + QUICK_BUTTON (4, 10, 11, SEARCH_DLG_HEIGHT, N_("&Find all"), B_USER, NULL), + /* 2 */ + QUICK_BUTTON (2, 10, 11, SEARCH_DLG_HEIGHT, N_("&OK"), B_ENTER, NULL), + /* 3 */ + QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 8, SEARCH_DLG_HEIGHT, N_("&Whole words"), &tsearchopt_whole), + /* 4 */ + QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 6, SEARCH_DLG_HEIGHT, N_("&Backwards"), &tsearchopt_backwards), + /* 5 */ + QUICK_CHECKBOX (33, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT, N_("case &Sensitive"), &tsearchopt_case), + /* 6 */ + QUICK_RADIO ( 3, SEARCH_DLG_WIDTH, 5, SEARCH_DLG_HEIGHT, + 1, (const char **) search_str, (int *) &tsearchopt_type), + /* 7 */ + QUICK_INPUT (3, SEARCH_DLG_WIDTH, 3, SEARCH_DLG_HEIGHT, + tsearchopt_text, SEARCH_DLG_WIDTH - 6, 0, MC_HISTORY_SHARED_SEARCH, &tsearchopt_text), + /* 8 */ + QUICK_LABEL (2, SEARCH_DLG_WIDTH, 2, SEARCH_DLG_HEIGHT, N_(" Enter search string:")), + QUICK_END + }; + + QuickDialog search_input = + { + SEARCH_DLG_WIDTH, SEARCH_DLG_HEIGHT, -1, 0, + N_("Search"), "[Input Line Keys]", + search_widgets, 0 + }; + + if (quick_dialog (&search_input) == B_CANCEL) { + return; + } + if (tsearchopt_text == NULL || !*tsearchopt_text) { + g_free (tsearchopt_text); + return; + } + g_free (searchopt_text); + + searchopt_text = tsearchopt_text; + searchopt_type = tsearchopt_type; + searchopt_case = tsearchopt_case; + searchopt_backwards = tsearchopt_backwards; + searchopt_whole = tsearchopt_whole; + } + + if (compiled) { + compiled = 0; + /*XXX free search exp */ + } + if (0 /*XXX new search exp */ ) { + error_dialog (_("Error"), _(" Cannot search ")); + return; + } + compiled = 1; + if (searchopt_type == 0) { + dview->last_found = + view_search_string (dview, searchopt_text, searchopt_case, searchopt_backwards, + searchopt_whole); + } + + if (dview->last_found == -1) { + error_dialog (_("Search"), _(" Search string not found ")); + } else { + dview->skip_rows = dview->last_found; + view_update (dview); + } +} + + +static void +view_search_cmd (WDiff * dview) +{ + view_search (dview, 0); +} + + +static void +view_edit (WDiff * dview, int ord) +{ + int linenum, lineofs; + + if (dview->dsrc == DATA_SRC_TMP) { + error_dialog (_("Edit"), _(" Edit is disabled ")); + return; + } + + get_line_numbers (&dview->a[ord], dview->skip_rows, &linenum, &lineofs); + do_edit_at_line (dview->file[ord], linenum); + view_redo (dview); + view_update (dview); +} + + +static void +view_edit_cmd (WDiff * dview) +{ + view_edit (dview, dview->ord); +} + + +static void +view_goto_cmd (WDiff * dview, int ord) +{ + static const char *title[2] = { " Goto line (left) ", " Goto line (right) " }; + static char prev[256]; + /* XXX some statics here, to be remembered between runs */ + + int newline; + char *input; + + input = input_dialog (_(title[ord]), _(" Enter line: "), MC_HISTORY_YDIFF_GOTO_LINE, prev); + if (input != NULL) { + const char *s = input; + if (scan_deci (&s, &newline) == 0 && *s == '\0') { + int i = 0; + if (newline > 0) { + const DIFFLN *p; + for (p = dview->a[ord].data; i < dview->a[ord].len; i++, p++) { + if (p->line == newline) { + break; + } + } + } + dview->skip_rows = i; + snprintf (prev, sizeof (prev), "%d", newline); + } + g_free (input); + } +} + + +static void +view_help_cmd (void) +{ + interactive_display (NULL, "[Diff Viewer]"); +} + + +static void +view_quit_cmd (WDiff * dview) +{ + dlg_stop (dview->widget.parent); +} + + +static void +view_labels (WDiff * dview) +{ + Dlg_head *h = dview->widget.parent; + WButtonBar *b = find_buttonbar (h); + + buttonbar_set_label (b, 1, Q_("ButtonBar|Help"), diff_map, (Widget *) dview); + buttonbar_set_label (b, 4, Q_("ButtonBar|Edit"), diff_map, (Widget *) dview); + buttonbar_set_label (b, 7, Q_("ButtonBar|Search"), diff_map, (Widget *) dview); + buttonbar_set_label (b, 10, Q_("ButtonBar|Quit"), diff_map, (Widget *) dview); +} + + +static int +view_event (Gpm_Event * event, void *x) +{ + WDiff *dview = (WDiff *) x; + int result = MOU_NORMAL; + + /* We are not interested in the release events */ + if (!(event->type & (GPM_DOWN | GPM_DRAG))) { + return result; + } + + /* Wheel events */ + if ((event->buttons & GPM_B_UP) && (event->type & GPM_DOWN)) { + dview->skip_rows -= 2; + view_update (dview); + return result; + } + if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) { + dview->skip_rows += 2; + view_update (dview); + return result; + } + + return result; +} + + +static cb_ret_t +view_execute_cmd (WDiff *dview, unsigned long command) +{ + cb_ret_t res = MSG_HANDLED; + + switch (command) { + case CK_DiffDisplaySymbols: + dview->display_symbols ^= 1; + dview->new_frame = 1; + break; + case CK_DiffDisplayNumbers: + dview->display_numbers ^= calc_nwidth (dview->a); + dview->new_frame = 1; + break; + case CK_DiffFull: + dview->full ^= 1; + dview->new_frame = 1; + break; + case CK_DiffEqual: + if (!dview->full) { + dview->bias = 0; + dview->new_frame = 1; + } + break; + case CK_DiffSplitMore: + if (!dview->full) { + view_compute_split (dview, 1); + dview->new_frame = 1; + } + break; + + case CK_DiffSplitLess: + if (!dview->full) { + view_compute_split (dview, -1); + dview->new_frame = 1; + } + break; + case CK_DiffShowCR: + dview->show_cr ^= 1; + break; + case CK_DiffSetTab2: + dview->tab_size = 2; + break; + case CK_DiffSetTab3: + dview->tab_size = 3; + break; + case CK_DiffSetTab4: + dview->tab_size = 4; + break; + case CK_DiffSetTab8: + dview->tab_size = 8; + break; + case CK_DiffSwapPanel: + dview->ord ^= 1; + break; + case CK_DiffRedo: + view_redo (dview); + break; + case CK_DiffNextHunk: + dview->skip_rows = find_next_hunk (&dview->a[0], dview->skip_rows); + break; + case CK_DiffPrevHunk: + dview->skip_rows = find_prev_hunk (&dview->a[0], dview->skip_rows); + break; + case CK_DiffGoto: + view_goto_cmd (dview, TRUE); + break; +/* what this? + case KEY_BACKSPACE: + dview->last_found = -1; + break; +*/ + case CK_DiffEditCurrent: + view_edit (dview, dview->ord); + break; + case CK_DiffEditOther: + view_edit (dview, dview->ord ^ 1); + break; + case CK_DiffSearch: + view_search (dview, 1); + break; + case CK_DiffBOF: + dview->skip_rows = 0; + break; + case CK_DiffEOF: + dview->skip_rows = dview->a[0].len - 1; + break; + case CK_DiffUp: + dview->skip_rows--; + break; + case CK_DiffDown: + dview->skip_rows++; + break; + case CK_DiffPageDown: + dview->skip_rows += dview->height - 2; + break; + case CK_DiffPageUp: + dview->skip_rows -= dview->height - 2; + break; + case CK_DiffLeft: + dview->skip_cols--; + break; + case CK_DiffRight: + dview->skip_cols++; + break; + case CK_DiffQuickLeft: + dview->skip_cols -= 8; + break; + case CK_DiffQuickRight: + dview->skip_cols += 8; + break; + case CK_DiffHome: + dview->skip_cols = 0; + break; + case CK_ShowCommandLine: + view_other_cmd (); + break; + case CK_DiffQuit: + dview->view_quit = 1; + break; + default: + res = MSG_NOT_HANDLED; + } + return res; +} + +static cb_ret_t +view_handle_key (WDiff *dview, int key) +{ + unsigned long command; + + key = convert_from_input_c (key); + + command = lookup_keymap_command (diff_map, key); + if ((command != CK_Ignore_Key) && (view_execute_cmd (dview, command) == MSG_HANDLED)) + return MSG_HANDLED; + + /* Key not used */ + return MSG_NOT_HANDLED; +} + + +static cb_ret_t +view_callback (Widget * w, widget_msg_t msg, int parm) +{ + WDiff *dview = (WDiff *) w; + Dlg_head *h = dview->widget.parent; + cb_ret_t i; + + switch (msg) { + case WIDGET_INIT: + view_labels (dview); + return MSG_HANDLED; + + case WIDGET_DRAW: + dview->new_frame = 1; + view_update (dview); + return MSG_HANDLED; + + case WIDGET_CURSOR: + return MSG_HANDLED; + + case WIDGET_KEY: + i = view_handle_key (dview, parm); + if (dview->view_quit) + dlg_stop (h); + else { + view_update (dview); + } + return i; + + case WIDGET_IDLE: + return MSG_HANDLED; + + case WIDGET_FOCUS: + view_labels (dview); + return MSG_HANDLED; + + case WIDGET_DESTROY: + return MSG_HANDLED; + + default: + return default_proc (msg, parm); + } +} + + +static void +view_adjust_size (Dlg_head * h) +{ + WDiff *dview; + WButtonBar *bar; + + /* Look up the viewer and the buttonbar, we assume only two widgets here */ + dview = (WDiff *) find_widget_type (h, view_callback); + bar = find_buttonbar (h); + widget_set_size (&dview->widget, 0, 0, LINES, COLS); + widget_set_size ((Widget *) bar, LINES - 1, 0, 1, COLS); + + view_compute_areas (dview); +} + + +static cb_ret_t +view_dialog_callback (Dlg_head *h, Widget *sender, dlg_msg_t msg, int parm, void *data) + +{ + switch (msg) { + case DLG_RESIZE: + view_adjust_size (h); + return MSG_HANDLED; + + default: + return default_dlg_callback (h, sender, msg, parm, data); + } +} + + +int +diff_view (const char *file1, const char *file2, const char *label1, const char *label2) +{ + int error; + WDiff *dview; + WButtonBar *bar; + Dlg_head *view_dlg; + + /* Create dialog and widgets, put them on the dialog */ + view_dlg = + create_dlg (0, 0, LINES, COLS, NULL, view_dialog_callback, + "[Diff Viewer]", NULL, DLG_WANT_TAB); + + dview = g_new0 (WDiff, 1); + + init_widget (&dview->widget, 0, 0, LINES, COLS, + (callback_fn) view_callback, (mouse_h) view_event); + + widget_want_cursor (dview->widget, 0); + + bar = buttonbar_new (1); + + add_widget (view_dlg, bar); + add_widget (view_dlg, dview); + + error = view_init (dview, "-a", file1, file2, label1, label2, DATA_SRC_MEM); /* XXX binary diff? */ + + /* Please note that if you add another widget, + * you have to modify view_adjust_size to + * be aware of it + */ + if (!error) { + run_dlg (view_dlg); + view_search (dview, -1); + view_fini (dview); + } + destroy_dlg (view_dlg); + + return error; +} + +/* --------------------------------------------------------------------------------------------- */ + +#define GET_FILE_AND_STAMP(n) \ + do { \ + use_copy##n = 0; \ + real_file##n = file##n; \ + if (!vfs_file_is_local(file##n)) { \ + real_file##n = mc_getlocalcopy(file##n); \ + if (real_file##n != NULL) { \ + use_copy##n = 1; \ + if (mc_stat(real_file##n, &st##n) != 0) { \ + use_copy##n = -1; \ + } \ + } \ + } \ + } while (0) +#define UNGET_FILE(n) \ + do { \ + if (use_copy##n) { \ + int changed = 0; \ + if (use_copy##n > 0) { \ + time_t mtime = st##n.st_mtime; \ + if (mc_stat(real_file##n, &st##n) == 0) { \ + changed = (mtime != st##n.st_mtime); \ + } \ + } \ + mc_ungetlocalcopy(file##n, real_file##n, changed); \ + g_free(real_file##n); \ + } \ + } while (0) + +void +view_diff_cmd (WDiff *dview) +{ + int rv = 0; + char *file0 = NULL; + char *file1 = NULL; + int is_dir0 = 0; + int is_dir1 = 0; + + if (dview == NULL) + { + const WPanel *panel0 = current_panel; + const WPanel *panel1 = other_panel; + if (get_current_index ()) + { + panel0 = other_panel; + panel1 = current_panel; + } + file0 = concat_dir_and_file (panel0->cwd, selection (panel0)->fname); + file1 = concat_dir_and_file (panel1->cwd, selection (panel1)->fname); + is_dir0 = S_ISDIR (selection (panel0)->st.st_mode); + is_dir1 = S_ISDIR (selection (panel1)->st.st_mode); + } + + if (rv == 0) { + rv = -1; + if (file0 != NULL && !is_dir0 && file1 != NULL && !is_dir1) + { + int use_copy0; + int use_copy1; + struct stat st0; + struct stat st1; + char *real_file0; + char *real_file1; + GET_FILE_AND_STAMP (0); + GET_FILE_AND_STAMP (1); + if (real_file0 != NULL && real_file1 != NULL) + { + rv = diff_view (real_file0, real_file1, file0, file1); + } + UNGET_FILE (1); + UNGET_FILE (0); + } + } + + g_free (file1); + g_free (file0); + + if (rv != 0) { + message (1, MSG_ERROR, _("Need two files to compare")); + } +} diff --git a/src/diffviewer/ydiff.h b/src/diffviewer/ydiff.h new file mode 100644 index 000000000..1671002f6 --- /dev/null +++ b/src/diffviewer/ydiff.h @@ -0,0 +1,6 @@ +#ifndef MC_YDIFF_H +#define MC_YDIFF_H + +int diff_view (const char *file1, const char *file2, const char *label1, const char *label2); + +#endif diff --git a/src/history.h b/src/history.h index 66c809a0d..9ac8498d3 100644 --- a/src/history.h +++ b/src/history.h @@ -41,4 +41,6 @@ #define MC_HISTORY_SHARED_SEARCH "mc.shared.search" +#define MC_HISTORY_YDIFF_GOTO_LINE "mc.ydiff.goto-line" + #endif diff --git a/src/keybind.c b/src/keybind.c index 4e24448cb..f9db70930 100644 --- a/src/keybind.c +++ b/src/keybind.c @@ -367,6 +367,9 @@ static name_keymap_t command_names[] = { { "CmdCopyOtherTagged", CK_CopyOtherTagged }, { "CmdToggleShowHidden", CK_ToggleShowHidden }, { "CmdTogglePanelsSplit", CK_TogglePanelsSplit }, +#ifdef USE_DIFF_VIEW + { "CmdDiffView", CK_DiffViewCmd}, +#endif /* panel */ { "PanelChdirOtherPanel", CK_PanelChdirOtherPanel }, @@ -444,6 +447,39 @@ static name_keymap_t command_names[] = { { "ShowCommandLine", CK_ShowCommandLine }, { "SelectCodepage", CK_SelectCodepage }, + + /* diff viewer */ + { "DiffDisplaySymbols", CK_DiffDisplaySymbols}, + { "DiffDisplayNumbers", CK_DiffDisplayNumbers}, + { "DiffFull", CK_DiffFull}, + { "DiffEqual", CK_DiffEqual}, + { "DiffSplitMore", CK_DiffSplitMore}, + { "DiffSplitLess", CK_DiffSplitLess}, + { "DiffShowDiff", CK_DiffShowDiff}, + { "DiffSetTab2", CK_DiffSetTab2}, + { "DiffSetTab3", CK_DiffSetTab3}, + { "DiffSetTab4", CK_DiffSetTab4}, + { "DiffSetTab8", CK_DiffSetTab8}, + { "DiffSwapPanel", CK_DiffSwapPanel}, + { "DiffRedo", CK_DiffRedo}, + { "DiffNextHunk", CK_DiffNextHunk}, + { "DiffPrevHunk", CK_DiffPrevHunk}, + { "DiffGoto", CK_DiffGoto}, + { "DiffEditCurrent", CK_DiffEditCurrent}, + { "DiffEditOther", CK_DiffEditOther}, + { "DiffSearch", CK_DiffSearch}, + { "DiffEOF", CK_DiffEOF}, + { "DiffBOF", CK_DiffBOF}, + { "DiffDown", CK_DiffDown}, + { "DiffUp", CK_DiffUp}, + { "DiffLeft", CK_DiffLeft}, + { "DiffRight", CK_DiffRight}, + { "DiffPageDown", CK_DiffPageDown}, + { "DiffPageUp", CK_DiffPageUp}, + { "DiffHome", CK_DiffHome}, + { "DiffEnd", CK_DiffEnd}, + { "DiffQuit", CK_DiffQuit}, + { NULL, CK_Ignore_Key } }; @@ -909,6 +945,47 @@ const global_keymap_t default_input_keymap[] = { { 0, CK_Ignore_Key, "" } }; +/* diff viewer */ +const global_keymap_t default_diff_keymap[] = { + + { 's', CK_DiffDisplaySymbols, "s" }, + { 'l', CK_DiffDisplayNumbers, "l" }, + { 'f', CK_DiffFull, "f" }, + { '=', CK_DiffEqual, "=" }, + { '>', CK_DiffSplitMore, ">" }, + { '<', CK_DiffSplitLess, "<" }, + { '2', CK_DiffSetTab2, "2" }, + { '3', CK_DiffSetTab3, "3" }, + { '4', CK_DiffSetTab4, "4" }, + { '8', CK_DiffSetTab8, "8" }, + { XCTRL ('u'), CK_DiffSwapPanel, "C-u" }, + { XCTRL ('r'), CK_DiffRedo, "C-r" }, + { XCTRL ('o'), CK_ShowCommandLine, "C-o" }, + { 'n', CK_DiffNextHunk, "n" }, + { 'p', CK_DiffPrevHunk, "p" }, + { 'g', CK_DiffGoto, "g" }, + { 'G', CK_DiffGoto, "G" }, + { KEY_F (4), CK_DiffEditCurrent, "F4" }, + { KEY_F (14), CK_DiffEditOther, "S-F4" }, + { KEY_F (17), CK_DiffSearch, "S-F7" }, + { KEY_M_CTRL | KEY_HOME, CK_DiffBOF, "C-Home" }, + { KEY_M_CTRL | KEY_END, CK_DiffEOF, "C-End" }, + { KEY_DOWN, CK_DiffDown, "Down" }, + { KEY_UP, CK_DiffUp, "Up" }, + { KEY_M_CTRL | KEY_LEFT, CK_DiffQuickLeft, "C-Left" }, + { KEY_M_CTRL | KEY_RIGHT, CK_DiffQuickRight, "C-Right" }, + { KEY_LEFT, CK_DiffLeft, "Left" }, + { KEY_RIGHT, CK_DiffRight, "Right" }, + { KEY_NPAGE, CK_DiffPageDown, "Down" }, + { KEY_PPAGE, CK_DiffPageUp, "Up" }, + { KEY_HOME, CK_DiffHome, "Home" }, + { KEY_END, CK_DiffEnd, "End" }, + { 'q', CK_DiffQuit, "q" }, + { 'Q', CK_DiffQuit, "Q" }, + { XCTRL ('g'), CK_DiffQuit, "C-g" }, + { ESC_CHAR, CK_DiffQuit, "Esc" }, +}; + static int name_keymap_comparator (const void *p1, const void *p2) { diff --git a/src/keybind.h b/src/keybind.h index 3ef491e06..96991c6d6 100644 --- a/src/keybind.h +++ b/src/keybind.h @@ -55,4 +55,9 @@ extern const global_keymap_t default_tree_keymap[]; /* help.c */ extern const global_keymap_t default_help_keymap[]; +#ifdef USE_DIFF_VIEW +/* yview.c */ +extern const global_keymap_t default_diff_keymap[]; +#endif + #endif /* MC_KEYBIND_H */ diff --git a/src/main.c b/src/main.c index 27e34b768..2b194e220 100644 --- a/src/main.c +++ b/src/main.c @@ -317,7 +317,9 @@ GArray *panel_keymap = NULL; GArray *input_keymap = NULL; GArray *tree_keymap = NULL; GArray *help_keymap = NULL; - +#ifdef USE_DIFF_VIEW +GArray *diff_keymap = NULL; +#endif const global_keymap_t *main_map; const global_keymap_t *main_x_map; @@ -746,6 +748,9 @@ create_command_menu (void) entries = g_list_append (entries, menu_entry_create (_("S&wap panels"), CK_SwapCmd)); entries = g_list_append (entries, menu_entry_create (_("Switch &panels on/off"), CK_ShowCommandLine)); entries = g_list_append (entries, menu_entry_create (_("&Compare directories"), CK_CompareDirsCmd)); +#ifdef USE_DIFF_VIEW + entries = g_list_append (entries, menu_entry_create (_("&View diff files"), CK_DiffViewCmd)); +#endif entries = g_list_append (entries, menu_entry_create (_("E&xternal panelize"), CK_ExternalPanelize)); entries = g_list_append (entries, menu_entry_create (_("Show directory s&izes"), CK_SingleDirsizeCmd)); entries = g_list_append (entries, menu_separator_create ()); @@ -1153,6 +1158,11 @@ midnight_execute_cmd (Widget *sender, unsigned long command) case CK_CompareDirsCmd: compare_dirs_cmd (); break; +#ifdef USE_DIFF_VIEW + case CK_DiffViewCmd: + diff_view_cmd (); + break; +#endif case CK_ConfigureBox: configure_box (); break; @@ -1880,6 +1890,12 @@ do_nc (void) if (help_keymap && help_keymap->len > 0) help_map = (global_keymap_t *) help_keymap->data; +#ifdef USE_DIFF_VIEW + diff_map = default_diff_keymap; + if (diff_keymap && diff_keymap->len > 0) + diff_map = (global_keymap_t *) diff_keymap->data; +#endif + /* Check if we were invoked as an editor or file viewer */ if ((view_one_file != NULL) || (edit_one_file != NULL)) mc_maybe_editor_or_viewer (); diff --git a/src/main.h b/src/main.h index 492dae765..1f1572681 100644 --- a/src/main.h +++ b/src/main.h @@ -99,12 +99,19 @@ extern GArray *panel_keymap; extern GArray *input_keymap; extern GArray *tree_keymap; extern GArray *help_keymap; +#ifdef USE_DIFF_VIEW +extern GArray *diff_keymap; +#endif extern const global_keymap_t *panel_map; extern const global_keymap_t *input_map; extern const global_keymap_t *tree_map; extern const global_keymap_t *help_map; +#ifdef USE_DIFF_VIEW +extern const global_keymap_t *diff_map; +#endif + #ifdef HAVE_SUBSHELL_SUPPORT void do_update_prompt (void); int load_prompt (int fd, void *unused); diff --git a/src/setup.c b/src/setup.c index 6e5ee6d2f..19b8d298c 100644 --- a/src/setup.c +++ b/src/setup.c @@ -1085,6 +1085,10 @@ load_keymap_defs (void) help_keymap = g_array_new (TRUE, FALSE, sizeof (global_keymap_t)); load_keymap_from_section ("help", help_keymap, mc_global_keymap); +#ifdef USE_DIFF_VIEW + diff_keymap = g_array_new (TRUE, FALSE, sizeof (global_keymap_t)); + load_keymap_from_section ("diffviewer", diff_keymap, mc_global_keymap); +#endif mc_config_deinit (mc_global_keymap); } } @@ -1114,6 +1118,10 @@ free_keymap_defs (void) g_array_free (tree_keymap, TRUE); if (help_keymap != NULL) g_array_free (help_keymap, TRUE); +#ifdef USE_DIFF_VIEW + if (diff_keymap != NULL) + g_array_free (diff_keymap, TRUE); +#endif } void