Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 11 Dec 2009 13:06:49 GMT
From:      Andrei Lavreniyuk <andy.lavr@reactor-xg.kiev.ua>
To:        freebsd-gnats-submit@FreeBSD.org
Subject:   ports/141368: [UPDATE] misc/mc: update to 4.6.2_1
Message-ID:  <200912111306.nBBD6nlT039583@www.freebsd.org>
Resent-Message-ID: <200912111310.nBBDA5Sk098499@freefall.freebsd.org>

next in thread | raw e-mail | index | archive | help

>Number:         141368
>Category:       ports
>Synopsis:       [UPDATE] misc/mc: update to 4.6.2_1
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    freebsd-ports-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri Dec 11 13:10:05 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator:     Andrei Lavreniyuk
>Release:        FreeBSD 8.0-STABLE
>Organization:
Technica-03, Inc.
>Environment:
FreeBSD datacenter.technica-03.local 8.0-STABLE FreeBSD 8.0-STABLE #0: Sat Dec  5 23:38:56 EET 2009     root@datacenter.technica-03.local:/usr/obj/usr/src/sys/SMP64  amd64
>Description:

 Adds new features:

1 - Diff Viewer (Ctrl-x-y)

2 - Show hidden files (Ctrl-.)

*- adds new patch


>How-To-Repeat:

>Fix:


Patch attached with submission follows:

diff -ruN mc.bak/Makefile mc/Makefile
--- mc.bak/Makefile	2009-12-10 21:13:00.000000000 +0200
+++ mc/Makefile	2009-12-11 14:59:16.765071101 +0200
@@ -7,6 +7,7 @@
 
 PORTNAME=	mc
 PORTVERSION=	4.6.2
+PORTREVISION=	1
 CATEGORIES=	misc shells
 MASTER_SITES=	http://www.midnight-commander.org/downloads/ \
 		${MASTER_SITE_SUNSITE}
@@ -118,6 +119,11 @@
 		-e 's|/usr/bin/unzip|${LOCALBASE}/bin/unzip|' \
 		${WRKSRC}/configure
 
+pre-configure:
+	@${RM} ${WRKSRC}/configure
+	@${CHMOD} 755 ${WRKSRC}/autogen.sh
+	@cd ${WRKSRC} && ${SH} autogen.sh
+
 post-install:
 	@${LN} -sf mc ${PREFIX}/bin/midc
 
diff -ruN mc.bak/files/patch-Makefile.am mc/files/patch-Makefile.am
--- mc.bak/files/patch-Makefile.am	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-Makefile.am	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,11 @@
+--- src/Makefile.am.orig	2009-12-10 21:32:50.000000000 +0200
++++ src/Makefile.am	2009-12-10 21:34:57.436062336 +0200
+@@ -64,7 +64,7 @@
+ 	tree.c tree.h treestore.c treestore.h timefmt.h tty.c tty.h user.c	\
+ 	user.h util.c util.h utilunix.c view.c view.h vfsdummy.h widget.c	\
+ 	widget.h win.c win.h wtools.c wtools.h unixcompat.h		\
+-	x11conn.h x11conn.c ecs.h ecs.c
++	x11conn.h x11conn.c ecs.h ecs.c ydiff.c ydiff.h
+ 
+ if CHARSET
+ mc_SOURCES = $(SRCS) $(CHARSET_SRC)
diff -ruN mc.bak/files/patch-cmd.c mc/files/patch-cmd.c
--- mc.bak/files/patch-cmd.c	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-cmd.c	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,54 @@
+--- src/cmd.c	2005-05-27 17:19:18.000000000 +0300
++++ src/cmd.c	2007-05-05 13:11:08.000000000 +0300
+@@ -48,6 +48,7 @@
+ #include "tty.h"		/* LINES */
+ #include "dialog.h"		/* Widget */
+ #include "view.h"		/* mc_internal_viewer() */
++#include "ydiff.h"		/* diff_view() */
+ #include "wtools.h"		/* message() */
+ #include "widget.h"		/* push_history() */
+ #include "key.h"		/* application_keypad_mode() */
+@@ -840,6 +841,43 @@
+ 		   "listing mode to use this command "));
+     }
+ }
++ 
++#ifdef USE_DIFF_VIEW
++void
++diff_view_cmd (void)
++{
++    int rv = -1;
++    char *file1;
++    char *file2;
++
++    if (!S_ISREG(selection(current_panel)->st.st_mode) ||
++	!S_ISREG(selection(other_panel)->st.st_mode)) {
++	message (1, MSG_ERROR,
++		 _(" Both files must be regular files "));
++	return;
++    }
++
++    file1 = mhl_str_dir_plus_file(current_panel->cwd, selection(current_panel)->fname);
++    file2 = mhl_str_dir_plus_file(other_panel->cwd, selection(other_panel)->fname);
++
++    if (file1 != NULL && file2 != NULL) {
++	/*if (get_current_index()) {
++	    char *tmp = file1;
++	    file1 = file2;
++	    file2 = tmp;
++	}*/
++	rv = diff_view(file1, file2);
++    }
++
++    g_free(file2);
++    g_free(file1);
++
++    if (rv != 0) {
++	message (1, MSG_ERROR,
++		 _(" Error building diff "));
++    }
++}
++#endif
+ 
+ void
+ history_cmd (void)
diff -ruN mc.bak/files/patch-cmd.h mc/files/patch-cmd.h
--- mc.bak/files/patch-cmd.h	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-cmd.h	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,10 @@
+--- src/cmd.h	2004-08-29 19:43:04.000000000 +0300
++++ src/cmd.h	2007-05-05 13:11:08.000000000 +0300
+@@ -35,6 +35,7 @@
+ void edit_syntax_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 -ruN mc.bak/files/patch-color.c mc/files/patch-color.c
--- mc.bak/files/patch-color.c	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-color.c	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,30 @@
+--- src/color.c	2005-05-27 17:19:18.000000000 +0300
++++ src/color.c	2007-05-05 13:12:22.000000000 +0300
+@@ -100,6 +100,13 @@
+ /* error dialog colors start at 37 */
+     { "errdhotnormal=",  0, 0 }, /* Error dialog normal/hot */ /* 37 */
+     { "errdhotfocus=",   0, 0 }, /* Error dialog focused/hot */
++
++/* diff viewer colors start at 39 */
++    { "dffadd=",  0, 0 },	/* added line */
++    { "dffchg=",  0, 0 },	/* changed line */
++    { "dffchh=",  0, 0 },	/* changed line (highlight) */
++    { "dffchd=",  0, 0 },	/* changed line (deleted) */
++    { "dffdel=",  0, 0 },	/* deleted line */
+ };
+ 
+ struct color_table_s {
+@@ -162,7 +169,12 @@
+ "editbold=yellow,blue:"
+ "editmarked=black,cyan:"
+ "errdhotnormal=yellow,red:"
+-"errdhotfocus=yellow,lightgray";
++"errdhotfocus=yellow,lightgray:"
++"dffadd=black,green:"
++"dffchg=black,brown:"
++"dffchh=black,magenta:"
++"dffchd=lightgray,black:"
++"dffdel=lightgray,red";
+ 
+ #ifdef HAVE_SLANG
+ #   define color_value(i) color_table [i].name
diff -ruN mc.bak/files/patch-color.h mc/files/patch-color.h
--- mc.bak/files/patch-color.h	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-color.h	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,16 @@
+--- src/color.h	2004-09-02 01:33:43.000000000 +0300
++++ src/color.h	2007-05-05 13:12:52.000000000 +0300
+@@ -83,6 +83,13 @@
+ #define ERROR_HOT_NORMAL   IF_COLOR (37, 0)
+ #define ERROR_HOT_FOCUS    IF_COLOR (38, 0)
+ 
++/* Diff colors */
++#define DFFADD_COLOR	IF_COLOR(39, A_BOLD)
++#define DFFCHG_COLOR	IF_COLOR(40, A_UNDERLINE)
++#define DFFCHH_COLOR	IF_COLOR(41, A_UNDERLINE)
++#define DFFCHD_COLOR	IF_COLOR(42, A_REVERSE)
++#define DFFDEL_COLOR	IF_COLOR(43, A_REVERSE)
++
+ #ifdef HAVE_SLANG
+ #   define CTYPE const char *
+ #else
diff -ruN mc.bak/files/patch-configure.ac mc/files/patch-configure.ac
--- mc.bak/files/patch-configure.ac	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-configure.ac	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,40 @@
+--- configure.ac	2005-07-23 19:52:49.000000000 +0300
++++ configure.ac	2007-05-05 13:11:08.000000000 +0300
+@@ -514,6 +514,22 @@
+ fi
+ 
+ 
++dnl
++dnl Diff viewer support.
++dnl
++AC_ARG_WITH(diff,
++        [  --with-diff              Enable diff viewer [[yes]]])
++
++if test x$with_diff != 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=""
+ case $host_os in
+@@ -583,6 +599,7 @@
+ fi
+ 
+ AM_CONDITIONAL(USE_EDIT, [test -n "$use_edit"])
++AM_CONDITIONAL(USE_DIFF, [test -n "$use_diff"])
+ AM_CONDITIONAL(USE_VFS, [test "x$use_vfs" = xyes])
+ AM_CONDITIONAL(USE_VFS_NET, [test x"$use_net_code" = xtrue])
+ AM_CONDITIONAL(USE_UNDEL_FS, [test -n "$use_undelfs"])
+@@ -656,5 +673,6 @@
+   X11 events support:         ${textmode_x11_support}
+   With subshell support:      ${subshell}
+   Internal editor:            ${edit_msg}
++  Diff viewer:                ${diff_msg}
+   Support for charset:        ${charset_msg}
+ "
diff -ruN mc.bak/files/patch-main.c mc/files/patch-main.c
--- mc.bak/files/patch-main.c	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-main.c	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,30 @@
+--- src/main.c	2005-07-23 19:52:02.000000000 +0300
++++ src/main.c	2007-05-05 13:13:29.000000000 +0300
+@@ -876,6 +876,9 @@
+     {' ', N_("s&Wap panels          C-u"), 'W', swap_cmd},
+     {' ', N_("switch &Panels on/off C-o"), 'P', view_other_cmd},
+     {' ', N_("&Compare directories  C-x d"), 'C', compare_dirs_cmd},
++#ifdef USE_DIFF_VIEW
++    {' ', N_("&View diff files      C-x C-y"), 'V', diff_view_cmd},
++#endif
+     {' ', N_("e&Xternal panelize    C-x !"), 'X', external_panelize},
+     {' ', N_("show directory s&Izes"), 'I', dirsizes_cmd},
+     {' ', "", ' ', 0},
+@@ -1216,6 +1219,9 @@
+ static const key_map ctl_x_map[] = {
+     {XCTRL ('c'), quit_cmd},
+     {'d', compare_dirs_cmd},
++#ifdef USE_DIFF_VIEW
++    {XCTRL ('y'), diff_view_cmd},
++#endif
+ #ifdef USE_VFS
+     {'a', reselect_vfs},
+ #endif				/* USE_VFS */
+@@ -1882,6 +1888,7 @@
+ 	    "                 errdhotfocus\n"
+ 	    "   Menus:        menu, menuhot, menusel, menuhotsel\n"
+ 	    "   Editor:       editnormal, editbold, editmarked\n"
++	    "   Diff viewer:  dffadd, dffchg, dffchh, dffchd, dffdel\n"
+ 	    "   Help:         helpnormal, helpitalic, helpbold, helplink, helpslink\n"
+ 	    "   File types:   directory, executable, link, stalelink, device, special, core\n"
+ 	    "\n" "Colors:\n"
diff -ruN mc.bak/files/patch-rh-hint-crash-fix mc/files/patch-rh-hint-crash-fix
--- mc.bak/files/patch-rh-hint-crash-fix	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-rh-hint-crash-fix	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,14 @@
+--- src/util.c.hintchk	2008-03-27 12:39:54.000000000 +0100
++++ src/util.c	2008-03-27 12:46:39.000000000 +0100
+@@ -995,6 +995,11 @@ load_mc_home_file (const char *filename,
+     if (hintfile != hintfile_base)
+ 	g_free (hintfile_base);
+ 
++    if (!data) {
++	g_free(hintfile);
++	return NULL;
++    }
++
+     if (allocated_filename)
+ 	*allocated_filename = hintfile;
+     else
diff -ruN mc.bak/files/patch-togle_hidden_files mc/files/patch-togle_hidden_files
--- mc.bak/files/patch-togle_hidden_files	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-togle_hidden_files	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,52 @@
+--- src/main.c.orig	2006-09-22 18:14:58.000000000 +0300
++++ src/main.c	2008-09-28 22:48:43.000000000 +0300
+@@ -1018,6 +1018,13 @@
+ {
+     show_dot_files = !show_dot_files;
+     update_panels (UP_RELOAD, UP_KEEPSEL);
++	do_refresh();
++}
++
++void toggle_horiz_vert_layout(void) {
++	horizontal_split	= !horizontal_split;
++	layout_change();
++	do_refresh();
+ }
+ 
+ /*
+@@ -1292,6 +1299,12 @@
+     /* Swap panels */
+     {XCTRL ('u'), swap_cmd},
+ 
++	/* Toggle Hidden Files */
++	{ALT ('.'), toggle_show_hidden},
++
++	/* Toggle Horizontal/Vertical layout */
++	{ALT (','), toggle_horiz_vert_layout},
++
+     /* View output */
+     {XCTRL ('o'), view_other_cmd},
+ 
+--- src/layout.c.orig	2006-11-08 15:37:25.000000000 +0200
++++ src/layout.c	2008-09-28 11:47:19.000000000 +0300
+@@ -488,9 +488,7 @@
+     radio_widget->sel = horizontal_split;
+ }
+ 
+-static void
+-layout_change (void)
+-{
++void layout_change(void) {
+     setup_panels ();
+     /* re-init the menu, because perhaps there was a change in the way 
+        how the panel are split (horizontal/vertical). */
+--- src/layout.h.orig	2004-12-03 21:17:47.000000000 +0200
++++ src/layout.h	2008-09-28 11:47:13.000000000 +0300
+@@ -8,6 +8,7 @@
+ void init_curses (void);
+ void done_screen (void);
+ void setup_panels (void);
++void layout_change(void);
+ void destroy_panels (void);
+ void move_resize_panel (void);
+ void flag_winch (int dummy);
diff -ruN mc.bak/files/patch-ydiff.c mc/files/patch-ydiff.c
--- mc.bak/files/patch-ydiff.c	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-ydiff.c	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,2679 @@
+--- src/ydiff.c.orig	2009-12-10 23:44:56.944204631 +0200
++++ src/ydiff.c	2009-12-10 23:46:06.709619876 +0200
+@@ -0,0 +1,2676 @@
++/*
++ * Copyright (c) 2007 Daniel Borca  All rights reserved.
++ *
++ * 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 <config.h>
++#include <ctype.h>
++#include <errno.h>
++#include <sys/stat.h>
++#include "global.h"
++#include "tty.h"
++#include "cmd.h"
++#include "dialog.h"
++#include "widget.h"
++#include "color.h"
++#include "help.h"
++#include "key.h"
++#include "wtools.h"
++#include "charsets.h"
++#include "ydiff.h"
++#include "tty.h"
++
++
++#ifdef USE_DIFF_VIEW
++
++#include "main.h"
++#define tty_setcolor		attrset
++#define tty_gotoyx		move
++#define tty_print_char		addch
++#define tty_print_string	addstr
++#define tty_printf		printw
++#define tty_print_alt_char(c)	do { acs(); addch(c); noacs(); } while (0)
++static void
++tty_print_nstring (const char *s, int n)
++{
++    int ch;
++    while (n-- > 0 && (ch = *s++)) {
++	addch(ch);
++    }
++}
++
++#define FILE_READ_BUF	4096
++#define FILE_FLAG_TEMP	(1 << 0)
++
++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 struct {
++    int len, max;
++    DIFFCMD *data;
++} DIFFOPS;
++
++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 struct {
++    int len, max;
++    BRACKET *data;
++    int error;
++} HDIFF;
++
++typedef int PAIR[2];
++
++typedef struct {
++    int len, max;
++    PAIR *data;
++} LCSET;
++
++#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 {
++    int len, max;
++    DIFFLN *data;
++    int error;
++} DIFFCC;
++
++typedef struct {
++    FBUF *f;
++    DIFFCC *a;
++    DSRC dsrc;
++} PRINTER_CTX;
++
++typedef struct {
++    int quality;
++    int strip_trailing_cr;
++    int ignore_tab_expansion;
++    int ignore_space_change;
++    int ignore_all_space;
++    int ignore_case;
++} DIFFOPT;
++
++typedef struct {
++    Widget widget;
++
++    const char *args;		/* Args passed to diff */
++    const char *file[2];	/* filenames */
++    FBUF *f[2];
++    DIFFCC a[2];
++    HDIFF **hdiff;
++    int ndiff;			/* number of hunks */
++    DSRC dsrc;			/* data source: memory or temporary file */
++
++    int view_quit:1;		/* Quit flag */
++
++    int height;
++    int width1;
++    int width2;
++    int new_frame;
++    int skip_rows;
++    int skip_cols;
++    int display_symbols;
++    int display_numbers;
++    int show_cr;
++    int show_hdiff;
++    int tab_size;
++    int ord;
++
++    DIFFOPT opt;
++} WDiff;
++
++
++#define OPTX 50
++#define OPTY 12
++
++static const char *quality_str[] = {
++    N_("&Normal"),
++    N_("&Fastest"),
++    N_("&Highest")
++};
++
++static QuickWidget diffopt_widgets[] = {
++    { quick_button,   6,   10, 9, OPTY, N_("&Cancel"),                 0, B_CANCEL, NULL, NULL, NULL },
++    { quick_button,   3,   10, 9, OPTY, N_("&OK"),                     0, B_ENTER,  NULL, NULL, NULL },
++    { quick_radio,   34, OPTX, 4, OPTY, "",                            3, 2,        NULL, const_cast(char **, quality_str), NULL },
++    { quick_checkbox, 4, OPTX, 7, OPTY, N_("strip trailing &CR"),      0, 0,        NULL, NULL, NULL },
++    { quick_checkbox, 4, OPTX, 6, OPTY, N_("ignore all &Whitespace"),  0, 0,        NULL, NULL, NULL },
++    { quick_checkbox, 4, OPTX, 5, OPTY, N_("ignore &Space change"),    0, 0,        NULL, NULL, NULL },
++    { quick_checkbox, 4, OPTX, 4, OPTY, N_("ignore tab &Expansion"),   0, 0,        NULL, NULL, NULL },
++    { quick_checkbox, 4, OPTX, 3, OPTY, N_("&Ignore case"),            0, 0,        NULL, NULL, NULL },
++    NULL_QuickWidget
++};
++
++static QuickDialog diffopt = {
++    OPTX, OPTY, -1, -1,
++    N_(" Diff Options "), "[Diff Options]",
++    diffopt_widgets, 0
++};
++
++
++/* 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
++ * \note tries mc_tmpdir() and then current directory
++ */
++static int
++open_temp (void **name)
++{
++    int fd;
++    int len;
++    char *temp;
++    const char *pattern = "mcdiffXXXXXX";
++    const char *env = mc_tmpdir();
++
++    if (env == NULL) {
++	env = "";
++    }
++
++    len = strlen(env);
++    temp = malloc(len + 1 + strlen(pattern) + 1);
++    if (temp == NULL) {
++	return -1;
++    }
++
++    if (len) {
++	strcpy(temp, env);
++	if (temp[len - 1] != PATH_SEP) {
++	    temp[len++] = PATH_SEP;
++	}
++    }
++    strcpy(temp + len, pattern);
++
++    fd = mkstemp(temp);
++    if (fd < 0) {
++	if (len) {
++	    strcpy(temp, pattern);
++	    fd = mkstemp(temp);
++	}
++	if (fd < 0) {
++	    free(temp);
++	    return -1;
++	}
++    }
++
++    *name = temp;
++    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 ****************************************************************/
++
++
++/**
++ * Initialize list of diff statements.
++ *
++ * \param ops list, must be non-NULL
++ */
++static void
++dff_init (DIFFOPS *ops)
++{
++    ops->len = ops->max = 0;
++    ops->data = NULL;
++}
++
++
++/**
++ * Enlarge list of diff statements.
++ *
++ * \param ops list, must be non-NULL
++ *
++ * \return new element, or NULL if error
++ */
++static DIFFCMD *
++dff_enlarge (DIFFOPS *ops)
++{
++    DIFFCMD *p;
++    if (ops->len == ops->max) {
++	int max = ops->max + 64;
++	p = realloc(ops->data, max * sizeof(ops->data[0]));
++	if (p == NULL) {
++	    return NULL;
++	}
++	ops->max = max;
++	ops->data = p;
++    }
++    p = ops->data + ops->len++;
++    return p;
++}
++
++
++/**
++ * Free list of diff statements.
++ *
++ * \param ops list, must be non-NULL
++ */
++static void
++dff_free (DIFFOPS *ops)
++{
++    free(ops->data);
++    dff_init(ops);
++}
++
++
++/**
++ * 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, DIFFOPS *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 = dff_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, DIFFOPS *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, DIFFOPS *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;
++    }
++
++    dff_init(ops);
++    rv = scan_diff(f, ops);
++    code = p_close(f);
++
++    if (rv < 0 || code == -1 || !WIFEXITED(code) || WEXITSTATUS(code) == 2) {
++	dff_free(ops);
++	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 DIFFOPS *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 ***********************************************************/
++
++
++/**
++ * Initialize list of longest common substring offsets.
++ *
++ * \param set list, must be non-NULL
++ */
++static void
++lcs_init (LCSET *set)
++{
++    set->len = set->max = 0;
++    set->data = NULL;
++}
++
++
++/**
++ * Reset list of longest common substring offsets.
++ *
++ * \param set list, must be non-NULL
++ *
++ * \note does nto deallocate storage
++ */
++static void
++lcs_reset (LCSET *set)
++{
++    set->len = 0;
++}
++
++
++/**
++ * Enlarge list of longest common substring offsets.
++ *
++ * \param set list, must be non-NULL
++ *
++ * \return new element, or NULL if error
++ */
++static PAIR *
++lcs_enlarge (LCSET *set)
++{
++    PAIR *p;
++    if (set->len == set->max) {
++	int max = set->max + 4;
++	p = realloc(set->data, max * sizeof(set->data[0]));
++	if (p == NULL) {
++	    return NULL;
++	}
++	set->max = max;
++	set->data = p;
++    }
++    p = set->data + set->len++;
++    return p;
++}
++
++
++/**
++ * Free list of longest common substring offsets.
++ *
++ * \param set list, must be non-NULL
++ */
++static void
++lcs_free (LCSET *set)
++{
++    free(set->data);
++    lcs_init(set);
++}
++
++
++/**
++ * Initialize list of horizontal diff ranges.
++ *
++ * \param set list, must be non-NULL
++ */
++static void
++hdiff_init (HDIFF *set)
++{
++    set->len = set->max = 0;
++    set->data = NULL;
++    set->error = 0;
++}
++
++
++/**
++ * Enlarge list of horizontal diff ranges.
++ *
++ * \param set list, must be non-NULL
++ *
++ * \return new element, or NULL if error
++ */
++static BRACKET *
++hdiff_enlarge (HDIFF *set)
++{
++    BRACKET *p;
++    if (set->error) {
++	return NULL;
++    }
++    if (set->len == set->max) {
++	int max = set->max + 4;
++	p = realloc(set->data, max * sizeof(set->data[0]));
++	if (p == NULL) {
++	    return NULL;
++	}
++	set->max = max;
++	set->data = p;
++    }
++    p = set->data + set->len++;
++    return p;
++}
++
++
++/**
++ * Free list of horizontal diff ranges.
++ *
++ * \param set list, must be non-NULL
++ */
++static void
++hdiff_free (HDIFF *set)
++{
++    free(set->data);
++    hdiff_init(set);
++}
++
++
++/**
++ * 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, LCSET *ret, int min)
++{
++    int i, j;
++
++    int *Lprev, *Lcurr;
++
++    int z = 0;
++
++    lcs_init(ret);
++
++    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;
++		    lcs_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 = lcs_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:
++    lcs_free(ret);
++    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, HDIFF *hdiff, unsigned int depth)
++{
++    BRACKET *p;
++
++    if (depth--) {
++	LCSET 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;
++
++	    b[0].off = bracket[0].off;
++	    b[0].len = ret.data[k][0];
++	    b[1].off = bracket[1].off;
++	    b[1].len = ret.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 + ret.data[k][0] + len;
++		b[0].len = ret.data[k + 1][0] - ret.data[k][0] - len;
++		b[1].off = bracket[1].off + ret.data[k][1] + len;
++		b[1].len = ret.data[k + 1][1] - ret.data[k][1] - len;
++		hdiff_multi(s, t, b, min, hdiff, depth);
++	    }
++
++	    b[0].off = bracket[0].off + ret.data[k][0] + len;
++	    b[0].len = bracket[0].len - ret.data[k][0] - len;
++	    b[1].off = bracket[1].off + ret.data[k][1] + len;
++	    b[1].len = bracket[1].len - ret.data[k][1] - len;
++	    hdiff_multi(s, t, b, min, hdiff, depth);
++
++	    lcs_free(&ret);
++	    return 0;
++	}
++    }
++
++    p = hdiff_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, HDIFF *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) */
++
++    hdiff_init(hdiff);
++    hdiff_multi(s, t, b, min, hdiff, depth);
++    if (hdiff->error) {
++	hdiff_free(hdiff);
++	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, HDIFF *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, HDIFF *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 *******************************************************/
++
++
++/**
++ * Initialize diff cache.
++ *
++ * \param a cache, must be non-NULL
++ */
++static void
++cc_init (DIFFCC *a)
++{
++    a->len = a->max = 0;
++    a->data = NULL;
++    a->error = 0;
++}
++
++
++/**
++ * Enlarge diff cache.
++ *
++ * \param a cache, must be non-NULL
++ *
++ * \return new element, or NULL if error
++ */
++static DIFFLN *
++cc_enlarge (DIFFCC *a)
++{
++    DIFFLN *p;
++    if (a->error) {
++	return NULL;
++    }
++    if (a->len == a->max) {
++	int max = a->max + 256;
++	p = realloc(a->data, max * sizeof(a->data[0]));
++	if (p == NULL) {
++	    a->error = -1;
++	    return NULL;
++	}
++	a->max = max;
++	a->data = p;
++    }
++    p = a->data + a->len++;
++    p->p = NULL;
++    return p;
++}
++
++
++/**
++ * Free diff cache.
++ *
++ * \param a cache, must be non-NULL
++ */
++static void
++cc_free (DIFFCC *a)
++{
++    int i;
++    DIFFLN *p;
++    for (p = a->data, i = 0; i < a->len; i++, p++) {
++	if (p->p) {
++	    free(p->p);
++	}
++    }
++    free(a->data);
++    cc_init(a);
++}
++
++
++static int
++printer (void *ctx, int ch, int line, off_t off, size_t sz, const char *str)
++{
++    DIFFLN *p;
++    DIFFCC *a = ((PRINTER_CTX *)ctx)->a;
++    DSRC dsrc = ((PRINTER_CTX *)ctx)->dsrc;
++    if (a->error) {
++	return -1;
++    }
++    if (ch) {
++	p = cc_enlarge(a);
++	if (p == NULL) {
++	    return -1;
++	}
++	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 = 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 *view)
++{
++    FBUF *const *f = view->f;
++    DIFFCC *a = view->a;
++
++    PRINTER_CTX ctx;
++    DIFFOPS ops;
++    int ndiff;
++    int rv;
++
++    char extra[256];
++
++    extra[0] = '\0';
++    if (view->opt.quality == 2) {
++	strcat(extra, " -d");
++    }
++    if (view->opt.quality == 1) {
++	strcat(extra, " --speed-large-files");
++    }
++    if (view->opt.strip_trailing_cr) {
++	strcat(extra, " --strip-trailing-cr");
++    }
++    if (view->opt.ignore_tab_expansion) {
++	strcat(extra, " -E");
++    }
++    if (view->opt.ignore_space_change) {
++	strcat(extra, " -b");
++    }
++    if (view->opt.ignore_all_space) {
++	strcat(extra, " -w");
++    }
++    if (view->opt.ignore_case) {
++	strcat(extra, " -i");
++    }
++
++    if (view->dsrc != DATA_SRC_MEM) {
++	f_reset(f[0]);
++	f_reset(f[1]);
++    }
++
++    ndiff = dff_execute(view->args, extra, view->file[0], view->file[1], &ops);
++    if (ndiff < 0) {
++	return -1;
++    }
++
++    ctx.dsrc = view->dsrc;
++
++    rv = 0;
++
++    cc_init(&a[0]);
++    ctx.a = &a[0];
++    ctx.f = f[0];
++    rv |= dff_reparse(0, view->file[0], &ops, printer, &ctx);
++
++    cc_init(&a[1]);
++    ctx.a = &a[1];
++    ctx.f = f[1];
++    rv |= dff_reparse(1, view->file[1], &ops, printer, &ctx);
++
++    dff_free(&ops);
++
++    if (rv || a[0].error || a[1].error || a[0].len != a[1].len) {
++	cc_free(&a[0]);
++	cc_free(&a[1]);
++	return -1;
++    }
++
++    if (view->dsrc == DATA_SRC_TMP) {
++	f_trunc(f[0]);
++	f_trunc(f[1]);
++    }
++
++    if (view->dsrc == DATA_SRC_MEM && HDIFF_ENABLE) {
++	view->hdiff = malloc(a[0].len * sizeof(HDIFF *));
++	if (view->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++) {
++		HDIFF *h = NULL;
++		if (p->line && q->line && p->ch == CHG_CH) {
++		    h = malloc(sizeof(HDIFF));
++		    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;
++			}
++		    }
++		}
++		view->hdiff[i] = h;
++	    }
++	}
++    }
++
++    return ndiff;
++}
++
++
++static void
++destroy_hdiff (WDiff *view)
++{
++    if (view->hdiff != NULL) {
++	int i;
++	int len = view->a[0].len;
++	for (i = 0; i < len; i++) {
++	    HDIFF *h = view->hdiff[i];
++	    if (h != NULL) {
++		hdiff_free(h);
++		free(h);
++	    }
++	}
++	free(view->hdiff);
++	view->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 DIFFCC *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 = 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 DIFFCC *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 DIFFCC *a, int pos)
++{
++#if 1
++    while (pos > 0 && a->data[pos].ch != EQU_CH) {
++	pos--;
++    }
++    while (pos > 0 && a->data[pos].ch == EQU_CH) {
++	pos--;
++    }
++#else
++    while (pos > 0 && a->data[pos - 1].ch == EQU_CH) {
++	pos--;
++    }
++    while (pos > 0 && a->data[pos - 1].ch != EQU_CH) {
++	pos--;
++    }
++#endif
++
++    return pos;
++}
++
++
++static int
++find_next_hunk (const DIFFCC *a, int pos)
++{
++    while (pos < a->len && a->data[pos].ch != EQU_CH) {
++	pos++;
++    }
++    while (pos < a->len && a->data[pos].ch == EQU_CH) {
++	pos++;
++    }
++
++    return pos;
++}
++
++
++/* view routines and callbacks ***********************************************/
++
++
++static void
++view_compute_areas (WDiff *view)
++{
++    view->height = LINES - 2;
++    view->width1 = COLS / 2;
++    view->width2 = COLS / 2 + (COLS & 1);
++}
++
++
++static int
++view_init (WDiff *view, const char *args, const char *file1, const char *file2, 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;
++	}
++    }
++
++    view->args = args;
++    view->file[0] = file1;
++    view->file[1] = file2;
++    view->f[0] = f[0];
++    view->f[1] = f[1];
++    view->hdiff = NULL;
++    view->dsrc = dsrc;
++
++    ndiff = redo_diff(view);
++    if (ndiff < 0) {
++	goto err_3;
++    }
++
++    view->ndiff = ndiff;
++
++    view->view_quit = 0;
++
++    view->new_frame = 1;
++    view->skip_rows = 0;
++    view->skip_cols = 0;
++    view->display_symbols = 0;
++    view->display_numbers = 0;
++    view->show_cr = 1;
++    view->show_hdiff = 1;
++    view->tab_size = 8;
++    view->ord = 0;
++
++    view->opt.quality = 0;
++    view->opt.strip_trailing_cr = 0;
++    view->opt.ignore_tab_expansion = 0;
++    view->opt.ignore_space_change = 0;
++    view->opt.ignore_all_space = 0;
++    view->opt.ignore_case = 0;
++
++    view_compute_areas(view);
++    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 *view)
++{
++    int ndiff = view->ndiff;
++
++    diffopt_widgets[2].value = view->opt.quality;
++    diffopt_widgets[2].result = &view->opt.quality;
++    diffopt_widgets[3].result = &view->opt.strip_trailing_cr;
++    diffopt_widgets[4].result = &view->opt.ignore_all_space;
++    diffopt_widgets[5].result = &view->opt.ignore_space_change;
++    diffopt_widgets[6].result = &view->opt.ignore_tab_expansion;
++    diffopt_widgets[7].result = &view->opt.ignore_case;
++
++    if (quick_dialog(&diffopt) != B_CANCEL) {
++	destroy_hdiff(view);
++	cc_free(&view->a[1]);
++	cc_free(&view->a[0]);
++	ndiff = redo_diff(view);
++	if (ndiff >= 0) {
++	    view->ndiff = ndiff;
++	}
++    }
++    return ndiff;
++}
++
++
++static void
++view_fini (WDiff *view)
++{
++    if (view->dsrc != DATA_SRC_MEM) {
++	f_close(view->f[1]);
++	f_close(view->f[0]);
++    }
++
++    destroy_hdiff(view);
++    cc_free(&view->a[1]);
++    cc_free(&view->a[0]);
++}
++
++
++static int
++view_display_file (const WDiff *view, int ord,
++		   int r, int c, int height, int width)
++{
++    int i, j;
++    char buf[BUFSIZ];
++    FBUF *f = view->f[ord];
++    const DIFFCC *a = &view->a[ord];
++    int skip = view->skip_cols;
++    int display_symbols = view->display_symbols;
++    int display_numbers = view->display_numbers;
++    int show_cr = view->show_cr;
++    int tab_size = view->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 = view->skip_rows, j = 0, p = 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);
++		snprintf(buf, display_numbers + 1, "%*d", nwidth, p->line);
++		tty_print_string(buf);
++	    }
++	    if (f == NULL) {
++		if (view->show_hdiff) {
++		    int k;
++		    if (view->hdiff != NULL && view->hdiff[i] != NULL) {
++			char att[BUFSIZ];
++			cvt_mgeta(p->p, p->u.len, buf, width, skip, tab_size, show_cr, view->hdiff[i], ord, att);
++			tty_gotoyx(r + j, c);
++			for (k = 0; k < width; k++) {
++			    tty_setcolor(att[k] ? DFFCHH_COLOR : DFFCHG_COLOR);
++			    tty_print_char(buf[k]);
++			}
++			continue;
++		    } else if (ch == CHG_CH) {
++			int sz = cvt_mget(p->p, p->u.len, buf, width, skip, tab_size, show_cr);
++			tty_gotoyx(r + j, c);
++			tty_setcolor(DFFCHH_COLOR);
++			for (k = 0; k < sz; k++) {
++			    tty_print_char(buf[k]);
++			}
++			tty_setcolor(DFFCHG_COLOR);
++			for (; k < width; k++) {
++			    tty_print_char(buf[k]);
++			}
++			continue;
++		    }
++		}
++		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);
++	    }
++	    if (ch == ADD_CH) {
++		tty_setcolor(DFFADD_COLOR);
++	    }
++	    if (ch == CHG_CH) {
++		tty_setcolor(DFFCHG_COLOR);
++	    }
++	} else {
++	    if (display_numbers) {
++		tty_gotoyx(r + j, c - xwidth);
++		memset(buf, ' ', display_numbers);
++		buf[display_numbers] = '\0';
++		tty_print_nstring(buf, display_numbers);
++	    }
++	    memset(buf, ' ', width);
++	    buf[width] = '\0';
++	    if (ch == DEL_CH) {
++		tty_setcolor(DFFDEL_COLOR);
++	    }
++	    if (ch == CHG_CH) {
++		tty_setcolor(DFFCHD_COLOR);
++	    }
++	}
++	tty_gotoyx(r + j, c);
++	tty_print_nstring(buf, width);
++    }
++    tty_setcolor(NORMAL_COLOR);
++    if (width < xwidth - 1) {
++	width = xwidth - 1;
++    }
++    memset(buf, ' ', width);
++    buf[width] = '\0';
++    for (; j < height; j++) {
++	if (xwidth) {
++	    tty_gotoyx(r + j, c - xwidth);
++	    tty_print_nstring(buf, xwidth - 1);
++	}
++	tty_gotoyx(r + j, c);
++	tty_print_nstring(buf, width);
++    }
++
++    return 0;
++}
++
++
++static void
++view_status (const WDiff *view)
++{
++    int ord = view->ord;
++    int skip_rows = view->skip_rows;
++    int skip_cols = view->skip_cols;
++    int width1 = view->width1;
++    int width2 = view->width2;
++
++    char buf[BUFSIZ];
++    int filename_width;
++    int linenum, lineofs;
++
++    tty_setcolor(SELECTED_COLOR);
++
++    tty_gotoyx(0, 0);
++    get_line_numbers(&view->a[ord], skip_rows, &linenum, &lineofs);
++
++    filename_width = width1 - 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(view->file[ord]), buf, filename_width);
++    tty_printf("%-*s %6d+%-4d Col %-4d ", filename_width, buf, linenum, lineofs, skip_cols);
++
++    ord ^= 1;
++
++    tty_gotoyx(0, width1);
++    get_line_numbers(&view->a[ord], skip_rows, &linenum, &lineofs);
++
++    filename_width = width2 - 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(view->file[ord]), buf, filename_width);
++    tty_printf("%-*s %6d+%-4d Dif %-4d ", filename_width, buf, linenum, lineofs, view->ndiff);
++}
++
++
++static void
++view_update (WDiff *view)
++{
++    int height = view->height;
++    int width1 = view->width1;
++    int width2 = view->width2;
++
++    int last = view->a[0].len - 1;
++
++    if (view->skip_rows > last) {
++	view->skip_rows = last;
++    }
++    if (view->skip_rows < 0) {
++	view->skip_rows = 0;
++    }
++    if (view->skip_cols < 0) {
++	view->skip_cols = 0;
++    }
++
++    if (height < 2 || width1 < 2 || width2 < 2) {
++	return;
++    }
++
++    if (view->new_frame) {
++	Dlg_head *h = view->widget.parent;
++
++	int xwidth = view->display_symbols + view->display_numbers;
++
++	tty_setcolor(NORMAL_COLOR);
++	draw_box(h, 1, 0,      height, width1);
++	draw_box(h, 1, width1, height, width2);
++
++	if (xwidth) {
++	    xwidth++;
++	    if (xwidth < width1 - 1) {
++		tty_gotoyx(1, xwidth);
++		tty_print_alt_char('w'/*ACS_TTEE*/);
++		tty_gotoyx(height, xwidth);
++		tty_print_alt_char('v'/*ACS_BTEE*/);
++		tty_print_vline(2, xwidth, height - 2);
++	    }
++	    if (xwidth < width2 - 1) {
++		tty_gotoyx(1, width1 + xwidth);
++		tty_print_alt_char('w'/*ACS_TTEE*/);
++		tty_gotoyx(height, width1 + xwidth);
++		tty_print_alt_char('v'/*ACS_BTEE*/);
++		tty_print_vline(2, width1 + xwidth, height - 2);
++	    }
++	}
++
++	view->new_frame = 0;
++    }
++
++    view_status(view);
++
++    view_display_file(view, view->ord,     2, 1,          height - 2, width1 - 2);
++    view_display_file(view, view->ord ^ 1, 2, width1 + 1, height - 2, width2 - 2);
++}
++
++
++static void
++view_help_cmd (void)
++{
++    interactive_display(NULL, "[Diff Viewer]");
++}
++
++
++static void
++view_quit_cmd (WDiff *view)
++{
++    dlg_stop(view->widget.parent);
++}
++
++
++static void
++view_labels (WDiff *view)
++{
++    Dlg_head *h = view->widget.parent;
++
++    buttonbar_set_label(h, 1, _("Help"), view_help_cmd);
++
++    buttonbar_set_label_data(h, 10, _("Quit"), (buttonbarfn)view_quit_cmd, view);
++}
++
++
++static int
++view_event (Gpm_Event *event, void *x)
++{
++    WDiff *view = (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)) {
++	view->skip_rows -= 2;
++	view_update(view);
++	return result;
++    }
++    if ((event->buttons & GPM_B_DOWN) && (event->type & GPM_DOWN)) {
++	view->skip_rows += 2;
++	view_update(view);
++	return result;
++    }
++
++    return result;
++}
++
++
++static cb_ret_t
++view_handle_key (WDiff *view, int c)
++{
++    c = convert_from_input_c(c);
++
++    switch (c) {
++	case 's':
++	    view->display_symbols ^= 1;
++	    view->new_frame = 1;
++	    return MSG_HANDLED;
++
++	case 'l':
++	    view->display_numbers ^= calc_nwidth(view->a);
++	    view->new_frame = 1;
++	    return MSG_HANDLED;
++
++	case 'c':
++	    view->show_cr ^= 1;
++	    return MSG_HANDLED;
++
++	case 'h':
++	    view->show_hdiff ^= 1;
++	    return MSG_HANDLED;
++
++	case '2':
++	case '3':
++	case '4':
++	case '8':
++	    view->tab_size = c - '0';
++	    return MSG_HANDLED;
++
++	case XCTRL('u'): {
++	    view->ord ^= 1;
++	    return MSG_HANDLED;
++	}
++
++	case XCTRL('r'):
++	    if (view_reinit(view) < 0) {
++		view->view_quit = 1;
++	    } else if (view->display_numbers) {
++		int old = view->display_numbers;
++		view->display_numbers = calc_nwidth(view->a);
++		view->new_frame = (old != view->display_numbers);
++	    }
++	    return MSG_HANDLED;
++
++	case 'n':
++	    view->skip_rows = find_next_hunk(&view->a[0], view->skip_rows);
++	    return MSG_HANDLED;
++
++	case 'p':
++	    view->skip_rows = find_prev_hunk(&view->a[0], view->skip_rows);
++	    return MSG_HANDLED;
++
++	case KEY_HOME:
++	case KEY_M_CTRL | KEY_PPAGE:
++	    view->skip_rows = 0;
++	    return MSG_HANDLED;
++
++	case KEY_END:
++	case KEY_M_CTRL | KEY_NPAGE:
++	    view->skip_rows = view->a[0].len - 1;
++	    return MSG_HANDLED;
++
++	case KEY_UP:
++	    view->skip_rows--;
++	    return MSG_HANDLED;
++
++	case KEY_DOWN:
++	    view->skip_rows++;
++	    return MSG_HANDLED;
++
++	case KEY_NPAGE:
++	    view->skip_rows += view->height - 2;
++	    return MSG_HANDLED;
++
++	case KEY_PPAGE:
++	    view->skip_rows -= view->height - 2;
++	    return MSG_HANDLED;
++
++	case KEY_LEFT:
++	    view->skip_cols--;
++	    return MSG_HANDLED;
++
++	case KEY_RIGHT:
++	    view->skip_cols++;
++	    return MSG_HANDLED;
++
++	case KEY_M_CTRL | KEY_LEFT:
++	    view->skip_cols -= 8;
++	    return MSG_HANDLED;
++
++	case KEY_M_CTRL | KEY_RIGHT:
++	    view->skip_cols += 8;
++	    return MSG_HANDLED;
++
++	case XCTRL('a'):
++	    view->skip_cols = 0;
++	    return MSG_HANDLED;
++
++	case XCTRL('o'):
++	    view_other_cmd();
++	    return MSG_HANDLED;
++
++	case 'q':
++	case XCTRL('g'):
++	case ESC_CHAR:
++	    view->view_quit = 1;
++	    return MSG_HANDLED;
++    }
++
++    /* Key not used */
++    return MSG_NOT_HANDLED;
++}
++
++
++static cb_ret_t
++view_callback (Widget *w, widget_msg_t msg, int parm)
++{
++    cb_ret_t i;
++    WDiff *view = (WDiff *)w;
++    Dlg_head *h = view->widget.parent;
++
++    switch (msg) {
++	case WIDGET_INIT:
++	    view_labels(view);
++	    return MSG_HANDLED;
++
++	case WIDGET_DRAW:
++	    view->new_frame = 1;
++	    view_update(view);
++	    return MSG_HANDLED;
++
++	case WIDGET_CURSOR:
++	    return MSG_HANDLED;
++
++	case WIDGET_KEY:
++	    i = view_handle_key((WDiff *)view, parm);
++	    if (view->view_quit)
++		dlg_stop(h);
++	    else {
++		view_update(view);
++	    }
++	    return i;
++
++	case WIDGET_IDLE:
++	    return MSG_HANDLED;
++
++	case WIDGET_FOCUS:
++	    view_labels(view);
++	    return MSG_HANDLED;
++
++	case WIDGET_DESTROY:
++	    return MSG_HANDLED;
++
++	default:
++	    return default_proc(msg, parm);
++    }
++}
++
++
++static void
++view_adjust_size (Dlg_head *h)
++{
++    WDiff *view;
++    WButtonBar *bar;
++
++    /* Look up the viewer and the buttonbar, we assume only two widgets here */
++    view = (WDiff *)find_widget_type(h, view_callback);
++    bar = find_buttonbar(h);
++    widget_set_size(&view->widget, 0, 0, LINES, COLS);
++    widget_set_size((Widget *)bar, LINES - 1, 0, 1, COLS);
++
++    view_compute_areas(view);
++}
++
++
++static cb_ret_t
++view_dialog_callback (Dlg_head *h, dlg_msg_t msg, int parm)
++{
++    switch (msg) {
++	case DLG_RESIZE:
++	    view_adjust_size(h);
++	    return MSG_HANDLED;
++
++	default:
++	    return default_dlg_callback(h, msg, parm);
++    }
++}
++
++
++int
++diff_view (const char *file1, const char *file2)
++{
++    int error;
++    WDiff *view;
++    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);
++
++    view = g_new0(WDiff, 1);
++
++    init_widget(&view->widget, 0, 0, LINES, COLS,
++		(callback_fn)view_callback,
++		(mouse_h)view_event);
++
++    widget_want_cursor(view->widget, 0);
++
++    bar = buttonbar_new(1);
++
++    add_widget(view_dlg, bar);
++    add_widget(view_dlg, view);
++
++    error = view_init(view, "-a", file1, file2, DATA_SRC_MEM);
++
++    /* 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_fini(view);
++    }
++    destroy_dlg(view_dlg);
++
++    return error;
++}
++#endif
diff -ruN mc.bak/files/patch-ydiff.h mc/files/patch-ydiff.h
--- mc.bak/files/patch-ydiff.h	1970-01-01 03:00:00.000000000 +0300
+++ mc/files/patch-ydiff.h	2009-12-11 00:04:06.000000000 +0200
@@ -0,0 +1,9 @@
+--- src/ydiff.h	1970-01-01 02:00:00.000000000 +0200
++++ src/ydiff.h	2007-05-05 13:11:08.000000000 +0300
+@@ -0,0 +1,6 @@
++#ifndef YDIFF_H_included
++#define YDIFF_H_included
++
++int diff_view (const char *file1, const char *file2);
++
++#endif


>Release-Note:
>Audit-Trail:
>Unformatted:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200912111306.nBBD6nlT039583>