source: trunk/gui/src/RpDiffview.c @ 4503

Last change on this file since 4503 was 3177, checked in by mmc, 8 years ago

Updated all of the copyright notices to reference the transfer to
the new HUBzero Foundation, LLC.

File size: 97.0 KB
Line 
1/*
2 * ----------------------------------------------------------------------
3 *  RpDiffview - like a textbox, but for showing diffs
4 *
5 *  Loads two strings, performs a diff, and shows the results.  Use
6 *  it as follows:
7 *
8 *  Rappture::Diffview .dv \
9 *      -addedbackground blue -addedforeground white \
10 *      -deletedbackground red -deletedforeground white -overstrike true \
11 *      -changedbackground black -changedforeground yellow \
12 *      -xscrollcommand {.x set} -yscrollcommand {.y set}
13 * 
14 *  scrollbar .x -orient horizontal -command {.dv xview}
15 *  scrollbar .y -orient vertical -command {.dv yview}
16 * 
17 *  .dv text 1 [exec cat /tmp/file1]
18 *  .dv text 2 [exec cat /tmp/file2]
19 *  .dv configure -diff 2->1 -layout sidebyside
20 * 
21 *  puts "DIFF OUTPUT:"
22 *  puts [.dv diffs -std]
23 *
24 * ======================================================================
25 *  AUTHOR:  Michael McLennan, Purdue University
26 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
27 *
28 *  See the file "license.terms" for information on usage and
29 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
30 * ======================================================================
31 */
32#include "tk.h"
33#include <string.h>
34#include <stdlib.h>
35
36/*
37 * Special options for controlling diffs.
38 */
39enum diffdir {
40    DIFF_1TO2, DIFF_2TO1
41};
42static char *diffdirStrings[] = {
43    "1->2", "2->1", (char*)NULL
44};
45
46enum layoutStyle {
47    LAYOUT_INLINE, LAYOUT_SIDEBYSIDE
48};
49static char *layoutStyleStrings[] = {
50    "inline", "sidebyside", (char*)NULL
51};
52
53enum scancommand {
54    SCAN_MARK, SCAN_DRAGTO
55};
56static CONST char *scanCommandNames[] = {
57    "mark", "dragto", (char *) NULL
58};
59
60
61/*
62 * Data structure for line start/end for all lines in a buffer.
63 */
64typedef struct {
65    int numLines;               /* number of lines */
66    int maxLines;               /* max size of storage in startPtr/endPtr */
67    char **startPtr;            /* array of start pointers for each line */
68    int *lenPtr;                /* array of lengths for each line */
69} DiffviewLines;
70
71/*
72 * Data structure for text buffers within the widget:
73 */
74typedef struct {
75    Tcl_Obj *textObj;           /* actual text within this buffer */
76    DiffviewLines *lineLimits;  /* breakdown of textObj into line starts/ends */
77} DiffviewBuffer;
78
79/*
80 * Data structure used internally by diff routine:
81 */
82typedef struct subseq {
83    int index1;                 /* index in buffer #1 */
84    int index2;                 /* index in buffer #2 */
85    int next;                   /* next candidate match in the chain */
86} DiffviewSubseq;
87
88/*
89 * Data structure returned by diff routine:
90 */
91typedef struct {
92    int op;                     /* operation: 'a'=add 'd'=del 'c'=change */
93    int fromIndex1;             /* starts at this line in buffer 1 */
94    int toIndex1;               /* ends at this line in buffer 1 */
95    int fromIndex2;             /* starts at this line in buffer 2 */
96    int toIndex2;               /* ends at this line in buffer 2 */
97    int fromWorld;              /* starts at this line in world view */
98    int toWorld;                /* ends at this line in world view */
99} DiffviewDiffOp;
100
101typedef struct {
102    int maxDiffs;               /* maximum storage space for diffs */
103    int numDiffs;               /* number of diffs stored in ops */
104    DiffviewDiffOp *ops;        /* list of diffs */
105} DiffviewDiffs;
106
107/*
108 * Data structure used for each line of the final layout:
109 */
110typedef struct {
111    char style;                 /* layout style: 'a'=add, 'd'=del, etc. */
112    char bnum;                  /* show line from this buffer */
113    int bline;                  /* line number in buffer bnum */
114    int diffNum;                /* line is part of this diff */
115} DiffviewLayoutLine;
116
117typedef struct {
118    int maxLines;               /* maximum number of lines allocated */
119    int numLines;               /* current number of lines in layout */
120    DiffviewLayoutLine *lines;  /* info for all lines in world view */
121} DiffviewLayout;
122
123/*
124 * Data structure for the widget:
125 */
126typedef struct {
127    Tk_Window tkwin;            /* tk window for the widget */
128    Display *display;           /* display containing the widget */
129    Tcl_Interp *interp;         /* interpreter containing the widget command */
130    Tcl_Command widgetCmd;      /* token for the widget command */
131    Tk_OptionTable optionTable; /* configuration options */
132
133    /*
134     * Used to display the widget:
135     */
136    Tk_Cursor cursor;           /* cursor for the widget */
137    Tk_3DBorder normalBorder;   /* border around the widget */
138    int borderWidth;            /* width of the border in pixels */
139    int relief;                 /* relief: raised, sunken, etc. */
140    int highlightWidth;         /* width of border showing focus highlight */
141    XColor *highlightBgColor;   /* color for focus highlight bg */
142    XColor *highlightColor;     /* color for focus highlight */
143    int inset;                  /* width of all borders--offset to ul corner */
144
145    DiffviewBuffer buffer[2];   /* text to diff -- buffers #1 and #2 */
146    DiffviewDiffs *diffsPtr;    /* diff: longest common subseq btwn buffers */
147    DiffviewLayout worldview;   /* array mapping each real line in world
148                                 * coordinates to the corresponding line
149                                 * in buffer #1/#2, along with its style */
150
151    enum diffdir diffdir;       /* diff between these buffers */
152    enum layoutStyle layout;    /* layout style (side-by-side or inline) */
153    int maxWidth;               /* maximum width of world view in pixels */
154    int maxHeight;              /* maximum height of world view in pixels */
155    int lineHeight;             /* height of a line with current font */
156    int lineAscent;             /* ascent of a line with current font */
157    int topLine;                /* index of topmost line in view */
158    int btmLine;                /* index of bottommost line in view */
159    int fullLines;              /* number of full lines that fit in view */
160
161    int xOffset;                /* offset in pixels to left edge of view */
162    int xScrollUnit;            /* num pixels for one unit of horizontal
163                                 * scrolling (one average character) */
164    int yOffset;                /* offset in pixels to top edge of view */
165    int yScrollUnit;            /* num pixels for one unit of vertical
166                                 * scrolling (one average line) */
167    char *takeFocus;            /* value of -takefocus option */
168    char *yScrollCmd;           /* command prefix for scrolling */
169    char *xScrollCmd;           /* command prefix for scrolling */
170
171    int scanMarkX;              /* x-coord for "scan mark" */
172    int scanMarkY;              /* y-coord for "scan mark" */
173    int scanMarkXStart;         /* xOffset at start of scan */
174    int scanMarkYStart;         /* yOffset at start of scan */
175
176
177    Tk_Font tkfont;             /* text font for content of widget */
178    Tk_Font tklastfont;         /* previous text font (so we detect changes) */
179    XColor *fgColorPtr;         /* normal foreground color */
180    XColor *addBgColorPtr;      /* background for "added" text in diff */
181    XColor *addFgColorPtr;      /* foreground for "added" text in diff */
182    XColor *delBgColorPtr;      /* background for "deleted" text in diff */
183    XColor *delFgColorPtr;      /* foreground for "deleted" text in diff */
184    XColor *chgBgColorPtr;      /* background for "changed" text in diff */
185    XColor *chgFgColorPtr;      /* foreground for "changed" text in diff */
186    int overStrDel;             /* non-zero => overstrike deleted text */
187    GC normGC;                  /* GC for drawing normal text */
188    GC addFgGC;                 /* GC for drawing "added" text in diff */
189    GC delFgGC;                 /* GC for drawing "deleted" text in diff */
190    GC chgFgGC;                 /* GC for drawing "changed" text in diff */
191    int width;                  /* overall width of widget, in pixels */
192    int height;                 /* overall height of widget, in pixels */
193
194    int flags;                  /* flag bits for redraw, etc. */
195} Diffview;
196
197/*
198 * Tk widget flag bits:
199 *
200 * REDRAW_PENDING:      DoWhenIdle handler has been queued to redraw
201 * UPDATE_V_SCROLLBAR:  non-zero => vertical scrollbar needs updating
202 * UPDATE_H_SCROLLBAR:  non-zero => horizontal scrollbar needs updating
203 * GOT_FOCUS:           non-zero => widget has input focus
204 * FONT_CHANGED:        font changed, so layout needs to be recomputed
205 * WIDGET_DELETED:      widget has been destroyed, so don't update
206 */
207
208#define REDRAW_PENDING          1
209#define UPDATE_V_SCROLLBAR      2
210#define UPDATE_H_SCROLLBAR      4
211#define GOT_FOCUS               8
212#define FONT_CHANGED            16
213#define WIDGET_DELETED          32
214
215
216/*
217 * Default option values.
218 */
219#define BLACK                           "#000000"
220#define WHITE                           "#ffffff"
221#define NORMAL_BG                       "#d9d9d9"
222
223#define DEF_DIFFVIEW_ADDBG              "#ccffcc"
224#define DEF_DIFFVIEW_ADDFG              BLACK
225#define DEF_DIFFVIEW_BG_COLOR           NORMAL_BG
226#define DEF_DIFFVIEW_BG_MONO            WHITE
227#define DEF_DIFFVIEW_BORDERWIDTH        "2"
228#define DEF_DIFFVIEW_CHGBG              "#ffffcc"
229#define DEF_DIFFVIEW_CHGFG              BLACK
230#define DEF_DIFFVIEW_CURSOR             ""
231#define DEF_DIFFVIEW_DELBG              "#ffcccc"
232#define DEF_DIFFVIEW_DELFG              "#666666"
233#define DEF_DIFFVIEW_DIFF               "1->2"
234#define DEF_DIFFVIEW_FG                 BLACK
235#define DEF_DIFFVIEW_FONT               "Courier -12"
236#define DEF_DIFFVIEW_HEIGHT             "2i"
237#define DEF_DIFFVIEW_HIGHLIGHT_BG       NORMAL_BG
238#define DEF_DIFFVIEW_HIGHLIGHT          BLACK
239#define DEF_DIFFVIEW_HIGHLIGHT_WIDTH    "1"
240#define DEF_DIFFVIEW_LAYOUT             "inline"
241#define DEF_DIFFVIEW_OVERSTRDEL         "true"
242#define DEF_DIFFVIEW_RELIEF             "sunken"
243#define DEF_DIFFVIEW_TAKE_FOCUS         (char*)NULL
244#define DEF_DIFFVIEW_WIDTH              "2i"
245#define DEF_DIFFVIEW_SCROLL_CMD         ""
246
247/*
248 * Configuration options for this widget.
249 * (must be in alphabetical order)
250 */
251static Tk_OptionSpec optionSpecs[] = {
252    {TK_OPTION_COLOR, "-addedbackground", "addedBackground", "Background",
253         DEF_DIFFVIEW_ADDBG, -1, Tk_Offset(Diffview, addBgColorPtr),
254         TK_OPTION_NULL_OK, 0, 0},
255    {TK_OPTION_COLOR, "-addedforeground", "addedForeground", "Foreground",
256         DEF_DIFFVIEW_ADDFG, -1, Tk_Offset(Diffview, addFgColorPtr),
257         TK_OPTION_NULL_OK, 0, 0},
258    {TK_OPTION_BORDER, "-background", "background", "Background",
259         DEF_DIFFVIEW_BG_COLOR, -1, Tk_Offset(Diffview, normalBorder),
260         0, (ClientData) DEF_DIFFVIEW_BG_MONO, 0},
261    {TK_OPTION_SYNONYM, "-bd", (char*)NULL, (char*)NULL,
262         (char*)NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
263    {TK_OPTION_SYNONYM, "-bg", (char*)NULL, (char*)NULL,
264         (char*)NULL, 0, -1, 0, (ClientData) "-background", 0},
265    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
266         DEF_DIFFVIEW_BORDERWIDTH, -1, Tk_Offset(Diffview, borderWidth),
267         0, 0, 0},
268    {TK_OPTION_COLOR, "-changedbackground", "changedBackground", "Background",
269         DEF_DIFFVIEW_CHGBG, -1, Tk_Offset(Diffview, chgBgColorPtr),
270         TK_OPTION_NULL_OK, 0, 0},
271    {TK_OPTION_COLOR, "-changedforeground", "changedForeground", "Foreground",
272         DEF_DIFFVIEW_CHGFG, -1, Tk_Offset(Diffview, chgFgColorPtr),
273         TK_OPTION_NULL_OK, 0, 0},
274    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
275         DEF_DIFFVIEW_CURSOR, -1, Tk_Offset(Diffview, cursor),
276         TK_OPTION_NULL_OK, 0, 0},
277    {TK_OPTION_COLOR, "-deletedbackground", "deletedBackground", "Background",
278         DEF_DIFFVIEW_DELBG, -1, Tk_Offset(Diffview, delBgColorPtr),
279         TK_OPTION_NULL_OK, 0, 0},
280    {TK_OPTION_COLOR, "-deletedforeground", "deletedForeground", "Foreground",
281         DEF_DIFFVIEW_DELFG, -1, Tk_Offset(Diffview, delFgColorPtr),
282         TK_OPTION_NULL_OK, 0, 0},
283    {TK_OPTION_STRING_TABLE, "-diff", "diff", "Diff",
284        DEF_DIFFVIEW_DIFF, -1, Tk_Offset(Diffview, diffdir),
285        0, (ClientData)diffdirStrings, 0},
286    {TK_OPTION_SYNONYM, "-fg", "foreground", (char*)NULL,
287         (char*)NULL, 0, -1, 0, (ClientData) "-foreground", 0},
288    {TK_OPTION_FONT, "-font", "font", "Font",
289         DEF_DIFFVIEW_FONT, -1, Tk_Offset(Diffview, tkfont), 0, 0, 0},
290    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
291         DEF_DIFFVIEW_FG, -1, Tk_Offset(Diffview, fgColorPtr), 0, 0, 0},
292    {TK_OPTION_PIXELS, "-height", "height", "Height",
293         DEF_DIFFVIEW_HEIGHT, -1, Tk_Offset(Diffview, height), 0, 0, 0},
294    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
295         "HighlightBackground", DEF_DIFFVIEW_HIGHLIGHT_BG, -1,
296         Tk_Offset(Diffview, highlightBgColor), 0, 0, 0},
297    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
298         DEF_DIFFVIEW_HIGHLIGHT, -1, Tk_Offset(Diffview, highlightColor),
299         0, 0, 0},
300    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
301         "HighlightThickness", DEF_DIFFVIEW_HIGHLIGHT_WIDTH, -1,
302         Tk_Offset(Diffview, highlightWidth), 0, 0, 0},
303    {TK_OPTION_STRING_TABLE, "-layout", "layout", "Layout",
304        DEF_DIFFVIEW_LAYOUT, -1, Tk_Offset(Diffview, layout),
305        0, (ClientData)layoutStyleStrings, 0},
306    {TK_OPTION_BOOLEAN, "-overstrike", "overstrike", "Overstrike",
307         DEF_DIFFVIEW_OVERSTRDEL, -1, Tk_Offset(Diffview, overStrDel), 0, 0, 0},
308    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
309         DEF_DIFFVIEW_RELIEF, -1, Tk_Offset(Diffview, relief), 0, 0, 0},
310    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
311         DEF_DIFFVIEW_TAKE_FOCUS, -1, Tk_Offset(Diffview, takeFocus),
312         TK_OPTION_NULL_OK, 0, 0},
313    {TK_OPTION_PIXELS, "-width", "width", "Width",
314         DEF_DIFFVIEW_WIDTH, -1, Tk_Offset(Diffview, width), 0, 0, 0},
315    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
316         DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, xScrollCmd),
317         TK_OPTION_NULL_OK, 0, 0},
318    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
319         DEF_DIFFVIEW_SCROLL_CMD, -1, Tk_Offset(Diffview, yScrollCmd),
320         TK_OPTION_NULL_OK, 0, 0},
321    {TK_OPTION_END, (char*)NULL, (char*)NULL, (char*)NULL,
322        (char*)NULL, 0, -1, 0, 0, 0}
323};
324
325/*
326 * Widget commands:
327 */
328static CONST char *commandNames[] = {
329    "bbox", "cget", "configure",
330    "diffs", "scan", "see",
331    "text", "xview", "yview",
332    (char*)NULL
333};
334
335enum command {
336    COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE,
337    COMMAND_DIFFS, COMMAND_SCAN, COMMAND_SEE,
338    COMMAND_TEXT, COMMAND_XVIEW, COMMAND_YVIEW
339};
340
341/*
342 * Forward declarations for remaining procedures.
343 */
344static int              DiffviewObjCmd _ANSI_ARGS_((ClientData clientData,
345                            Tcl_Interp *interp, int objc,
346                            Tcl_Obj *CONST objv[]));
347static int              DiffviewWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
348                            Tcl_Interp *interp, int objc,
349                            Tcl_Obj *CONST objv[]));
350static int              DiffviewBboxSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
351                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
352static int              DiffviewDiffsSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
353                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
354static int              DiffviewTextSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
355                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
356static int              DiffviewXviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
357                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
358static int              DiffviewYviewSubCmd _ANSI_ARGS_ ((Tcl_Interp *interp,
359                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[]));
360static int              DiffviewGetIndex _ANSI_ARGS_ ((Tcl_Interp *interp,
361                            Diffview *dvPtr, Tcl_Obj *objPtr, int *linePtr,
362                            DiffviewDiffOp **diffOpPtrPtr));
363
364static void             DestroyDiffview _ANSI_ARGS_((char *memPtr));
365static int              ConfigureDiffview _ANSI_ARGS_((Tcl_Interp *interp,
366                            Diffview *dvPtr, int objc, Tcl_Obj *CONST objv[],
367                            int flags));
368static void             DiffviewWorldChanged _ANSI_ARGS_((
369                            ClientData instanceData));
370static void             EventuallyRedraw _ANSI_ARGS_((Diffview *dvPtr));
371static void             DisplayDiffview _ANSI_ARGS_((ClientData clientData));
372static void             DiffviewComputeGeometry _ANSI_ARGS_((Diffview *dvPtr));
373static void             DiffviewEventProc _ANSI_ARGS_((ClientData clientData,
374                            XEvent *eventPtr));
375static void             DiffviewCmdDeletedProc _ANSI_ARGS_((
376                            ClientData clientData));
377static void             ChangeDiffviewView _ANSI_ARGS_((Diffview *dvPtr,
378                            int lnum));
379static void             DiffviewScanTo _ANSI_ARGS_((Diffview *dvPtr,
380                            int x, int y));
381static void             DiffviewUpdateLayout _ANSI_ARGS_((
382                            Diffview *dvPtr));
383static void             DiffviewUpdateVScrollbar _ANSI_ARGS_((
384                            Diffview *dvPtr));
385static void             DiffviewUpdateHScrollbar _ANSI_ARGS_((
386                            Diffview *dvPtr));
387static DiffviewLines*   DiffviewLinesCreate _ANSI_ARGS_((char *textPtr,
388                            int textLen));
389static void             DiffviewLinesFree _ANSI_ARGS_((
390                            DiffviewLines *lineLimitsPtr));
391static DiffviewDiffs*   DiffviewDiffsCreate _ANSI_ARGS_((
392                            char *textPtr1, DiffviewLines *limsPtr1,
393                            char *textPtr2, DiffviewLines *limsPtr2));
394static void             DiffviewDiffsAppend _ANSI_ARGS_((
395                            DiffviewDiffs *diffsPtr, int op,
396                            int fromIndex1, int toIndex1,
397                            int fromIndex2, int toIndex2));
398static void             DiffviewDiffsFree _ANSI_ARGS_((
399                            DiffviewDiffs *diffsPtr));
400static void             DiffviewLayoutAdd _ANSI_ARGS_((
401                            DiffviewLayout *layoutPtr,
402                            DiffviewLayoutLine *linePtr));
403static void             DiffviewLayoutClear _ANSI_ARGS_((
404                            DiffviewLayout *layoutPtr));
405static void             DiffviewLayoutFree _ANSI_ARGS_((
406                            DiffviewLayout *layoutPtr));
407
408/*
409 * Standard Tk procs invoked from generic window code.
410 */
411static Tk_ClassProcs diffviewClass = {
412    sizeof(Tk_ClassProcs),      /* size */
413    DiffviewWorldChanged,       /* worldChangedProc */
414};
415
416/*
417 * ------------------------------------------------------------------------
418 *  RpDiffview_Init --
419 *
420 *  Invoked when the Rappture GUI library is being initialized
421 *  to install the "diffview" widget into the interpreter.
422 *
423 *  Returns TCL_OK if successful, or TCL_ERROR (along with an error
424 *  message in the interp) if anything goes wrong.
425 * ------------------------------------------------------------------------
426 */
427int
428RpDiffview_Init(interp)
429    Tcl_Interp *interp;         /* interpreter being initialized */
430{
431    static char *script = "source [file join $RapptureGUI::library scripts diffview.tcl]";
432
433    /* install the widget command */
434    Tcl_CreateObjCommand(interp, "Rappture::Diffview", DiffviewObjCmd,
435        NULL, NULL);
436
437    /* load the default bindings */
438    if (Tcl_Eval(interp, script) != TCL_OK) {
439        return TCL_ERROR;
440    }
441
442    return TCL_OK;
443}
444
445/*
446 * ----------------------------------------------------------------------
447 * DiffviewObjCmd()
448 *
449 * Called whenever a Diffview object is created to create the widget
450 * and install the Tcl access command.
451 * ----------------------------------------------------------------------
452 */
453static int
454DiffviewObjCmd(clientData, interp, objc, objv)
455    ClientData clientData;        /* not used */
456    Tcl_Interp *interp;                /* current interpreter */
457    int objc;                        /* number of command arguments */
458    Tcl_Obj *CONST objv[];        /* command argument objects */
459{
460    Diffview *dvPtr;
461    Tk_Window tkwin;
462
463    if (objc < 2) {
464        Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
465        return TCL_ERROR;
466    }
467
468    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
469                Tcl_GetString(objv[1]), (char*)NULL);
470
471    if (tkwin == NULL) {
472        return TCL_ERROR;
473    }
474
475    /*
476     * Create a data structure for the widget objects and set good
477     * initial values.
478     */
479    dvPtr = (Diffview*)ckalloc(sizeof(Diffview));
480    memset((void*)dvPtr, 0, (sizeof(Diffview)));
481
482    dvPtr->tkwin   = tkwin;
483    dvPtr->display = Tk_Display(tkwin);
484    dvPtr->interp  = interp;
485    dvPtr->widgetCmd = Tcl_CreateObjCommand(interp,
486            Tk_PathName(dvPtr->tkwin), DiffviewWidgetObjCmd,
487            (ClientData)dvPtr, DiffviewCmdDeletedProc);
488    dvPtr->optionTable = Tk_CreateOptionTable(interp, optionSpecs);
489
490    dvPtr->buffer[0].textObj = NULL;
491    dvPtr->buffer[0].lineLimits = NULL;
492    dvPtr->buffer[1].textObj = NULL;
493    dvPtr->buffer[1].lineLimits = NULL;
494    dvPtr->diffdir = DIFF_1TO2;
495
496    dvPtr->cursor = None;
497    dvPtr->relief = TK_RELIEF_SUNKEN;
498    dvPtr->normGC = None;
499    dvPtr->addFgGC = None;
500    dvPtr->delFgGC = None;
501    dvPtr->chgFgGC = None;
502    dvPtr->xScrollUnit = 1;
503    dvPtr->yScrollUnit = 1;
504
505    /*
506     * Use reference counting to decide when this data should be
507     * cleaned up.
508     */
509    Tcl_Preserve((ClientData) dvPtr->tkwin);
510
511    Tk_SetClass(dvPtr->tkwin, "Diffview");
512    Tk_SetClassProcs(dvPtr->tkwin, &diffviewClass, (ClientData)dvPtr);
513
514    Tk_CreateEventHandler(dvPtr->tkwin,
515            ExposureMask|StructureNotifyMask|FocusChangeMask,
516            DiffviewEventProc, (ClientData)dvPtr);
517
518    if (Tk_InitOptions(interp, (char*)dvPtr, dvPtr->optionTable, tkwin)
519            != TCL_OK) {
520        Tk_DestroyWindow(dvPtr->tkwin);
521        return TCL_ERROR;
522    }
523
524    if (ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0) != TCL_OK) {
525        Tk_DestroyWindow(dvPtr->tkwin);
526        return TCL_ERROR;
527    }
528
529    Tcl_SetResult(interp, Tk_PathName(dvPtr->tkwin), TCL_STATIC);
530    return TCL_OK;
531}
532
533/*
534 * ----------------------------------------------------------------------
535 * DiffviewWidgetObjCmd()
536 *
537 * Called to process the operations associated with the widget.  Handles
538 * the widget access command, using the operations defined in the
539 * commandNames[] array.
540 * ----------------------------------------------------------------------
541 */
542static int
543DiffviewWidgetObjCmd(clientData, interp, objc, objv)
544    ClientData clientData;    /* widget data */
545    Tcl_Interp *interp;       /* interp handling this command */
546    int objc;                 /* number of command arguments */
547    Tcl_Obj *CONST objv[];    /* command arguments */
548{
549    Diffview *dvPtr = (Diffview*)clientData;
550    int result = TCL_OK;
551    int cmdToken;
552   
553    if (objc < 2) {
554        Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?");
555        return TCL_ERROR;
556    }
557
558    /*
559     * Look up the command option and map it to an enumerated value.
560     */
561    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
562            "option", 0, &cmdToken);
563
564    if (result != TCL_OK) {
565        return result;
566    }
567
568    /* hang on to the widget data until we exit... */
569    Tcl_Preserve((ClientData)dvPtr);
570
571    switch (cmdToken) {
572        case COMMAND_BBOX: {
573            result = DiffviewBboxSubCmd(interp, dvPtr, objc, objv);
574            break;
575        }
576
577        case COMMAND_CGET: {
578            Tcl_Obj *objPtr;
579            if (objc != 3) {
580                Tcl_WrongNumArgs(interp, 2, objv, "option");
581                result = TCL_ERROR;
582                break;
583            }
584
585            objPtr = Tk_GetOptionValue(interp, (char*)dvPtr,
586                    dvPtr->optionTable, objv[2], dvPtr->tkwin);
587            if (objPtr == NULL) {
588                result = TCL_ERROR;
589                break;
590            }
591            Tcl_SetObjResult(interp, objPtr);
592            result = TCL_OK;
593            break;
594        }
595       
596        case COMMAND_CONFIGURE: {
597            Tcl_Obj *objPtr;
598            if (objc <= 3) {
599                /* query the value of an option */
600                objPtr = Tk_GetOptionInfo(interp, (char*)dvPtr,
601                        dvPtr->optionTable,
602                        (objc == 3) ? objv[2] : (Tcl_Obj*)NULL,
603                        dvPtr->tkwin);
604                if (objPtr == NULL) {
605                    result = TCL_ERROR;
606                    break;
607                } else {
608                    Tcl_SetObjResult(interp, objPtr);
609                    result = TCL_OK;
610                }
611            } else {
612                /* set one or more configuration options */
613                result = ConfigureDiffview(interp, dvPtr, objc-2, objv+2, 0);
614            }
615            break;
616        }
617
618        case COMMAND_DIFFS: {
619            result = DiffviewDiffsSubCmd(interp, dvPtr, objc, objv);
620            break;
621        }
622
623        case COMMAND_SCAN: {
624            int x, y, scanCmdIndex;
625
626            if (objc != 5) {
627                Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
628                result = TCL_ERROR;
629                break;
630            }
631
632            if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
633                    || Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
634                result = TCL_ERROR;
635                break;
636            }
637
638            result = Tcl_GetIndexFromObj(interp, objv[2], scanCommandNames,
639                    "option", 0, &scanCmdIndex);
640            if (result != TCL_OK) {
641                break;
642            }
643            switch (scanCmdIndex) {
644                case SCAN_MARK: {
645                    dvPtr->scanMarkX = x;
646                    dvPtr->scanMarkY = y;
647                    dvPtr->scanMarkXStart = dvPtr->xOffset;
648                    dvPtr->scanMarkYStart = dvPtr->yOffset;
649                    break;
650                }
651                case SCAN_DRAGTO: {
652                    DiffviewScanTo(dvPtr, x, y);
653                    break;
654                }
655            }
656            result = TCL_OK;
657            break;
658        }
659
660        case COMMAND_SEE: {
661            DiffviewDiffOp *diffOpPtr;
662            int line1, line2, topline;
663
664            if (objc != 3) {
665                Tcl_WrongNumArgs(interp, 2, objv, "index");
666                result = TCL_ERROR;
667                break;
668            }
669            result = DiffviewGetIndex(interp, dvPtr, objv[2],
670                &line1, &diffOpPtr);
671            if (result != TCL_OK) {
672                break;
673            }
674
675            if (line1 < 0 && diffOpPtr != NULL) {
676                line1 = diffOpPtr->fromWorld;
677                line2 = diffOpPtr->toWorld;
678            } else {
679                line2 = line1;
680            }
681
682            if (line1 >= dvPtr->worldview.numLines) {
683                line1 = line2 = dvPtr->worldview.numLines-1;
684            }
685            if (line1 < 0) {
686                line1 = line2 = 0;
687            }
688
689            if (line1 < dvPtr->topLine || line1 > dvPtr->btmLine) {
690                /* location off screen? then center it in y-view */
691                if (line2-line1 >= dvPtr->fullLines) {
692                    topline = line1;
693                } else {
694                    topline = (line1+line2)/2 - dvPtr->fullLines/2;
695                    if (topline < 0) topline = 0;
696                }
697                ChangeDiffviewView(dvPtr, topline);
698            }
699            result = TCL_OK;
700            break;
701        }
702
703        case COMMAND_TEXT: {
704            result = DiffviewTextSubCmd(interp, dvPtr, objc, objv);
705            break;
706        }
707
708        case COMMAND_XVIEW: {
709            result = DiffviewXviewSubCmd(interp, dvPtr, objc, objv);
710            break;
711        }
712       
713        case COMMAND_YVIEW: {
714            result = DiffviewYviewSubCmd(interp, dvPtr, objc, objv);
715            break;
716        }
717    }
718
719    /* okay, release the widget data and return */
720    Tcl_Release((ClientData)dvPtr);
721    return result;
722}
723
724/*
725 * ----------------------------------------------------------------------
726 * DiffviewBboxSubCmd()
727 *
728 * Handles the "bbox" operation on the widget.  Returns a bounding
729 * box for the specified part of the widget.
730 * ----------------------------------------------------------------------
731 */
732static int
733DiffviewBboxSubCmd(interp, dvPtr, objc, objv)
734    Tcl_Interp *interp;       /* interp handling this command */
735    Diffview *dvPtr;          /* widget data */
736    int objc;                 /* number of command arguments */
737    Tcl_Obj *CONST objv[];    /* command arguments */
738{
739    char *opt, buf[256];
740    DiffviewDiffOp *diffOpPtr;
741    int line, bnum, bline, x1, y0, y1, wd;
742    char *textPtr; int textLen;
743
744    if (objc != 3) {
745        Tcl_WrongNumArgs(interp, 2, objv, "index");
746        return TCL_ERROR;
747    }
748
749    /* make sure that our layout info is up to date for queries below */
750    DiffviewUpdateLayout(dvPtr);
751
752    opt = Tcl_GetString(objv[2]);
753    if (*opt == 'a' && strcmp(opt,"all") == 0) {
754        sprintf(buf, "0 0 %d %d", dvPtr->maxWidth, dvPtr->maxHeight);
755        Tcl_SetResult(interp, buf, TCL_VOLATILE);
756        return TCL_OK;
757    }
758
759    if (DiffviewGetIndex(interp, dvPtr, objv[2], &line, &diffOpPtr) != TCL_OK) {
760        Tcl_AppendResult(interp, " or all", (char*)NULL);
761        return TCL_ERROR;
762    }
763
764    /* return the bounding box for a line in world coords */
765    if (line >= 0) {
766        y0 = line*dvPtr->lineHeight;
767        y1 = (line+1)*dvPtr->lineHeight;
768        x1 = 0;
769        if (line < dvPtr->worldview.numLines) {
770            bnum = dvPtr->worldview.lines[line].bnum;
771            bline = dvPtr->worldview.lines[line].bline;
772            if (bline >= 0) {
773                textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
774                textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
775                x1 = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
776            }
777        }
778        sprintf(buf, "0 %d %d %d", y0, x1, y1);
779        Tcl_SetResult(interp, buf, TCL_VOLATILE);
780        return TCL_OK;
781    }
782
783    /* return the bounding box for a diff in world coords */
784    if (diffOpPtr) {
785        y0 = diffOpPtr->fromWorld*dvPtr->lineHeight;
786        y1 = (diffOpPtr->toWorld+1)*dvPtr->lineHeight;
787        x1 = 0;
788
789        for (line=diffOpPtr->fromWorld; line <= diffOpPtr->toWorld; line++) {
790            if (line < dvPtr->worldview.numLines) {
791                bnum = dvPtr->worldview.lines[line].bnum;
792                bline = dvPtr->worldview.lines[line].bline;
793                if (bline >= 0) {
794                    textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
795                    textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
796                    wd = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
797                    if (wd > x1) {
798                        x1 = wd;
799                    }
800                }
801            }
802        }
803        sprintf(buf, "0 %d %d %d", y0, x1, y1);
804        Tcl_SetResult(interp, buf, TCL_VOLATILE);
805    }
806    return TCL_OK;
807}
808
809/*
810 * ----------------------------------------------------------------------
811 * DiffviewDiffsSubCmd()
812 *
813 * Handles the "diffs" operation on the widget with the following
814 * syntax:
815 *
816 *   widget diffs ?-std|-debug|-number?
817 *
818 * The default is "diffs -std", which returns the usual output from
819 * the Unix "diff" command.  The -debug option returns a more detailed
820 * output that is useful for testing/debugging.  The -number option
821 * returns the total number of diffs.  This is useful for commands
822 * such as "see #2", where you can bring a particular diff into view.
823 * ----------------------------------------------------------------------
824 */
825static int
826DiffviewDiffsSubCmd(interp, dvPtr, objc, objv)
827    Tcl_Interp *interp;       /* interp handling this command */
828    Diffview *dvPtr;          /* widget data */
829    int objc;                 /* number of command arguments */
830    Tcl_Obj *CONST objv[];    /* command arguments */
831{
832    Tcl_Obj *resultPtr;
833    char range1[128], range2[128];
834    int i, n;
835
836    static CONST char *options[] = {
837        "-std",         "-debug",       "-number",      (char*)NULL
838    };
839    enum options {
840        DIFFS_STD,      DIFFS_DEBUG,    DIFFS_NUMBER
841    };
842    int op = DIFFS_STD;
843
844
845    if (objc > 3) {
846        Tcl_WrongNumArgs(interp, 2, objv, "?-std? ?-debug? ?-number?");
847        return TCL_ERROR;
848    }
849
850    /* decode the option that controls the return result */
851    if (objc > 2) {
852        if (Tcl_GetIndexFromObj(interp, objv[2], options, "option", 0,
853                &op) != TCL_OK) {
854            return TCL_ERROR;
855        }
856    }
857
858    /* make sure that our layout info is up to date for queries below */
859    DiffviewUpdateLayout(dvPtr);
860
861    switch ((enum options)op) {
862        case DIFFS_STD: {
863            DiffviewDiffOp curr, *currOpPtr;
864            DiffviewLines *lines1, *lines2;
865
866            if (dvPtr->diffsPtr) {
867                resultPtr = Tcl_GetObjResult(interp);
868                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
869                    currOpPtr = &dvPtr->diffsPtr->ops[i];
870
871                    /* if we're diff'ing the other way, reverse the diff */
872                    if (dvPtr->diffdir == DIFF_1TO2) {
873                        memcpy((VOID*)&curr, (VOID*)currOpPtr,
874                            sizeof(DiffviewDiffOp));
875                        lines1 = dvPtr->buffer[0].lineLimits;
876                        lines2 = dvPtr->buffer[1].lineLimits;
877                    } else {
878                        switch (currOpPtr->op) {
879                            case 'a': curr.op = 'd'; break;
880                            case 'd': curr.op = 'a'; break;
881                            default:  curr.op = currOpPtr->op; break;
882                        }
883                        curr.fromIndex1 = currOpPtr->fromIndex2;
884                        curr.toIndex1   = currOpPtr->toIndex2;
885                        curr.fromIndex2 = currOpPtr->fromIndex1;
886                        curr.toIndex2   = currOpPtr->toIndex1;
887                        lines1 = dvPtr->buffer[1].lineLimits;
888                        lines2 = dvPtr->buffer[0].lineLimits;
889                    }
890
891                    /* append the diff info onto the output */
892                    if (curr.fromIndex1 == curr.toIndex1) {
893                        if (curr.op == 'a') {
894                            /* for append, use first index at raw value */
895                            sprintf(range1, "%d", curr.fromIndex1);
896                        } else {
897                            sprintf(range1, "%d", curr.fromIndex1+1);
898                        }
899                    } else {
900                        sprintf(range1, "%d,%d", curr.fromIndex1+1,
901                            curr.toIndex1+1);
902                    }
903
904                    if (curr.fromIndex2 == curr.toIndex2) {
905                        if (curr.op == 'd') {
906                            /* for delete, use second index at raw value */
907                            sprintf(range2, "%d", curr.fromIndex2);
908                        } else {
909                            sprintf(range2, "%d", curr.fromIndex2+1);
910                        }
911                    } else {
912                        sprintf(range2, "%d,%d", curr.fromIndex2+1,
913                            curr.toIndex2+1);
914                    }
915
916                    switch (curr.op) {
917                        case 'a': {
918                            Tcl_AppendToObj(resultPtr, range1, -1);
919                            Tcl_AppendToObj(resultPtr, "a", 1);
920                            Tcl_AppendToObj(resultPtr, range2, -1);
921                            Tcl_AppendToObj(resultPtr, "\n", 1);
922                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
923                                Tcl_AppendToObj(resultPtr, "> ", 2);
924                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
925                                    lines2->lenPtr[n]);
926                                Tcl_AppendToObj(resultPtr, "\n", 1);
927                            }
928                            break;
929                        }
930                        case 'd': {
931                            Tcl_AppendToObj(resultPtr, range1, -1);
932                            Tcl_AppendToObj(resultPtr, "d", 1);
933                            Tcl_AppendToObj(resultPtr, range2, -1);
934                            Tcl_AppendToObj(resultPtr, "\n", 1);
935                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
936                                Tcl_AppendToObj(resultPtr, "< ", 2);
937                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
938                                    lines1->lenPtr[n]);
939                                Tcl_AppendToObj(resultPtr, "\n", 1);
940                            }
941                            break;
942                        }
943                        case 'c': {
944                            Tcl_AppendToObj(resultPtr, range1, -1);
945                            Tcl_AppendToObj(resultPtr, "c", 1);
946                            Tcl_AppendToObj(resultPtr, range2, -1);
947                            Tcl_AppendToObj(resultPtr, "\n", 1);
948                            for (n=curr.fromIndex1; n <= curr.toIndex1; n++) {
949                                Tcl_AppendToObj(resultPtr, "< ", 2);
950                                Tcl_AppendToObj(resultPtr, lines1->startPtr[n],
951                                    lines1->lenPtr[n]);
952                                Tcl_AppendToObj(resultPtr, "\n", 1);
953                            }
954                            Tcl_AppendToObj(resultPtr, "---\n", 4);
955                            for (n=curr.fromIndex2; n <= curr.toIndex2; n++) {
956                                Tcl_AppendToObj(resultPtr, "> ", 2);
957                                Tcl_AppendToObj(resultPtr, lines2->startPtr[n],
958                                    lines2->lenPtr[n]);
959                                Tcl_AppendToObj(resultPtr, "\n", 1);
960                            }
961                            break;
962                        }
963                    }
964                }
965            }
966            break;
967        }
968        case DIFFS_DEBUG: {
969            DiffviewDiffOp *c;
970            char elem[256];
971
972            if (dvPtr->diffsPtr) {
973                for (i=0; i < dvPtr->diffsPtr->numDiffs; i++) {
974                    c = &dvPtr->diffsPtr->ops[i];
975
976                    /* append the diff info onto the output */
977                    if (c->fromIndex1 == c->toIndex1) {
978                        sprintf(range1, "%d", c->fromIndex1);
979                    } else {
980                        sprintf(range1, "%d,%d", c->fromIndex1, c->toIndex1);
981                    }
982
983                    if (c->fromIndex2 == c->toIndex2) {
984                        sprintf(range2, "%d", c->fromIndex2);
985                    } else {
986                        sprintf(range2, "%d,%d", c->fromIndex2, c->toIndex2);
987                    }
988
989                    sprintf(elem, "%c %s %s", c->op, range1, range2);
990                    Tcl_AppendElement(interp, elem);
991                }
992            }
993            break;
994        }
995        case DIFFS_NUMBER: {
996            int num = 0;
997            if (dvPtr->diffsPtr) {
998                num = dvPtr->diffsPtr->numDiffs;
999            }
1000            resultPtr = Tcl_NewIntObj(num);
1001            Tcl_SetObjResult(interp, resultPtr);
1002            break;
1003        }
1004    }
1005    return TCL_OK;
1006}
1007
1008/*
1009 * ----------------------------------------------------------------------
1010 * DiffviewTextSubCmd()
1011 *
1012 * Handles the "text" operation on the widget with the following
1013 * syntax:
1014 *
1015 *   widget text 1 ?string?
1016 *   widget text 2 ?string?
1017 *
1018 * Used to get/set the text being diff'd and displayed in the widget.
1019 * ----------------------------------------------------------------------
1020 */
1021static int
1022DiffviewTextSubCmd(interp, dvPtr, objc, objv)
1023    Tcl_Interp *interp;       /* interp handling this command */
1024    Diffview *dvPtr;          /* widget data */
1025    int objc;                 /* number of command arguments */
1026    Tcl_Obj *CONST objv[];    /* command arguments */
1027{
1028    int bufferNum;
1029    DiffviewBuffer *slotPtr;
1030
1031    if (objc < 2 || objc > 4) {
1032        Tcl_WrongNumArgs(interp, 2, objv, "bufferNum ?string?");
1033        return TCL_ERROR;
1034    }
1035
1036    /* decode the buffer number -- right now only buffers 1 & 2 */
1037    if (Tcl_GetIntFromObj(interp, objv[2], &bufferNum) != TCL_OK) {
1038        return TCL_ERROR;
1039    }
1040    if (bufferNum < 1 || bufferNum > 2) {
1041        Tcl_AppendResult(interp, "bad buffer number \"",
1042            Tcl_GetString(objv[2]), "\": should be 1 or 2",
1043            (char*)NULL);
1044        return TCL_ERROR;
1045    }
1046    slotPtr = &dvPtr->buffer[bufferNum-1];
1047
1048    if (objc == 3) {
1049        /* return current contents of the buffer */
1050        if (slotPtr->textObj != NULL) {
1051            Tcl_SetObjResult(interp, slotPtr->textObj);
1052        } else {
1053            Tcl_ResetResult(interp);
1054        }
1055        return TCL_OK;
1056    }
1057
1058    /* set and return the new contents of the buffer */
1059    if (slotPtr->textObj != NULL) {
1060        Tcl_DecrRefCount(slotPtr->textObj);
1061        DiffviewLinesFree(slotPtr->lineLimits);
1062        slotPtr->lineLimits = NULL;
1063    }
1064    slotPtr->textObj = objv[3];
1065    Tcl_IncrRefCount(slotPtr->textObj);
1066
1067    EventuallyRedraw(dvPtr);
1068
1069    Tcl_SetObjResult(interp, slotPtr->textObj);
1070    return TCL_OK;
1071}
1072
1073/*
1074 * ----------------------------------------------------------------------
1075 * DiffviewXviewSubCmd()
1076 *
1077 * Handles the "xview" operation on the widget.  Adjusts the x-axis view
1078 * according to some request, which usually comes from a scrollbar.
1079 * Supports the standard Tk operations for "xview".
1080 * ----------------------------------------------------------------------
1081 */
1082static int
1083DiffviewXviewSubCmd(interp, dvPtr, objc, objv)
1084    Tcl_Interp *interp;       /* interp handling this command */
1085    Diffview *dvPtr;          /* widget data */
1086    int objc;                 /* number of command arguments */
1087    Tcl_Obj *CONST objv[];    /* command arguments */
1088{
1089
1090    int offset = 0;
1091    int index, count, type, viewWidth, windowUnits, maxOffset;
1092    double fraction, fraction2;
1093   
1094    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
1095
1096    if (objc == 2) {
1097        /*
1098         * COMMAND: widget xview
1099         * return current limits as fractions 0-1
1100         */
1101        if (dvPtr->maxWidth == 0) {
1102            Tcl_SetResult(interp, "0 1", TCL_STATIC);
1103        } else {
1104            char buf[TCL_DOUBLE_SPACE*2+2];
1105
1106            fraction = dvPtr->xOffset/((double)dvPtr->maxWidth);
1107            fraction2 = (dvPtr->xOffset + viewWidth)
1108                /((double)dvPtr->maxWidth);
1109            if (fraction2 > 1.0) {
1110                fraction2 = 1.0;
1111            }
1112            sprintf(buf, "%g %g", fraction, fraction2);
1113            Tcl_SetResult(interp, buf, TCL_VOLATILE);
1114        }
1115    } else {
1116        if (objc == 3) {
1117            /*
1118             * COMMAND: widget xview index
1119             * set left edge to character at index
1120             */
1121            if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
1122                return TCL_ERROR;
1123            }
1124            offset = index*dvPtr->xScrollUnit;
1125        } else {
1126            /*
1127             * COMMAND: widget xview moveto fraction
1128             * COMMAND: widget xview scroll number what
1129             * handles more complex scrolling movements
1130             */
1131            type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
1132            switch (type) {
1133                case TK_SCROLL_ERROR:
1134                    return TCL_ERROR;
1135                case TK_SCROLL_MOVETO:
1136                    offset = (int)(fraction*dvPtr->maxWidth + 0.5);
1137                    break;
1138                case TK_SCROLL_PAGES:
1139                    windowUnits = viewWidth/dvPtr->xScrollUnit;
1140                    if (windowUnits > 2) {
1141                        offset = dvPtr->xOffset
1142                            + count*dvPtr->xScrollUnit*(windowUnits-2);
1143                    } else {
1144                        offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
1145                    }
1146                    break;
1147                case TK_SCROLL_UNITS:
1148                    offset = dvPtr->xOffset + count*dvPtr->xScrollUnit;
1149                    break;
1150            }
1151        }
1152        maxOffset = dvPtr->maxWidth
1153            - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset)
1154            + (dvPtr->xScrollUnit - 1);
1155        if (offset > maxOffset) { offset = maxOffset; }
1156        if (offset < 0) { offset = 0; }
1157
1158        /* nudge back to an even boundary */
1159        offset -= offset % dvPtr->xScrollUnit;
1160
1161        if (offset != dvPtr->xOffset) {
1162            dvPtr->xOffset = offset;
1163            dvPtr->flags |= UPDATE_H_SCROLLBAR;
1164            EventuallyRedraw(dvPtr);
1165        }
1166    }
1167    return TCL_OK;
1168}
1169
1170/*
1171 * ----------------------------------------------------------------------
1172 * DiffviewYviewSubCmd()
1173 *
1174 * Handles the "yview" operation on the widget.  Adjusts the y-axis view
1175 * according to some request, which usually comes from a scrollbar.
1176 * Supports the standard Tk operations for "yview".
1177 * ----------------------------------------------------------------------
1178 */
1179static int
1180DiffviewYviewSubCmd(interp, dvPtr, objc, objv)
1181    Tcl_Interp *interp;       /* interp handling this command */
1182    Diffview *dvPtr;          /* widget data */
1183    int objc;                 /* number of command arguments */
1184    Tcl_Obj *CONST objv[];    /* command arguments */
1185{
1186    int index, yLines, count, type;
1187    double fraction, fraction2;
1188
1189    /* make sure that our layout info is up to date */
1190    DiffviewUpdateLayout(dvPtr);
1191
1192    if (objc == 2) {
1193        /*
1194         * COMMAND: widget yview
1195         * return current limits as fractions 0-1
1196         */
1197        if (dvPtr->maxHeight == 0) {
1198            Tcl_SetResult(interp, "0 1", TCL_STATIC);
1199        } else {
1200            char buf[TCL_DOUBLE_SPACE * 2+2];
1201
1202            yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1203            fraction = dvPtr->topLine/((double)yLines);
1204            fraction2 = (dvPtr->topLine+dvPtr->fullLines)/((double)yLines);
1205            if (fraction2 > 1.0) {
1206                fraction2 = 1.0;
1207            }
1208            sprintf(buf, "%g %g", fraction, fraction2);
1209            Tcl_SetResult(interp, buf, TCL_VOLATILE);
1210        }
1211    } else if (objc == 3) {
1212        /*
1213         * COMMAND: widget yview index
1214         * set top edge to line at index
1215         */
1216        if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
1217            return TCL_ERROR;
1218        }
1219        ChangeDiffviewView(dvPtr, index);
1220    } else {
1221        /*
1222         * COMMAND: widget yview moveto fraction
1223         * COMMAND: widget yview scroll number what
1224         * handles more complex scrolling movements
1225         */
1226        type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
1227        switch (type) {
1228            case TK_SCROLL_ERROR:
1229                return TCL_ERROR;
1230            case TK_SCROLL_MOVETO:
1231                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1232                index = (int)(yLines*fraction + 0.5);
1233                break;
1234            case TK_SCROLL_PAGES:
1235                yLines = dvPtr->maxHeight/dvPtr->lineHeight;
1236                if (dvPtr->fullLines > 2) {
1237                    index = dvPtr->topLine + count*(dvPtr->fullLines-2);
1238                } else {
1239                    index = dvPtr->topLine + count;
1240                }
1241                break;
1242            case TK_SCROLL_UNITS:
1243                index = dvPtr->topLine + count;
1244                break;
1245        }
1246        ChangeDiffviewView(dvPtr, index);
1247    }
1248    return TCL_OK;
1249}
1250
1251/*
1252 * ----------------------------------------------------------------------
1253 * DiffviewGetIndex()
1254 *
1255 * Used by many of the widget operations to parse an index into the
1256 * widget view.  Handles the following syntax:
1257 *   #NN ...... particular diff number, starting from 1
1258 *   NN ....... particular line number in world view, starting from 0
1259 *
1260 * Returns TCL_OK if successful, and TCL_ERROR (along with an error
1261 * message) if anything goes wrong.
1262 * ----------------------------------------------------------------------
1263 */
1264static int
1265DiffviewGetIndex(interp, dvPtr, objPtr, linePtr, diffOpPtrPtr)
1266    Tcl_Interp *interp;             /* interp handling this command */
1267    Diffview *dvPtr;                /* widget data */
1268    Tcl_Obj *objPtr;                /* argument being parsed */
1269    int *linePtr;                   /* returns: line number or -1 */
1270    DiffviewDiffOp **diffOpPtrPtr;  /* returns: specific diff or NULL */
1271{
1272    int result = TCL_OK;
1273    char *opt = Tcl_GetString(objPtr);
1274    char *tail;
1275    int dnum;
1276
1277    /* clear the return values */
1278    if (linePtr) *linePtr = -1;
1279    if (diffOpPtrPtr) *diffOpPtrPtr = NULL;
1280
1281    if (*opt == '#') {
1282        dnum = strtol(opt+1, &tail, 10);
1283        if (*tail != '\0' || dnum <= 0) {
1284           result = TCL_ERROR;
1285        }
1286        else if (dvPtr->diffsPtr == NULL
1287                   || dnum > dvPtr->diffsPtr->numDiffs) {
1288           Tcl_AppendResult(interp, "diff \"", opt, "\" doesn't exist",
1289               (char*)NULL);
1290           return TCL_ERROR;
1291        }
1292        *diffOpPtrPtr = &dvPtr->diffsPtr->ops[dnum-1];
1293    } else {
1294        result = Tcl_GetIntFromObj(interp, objPtr, linePtr);
1295    }
1296
1297    if (result != TCL_OK) {
1298        Tcl_ResetResult(interp);
1299        Tcl_AppendResult(interp, "bad index \"", opt, "\": should be "
1300            "\"#n\" for diff, or an integer value for line number",
1301            (char*)NULL);
1302    }
1303    return result;
1304}
1305
1306/*
1307 * ----------------------------------------------------------------------
1308 * DestroyDiffview()
1309 *
1310 * Used by Tcl_Release/Tcl_EventuallyFree to clean up the data associated
1311 * with a widget when it is no longer being used.  This happens at the
1312 * end of a widget destruction sequence.
1313 * ----------------------------------------------------------------------
1314 */
1315static void
1316DestroyDiffview(memPtr)
1317    char *memPtr;             /* widget data */
1318{
1319    Diffview *dvPtr = (Diffview*)memPtr;
1320    int bnum;
1321
1322    /*
1323     * Clean up text associated with the diffs.
1324     */
1325    for (bnum=0; bnum < 2; bnum++) {
1326        if (dvPtr->buffer[bnum].textObj != NULL) {
1327            Tcl_DecrRefCount(dvPtr->buffer[bnum].textObj);
1328        }
1329        if (dvPtr->buffer[bnum].lineLimits != NULL) {
1330            DiffviewLinesFree(dvPtr->buffer[bnum].lineLimits);
1331        }
1332    }
1333
1334    if (dvPtr->diffsPtr) {
1335        DiffviewDiffsFree(dvPtr->diffsPtr);
1336    }
1337    DiffviewLayoutFree(&dvPtr->worldview);
1338
1339    /*
1340     * Free up GCs and configuration options.
1341     */
1342    if (dvPtr->normGC != None) {
1343        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
1344    }
1345    if (dvPtr->addFgGC != None) {
1346        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
1347    }
1348    if (dvPtr->delFgGC != None) {
1349        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
1350    }
1351    if (dvPtr->chgFgGC != None) {
1352        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
1353    }
1354
1355    Tk_FreeConfigOptions((char*)dvPtr, dvPtr->optionTable, dvPtr->tkwin);
1356    Tcl_Release((ClientData) dvPtr->tkwin);
1357    dvPtr->tkwin = NULL;
1358    ckfree((char*)dvPtr);
1359}
1360
1361/*
1362 * ----------------------------------------------------------------------
1363 * ConfigureDiffview()
1364 *
1365 * Takes a list of configuration options in objc/objv format and applies
1366 * the settings to the widget.  This is the underlying "configure"
1367 * operation for the widget.
1368 * ----------------------------------------------------------------------
1369 */
1370static int
1371ConfigureDiffview(interp, dvPtr, objc, objv, flags)
1372    Tcl_Interp *interp;       /* interp handling this command */
1373    Diffview *dvPtr;          /* widget data */
1374    int objc;                 /* number of configuration option words */
1375    Tcl_Obj *CONST objv[];    /* configuration option words */
1376    int flags;                /* flags for Tk_ConfigureWidget */
1377{
1378    Tcl_Obj *errorResult = NULL;
1379    int status;
1380    Tk_SavedOptions savedOptions;
1381
1382    status = Tk_SetOptions(interp, (char*)dvPtr, dvPtr->optionTable,
1383        objc, objv, dvPtr->tkwin, &savedOptions, (int*)NULL);
1384
1385    if (status != TCL_OK) {
1386        errorResult = Tcl_GetObjResult(interp);
1387        Tcl_IncrRefCount(errorResult);
1388        Tk_RestoreSavedOptions(&savedOptions);
1389
1390        Tcl_SetObjResult(interp, errorResult);
1391        Tcl_DecrRefCount(errorResult);
1392        return TCL_ERROR;
1393    }
1394
1395    /*
1396     * Fix up the widget to react to any new configuration values.
1397     */
1398    Tk_SetBackgroundFromBorder(dvPtr->tkwin, dvPtr->normalBorder);
1399
1400    if (dvPtr->highlightWidth < 0) {
1401        dvPtr->highlightWidth = 0;
1402    }
1403    dvPtr->inset = dvPtr->highlightWidth + dvPtr->borderWidth;
1404
1405    if (dvPtr->tklastfont != dvPtr->tkfont) {
1406        /* the font changed, so re-measure everything */
1407        dvPtr->flags |= FONT_CHANGED;
1408        dvPtr->tklastfont = dvPtr->tkfont;
1409    }
1410
1411    Tk_FreeSavedOptions(&savedOptions);
1412
1413    DiffviewWorldChanged((ClientData)dvPtr);
1414    return TCL_OK;
1415}
1416
1417/*
1418 * ----------------------------------------------------------------------
1419 * DiffviewWorldChanged()
1420 *
1421 * Called by Tk when the world has changed in some way that causes
1422 * all GCs and other things to be recomputed.  Reinitializes the
1423 * widget and gets it ready to draw.
1424 * ----------------------------------------------------------------------
1425 */
1426static void
1427DiffviewWorldChanged(cdata)
1428    ClientData cdata;        /* widget data */
1429{
1430    Diffview *dvPtr = (Diffview*)cdata;
1431    GC gc; XGCValues gcValues;
1432    unsigned long mask;
1433
1434    /* GC for normal widget text */
1435    gcValues.foreground = dvPtr->fgColorPtr->pixel;
1436    gcValues.graphics_exposures = False;
1437    gcValues.font = Tk_FontId(dvPtr->tkfont);
1438    mask = GCForeground | GCFont | GCGraphicsExposures;
1439
1440    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1441    if (dvPtr->normGC != None) {
1442        Tk_FreeGC(dvPtr->display, dvPtr->normGC);
1443    }
1444    dvPtr->normGC = gc;
1445
1446    /* GC for added diff text */
1447    gcValues.foreground = dvPtr->addFgColorPtr->pixel;
1448    gcValues.graphics_exposures = False;
1449    gcValues.font = Tk_FontId(dvPtr->tkfont);
1450    mask = GCForeground | GCFont | GCGraphicsExposures;
1451
1452    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1453    if (dvPtr->addFgGC != None) {
1454        Tk_FreeGC(dvPtr->display, dvPtr->addFgGC);
1455    }
1456    dvPtr->addFgGC = gc;
1457
1458    /* GC for deleted diff text */
1459    gcValues.foreground = dvPtr->delFgColorPtr->pixel;
1460    gcValues.graphics_exposures = False;
1461    gcValues.font = Tk_FontId(dvPtr->tkfont);
1462    mask = GCForeground | GCFont | GCGraphicsExposures;
1463
1464    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1465    if (dvPtr->delFgGC != None) {
1466        Tk_FreeGC(dvPtr->display, dvPtr->delFgGC);
1467    }
1468    dvPtr->delFgGC = gc;
1469
1470    /* GC for changed diff text */
1471    gcValues.foreground = dvPtr->chgFgColorPtr->pixel;
1472    gcValues.graphics_exposures = False;
1473    gcValues.font = Tk_FontId(dvPtr->tkfont);
1474    mask = GCForeground | GCFont | GCGraphicsExposures;
1475
1476    gc = Tk_GetGC(dvPtr->tkwin, mask, &gcValues);
1477    if (dvPtr->chgFgGC != None) {
1478        Tk_FreeGC(dvPtr->display, dvPtr->chgFgGC);
1479    }
1480    dvPtr->chgFgGC = gc;
1481
1482    /* this call comes when the font changes -- fix the layout */
1483    dvPtr->flags |= FONT_CHANGED;
1484
1485    /* get ready to redraw */
1486    DiffviewComputeGeometry(dvPtr);
1487    dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
1488    EventuallyRedraw(dvPtr);
1489}
1490
1491/*
1492 * ----------------------------------------------------------------------
1493 * EventuallyRedraw()
1494 *
1495 * Arranges for the widget to redraw itself at the next idle point.
1496 * ----------------------------------------------------------------------
1497 */
1498static void
1499EventuallyRedraw(dvPtr)
1500    Diffview *dvPtr;          /* widget data */
1501{
1502    if ((dvPtr->flags & REDRAW_PENDING) || (dvPtr->flags & WIDGET_DELETED)
1503            || !Tk_IsMapped(dvPtr->tkwin)) {
1504        return;  /* no need to redraw */
1505    }
1506
1507    dvPtr->flags |= REDRAW_PENDING;
1508    Tcl_DoWhenIdle(DisplayDiffview, (ClientData)dvPtr);
1509}
1510
1511/*
1512 * ----------------------------------------------------------------------
1513 * DisplayDiffview()
1514 *
1515 * Redraws the widget based on the current view and all data.
1516 * ----------------------------------------------------------------------
1517 */
1518static void
1519DisplayDiffview(cdata)
1520    ClientData cdata;        /* widget data */
1521{
1522    Diffview *dvPtr = (Diffview*)cdata;
1523    Tk_Window tkwin = dvPtr->tkwin;
1524
1525    int i, bnum, bline, x, xw, y, ymid, width;
1526    char *textPtr; int textLen;
1527    Pixmap pixmap;
1528    GC bg, fg;
1529
1530    /* handling redraw now -- no longer pending */
1531    dvPtr->flags &= ~REDRAW_PENDING;
1532
1533    if (dvPtr->flags & WIDGET_DELETED) {
1534        /* widget is being torn down -- bail out! */
1535        return;
1536    }
1537
1538    /* handle any pending layout changes */
1539    DiffviewUpdateLayout(dvPtr);
1540
1541    /*
1542     * Hang onto the widget data until we're done updating scrollbars.
1543     * The -xscrollcommand and -yscrollcommand options may take us into
1544     * code that deletes the widget.
1545     */
1546    Tcl_Preserve((ClientData)dvPtr);
1547
1548    if (dvPtr->flags & UPDATE_V_SCROLLBAR) {
1549        DiffviewUpdateVScrollbar(dvPtr);
1550        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
1551            Tcl_Release((ClientData)dvPtr);
1552            return;
1553        }
1554    }
1555    if (dvPtr->flags & UPDATE_H_SCROLLBAR) {
1556        DiffviewUpdateHScrollbar(dvPtr);
1557        if ((dvPtr->flags & WIDGET_DELETED) || !Tk_IsMapped(tkwin)) {
1558            Tcl_Release((ClientData)dvPtr);
1559            return;
1560        }
1561    }
1562    dvPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
1563    Tcl_Release((ClientData)dvPtr);
1564
1565#ifndef TK_NO_DOUBLE_BUFFERING
1566    /*
1567     * Best solution is to draw everything into a temporary pixmap
1568     * and copy that to the screen in one shot.  That avoids flashing.
1569     */
1570    pixmap = Tk_GetPixmap(dvPtr->display, Tk_WindowId(tkwin),
1571            Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
1572#else
1573    pixmap = Tk_WindowId(tkwin);
1574#endif
1575
1576    Tk_Fill3DRectangle(tkwin, pixmap, dvPtr->normalBorder, 0, 0,
1577            Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
1578
1579    /*
1580     * Find the top line in the view in each buffer
1581     */
1582    y = dvPtr->inset + dvPtr->topLine*dvPtr->lineHeight + dvPtr->lineAscent
1583          - dvPtr->yOffset;
1584    x = dvPtr->inset - dvPtr->xOffset;
1585
1586    if (Tk_Width(tkwin) > dvPtr->maxWidth) {
1587        width = Tk_Width(tkwin)+10;
1588    } else {
1589        width = dvPtr->maxWidth+10;
1590    }
1591
1592    for (i=dvPtr->topLine;
1593         i <= dvPtr->btmLine && i < dvPtr->worldview.numLines;
1594         i++) {
1595
1596        /* draw any diff rectangle for this line */
1597        fg = dvPtr->normGC;
1598        bg = None;
1599
1600        switch (dvPtr->worldview.lines[i].style) {
1601            case 'a': {
1602                fg = dvPtr->addFgGC;
1603                bg = Tk_GCForColor(dvPtr->addBgColorPtr, pixmap);
1604                break;
1605            }
1606            case 'd': {
1607                fg = dvPtr->delFgGC;
1608                bg = Tk_GCForColor(dvPtr->delBgColorPtr, pixmap);
1609                break;
1610            }
1611            case 'c': {
1612                fg = dvPtr->chgFgGC;
1613                bg = Tk_GCForColor(dvPtr->chgBgColorPtr, pixmap);
1614                break;
1615            }
1616        }
1617
1618        if (bg != None) {
1619            XFillRectangle(Tk_Display(tkwin), pixmap, bg,
1620                x, y - dvPtr->lineAscent,
1621                (unsigned int)width, (unsigned int)dvPtr->lineHeight);
1622        }
1623
1624        bnum = dvPtr->worldview.lines[i].bnum;
1625        bline = dvPtr->worldview.lines[i].bline;
1626
1627        /* a negative number means "leave the line blank" */
1628        if (bline >= 0) {
1629            textPtr = dvPtr->buffer[bnum].lineLimits->startPtr[bline];
1630            textLen = dvPtr->buffer[bnum].lineLimits->lenPtr[bline];
1631
1632            /* Draw the actual text of this item */
1633            Tk_DrawChars(dvPtr->display, pixmap, fg, dvPtr->tkfont,
1634                textPtr, textLen, x, y);
1635
1636            /* Draw an overstrike on deleted text */
1637            if (dvPtr->worldview.lines[i].style == 'd' && dvPtr->overStrDel) {
1638                xw = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen) + 5;
1639                ymid = y - dvPtr->lineAscent/2;
1640                XDrawLine(Tk_Display(tkwin), pixmap, fg, 0, ymid, xw, ymid);
1641            }
1642        }
1643
1644        y += dvPtr->lineHeight;
1645    }
1646
1647    /*
1648     * Draw the border on top so that it covers any characters that
1649     * have scrolled off screen.
1650     */
1651    Tk_Draw3DRectangle(tkwin, pixmap, dvPtr->normalBorder,
1652            dvPtr->highlightWidth, dvPtr->highlightWidth,
1653            Tk_Width(tkwin) - 2*dvPtr->highlightWidth,
1654            Tk_Height(tkwin) - 2*dvPtr->highlightWidth,
1655            dvPtr->borderWidth, dvPtr->relief);
1656
1657    if (dvPtr->highlightWidth > 0) {
1658        XColor *color; GC gc;
1659
1660        color = (dvPtr->flags & GOT_FOCUS)
1661            ? dvPtr->highlightColor : dvPtr->highlightBgColor;
1662        gc = Tk_GCForColor(color, pixmap);
1663        Tk_DrawFocusHighlight(dvPtr->tkwin, gc, dvPtr->highlightWidth, pixmap);
1664    }
1665
1666#ifndef TK_NO_DOUBLE_BUFFERING
1667    XCopyArea(dvPtr->display, pixmap, Tk_WindowId(tkwin),
1668            dvPtr->normGC, 0, 0, (unsigned) Tk_Width(tkwin),
1669            (unsigned) Tk_Height(tkwin), 0, 0);
1670    Tk_FreePixmap(dvPtr->display, pixmap);
1671#endif
1672}
1673
1674/*
1675 * ----------------------------------------------------------------------
1676 * DiffviewComputeGeometry()
1677 *
1678 * Recomputes the overall dimensions of the widget when the overall
1679 * size or the borderwidth is changed.  Registers a new size for the
1680 * widget.
1681 * ----------------------------------------------------------------------
1682 */
1683static void
1684DiffviewComputeGeometry(dvPtr)
1685    Diffview *dvPtr;          /* widget data */
1686{
1687    int pixelWidth, pixelHeight;
1688
1689    dvPtr->xScrollUnit = Tk_TextWidth(dvPtr->tkfont, "0", 1);
1690    if (dvPtr->xScrollUnit == 0) {
1691        dvPtr->xScrollUnit = 1;
1692    }
1693
1694    pixelWidth = dvPtr->width + 2*dvPtr->inset;
1695    pixelHeight = dvPtr->height + 2*dvPtr->inset;
1696    Tk_GeometryRequest(dvPtr->tkwin, pixelWidth, pixelHeight);
1697    Tk_SetInternalBorder(dvPtr->tkwin, dvPtr->inset);
1698}
1699
1700/*
1701 * ----------------------------------------------------------------------
1702 * DiffviewEventProc()
1703 *
1704 * Catches important events on the widget and causes it to redraw
1705 * itself and react to focus changes.
1706 * ----------------------------------------------------------------------
1707 */
1708static void
1709DiffviewEventProc(cdata, eventPtr)
1710    ClientData cdata;        /* widget data */
1711    XEvent *eventPtr;        /* event information coming from X11 */
1712{
1713    Diffview *dvPtr = (Diffview*)cdata;
1714   
1715    if (eventPtr->type == Expose) {
1716        EventuallyRedraw(dvPtr);
1717    }
1718    else if (eventPtr->type == DestroyNotify) {
1719        if (!(dvPtr->flags & WIDGET_DELETED)) {
1720            dvPtr->flags |= WIDGET_DELETED;
1721            Tcl_DeleteCommandFromToken(dvPtr->interp, dvPtr->widgetCmd);
1722            if (dvPtr->flags & REDRAW_PENDING) {
1723                Tcl_CancelIdleCall(DisplayDiffview, cdata);
1724            }
1725            Tcl_EventuallyFree(cdata, DestroyDiffview);
1726        }
1727    }
1728    else if (eventPtr->type == ConfigureNotify) {
1729        dvPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
1730        ChangeDiffviewView(dvPtr, dvPtr->topLine);
1731        EventuallyRedraw(dvPtr);
1732    }
1733    else if (eventPtr->type == FocusIn) {
1734        if (eventPtr->xfocus.detail != NotifyInferior) {
1735            dvPtr->flags |= GOT_FOCUS;
1736            EventuallyRedraw(dvPtr);
1737        }
1738    }
1739    else if (eventPtr->type == FocusOut) {
1740        if (eventPtr->xfocus.detail != NotifyInferior) {
1741            dvPtr->flags &= ~GOT_FOCUS;
1742            EventuallyRedraw(dvPtr);
1743        }
1744    }
1745}
1746
1747/*
1748 * ----------------------------------------------------------------------
1749 * DiffviewCmdDeletedProc --
1750 *
1751 * Invoked whenever the command associated with the widget is deleted.
1752 * If the widget is not already being destroyed, this starts the process.
1753 * ----------------------------------------------------------------------
1754 */
1755static void
1756DiffviewCmdDeletedProc(cdata)
1757    ClientData cdata;        /* widget data */
1758{
1759    Diffview *dvPtr = (Diffview*)cdata;
1760
1761    /*
1762     * This gets invoked whenever the command is deleted.  If we haven't
1763     * started destroying the widget yet, then do it now to clean up.
1764     */
1765    if (!(dvPtr->flags & WIDGET_DELETED)) {
1766        Tk_DestroyWindow(dvPtr->tkwin);
1767    }
1768}
1769
1770/*
1771 * ----------------------------------------------------------------------
1772 * ChangeDiffviewView()
1773 *
1774 * Changes the y-view of the widget for scrolling operations.  Sets the
1775 * given line to the top of the view.
1776 * ----------------------------------------------------------------------
1777 */
1778static void
1779ChangeDiffviewView(dvPtr, lnum)
1780    Diffview *dvPtr;          /* widget data */
1781    int lnum;                 /* this line should appear at the top */
1782{
1783    int maxLines, ySize, yLines;
1784
1785    DiffviewUpdateLayout(dvPtr);
1786    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
1787    yLines = ySize/dvPtr->lineHeight;
1788    maxLines = dvPtr->maxHeight/dvPtr->lineHeight;
1789
1790    if (lnum >= (maxLines - yLines)) {
1791        lnum = maxLines - yLines;
1792    }
1793    if (lnum < 0) {
1794        lnum = 0;
1795    }
1796    if (dvPtr->topLine != lnum) {
1797        dvPtr->topLine = lnum;
1798        dvPtr->yOffset = lnum * dvPtr->lineHeight;
1799        EventuallyRedraw(dvPtr);
1800        dvPtr->flags |= UPDATE_V_SCROLLBAR;
1801    }
1802}
1803
1804/*
1805 * ----------------------------------------------------------------------
1806 * DiffviewScanTo()
1807 *
1808 * Implements the "scan dragto" operation on the widget.  Given a
1809 * starting point from the "scan mark" operation, this adjusts the
1810 * view to the given location.
1811 * ----------------------------------------------------------------------
1812 */
1813static void
1814DiffviewScanTo(dvPtr, x, y)
1815    Diffview *dvPtr;          /* widget data */
1816    int x;                    /* x-coord for drag-to location */
1817    int y;                    /* y-coord for drag-to location */
1818{
1819    int newTopLine, newOffset, maxLine, maxOffset;
1820
1821    /*
1822     * Compute the top line for the display by amplifying the difference
1823     * between the current "drag" point and the original "start" point.
1824     */
1825    maxLine = dvPtr->maxHeight/dvPtr->lineHeight - dvPtr->fullLines;
1826
1827    newTopLine = dvPtr->scanMarkYStart
1828            - (10*(y - dvPtr->scanMarkY))/dvPtr->lineHeight;
1829
1830    if (newTopLine > maxLine) {
1831        newTopLine = dvPtr->scanMarkYStart = maxLine;
1832        dvPtr->scanMarkY = y;
1833    }
1834    else if (newTopLine < 0) {
1835        newTopLine = dvPtr->scanMarkYStart = 0;
1836        dvPtr->scanMarkY = y;
1837    }
1838    ChangeDiffviewView(dvPtr, newTopLine);
1839
1840    /*
1841     * Compute the left edge for the display by amplifying the difference
1842     * between the current "drag" point and the original "start" point.
1843     */
1844    maxOffset = dvPtr->maxWidth - (Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset);
1845    newOffset = dvPtr->scanMarkXStart - (10*(x - dvPtr->scanMarkX));
1846    if (newOffset > maxOffset) {
1847        newOffset = dvPtr->scanMarkXStart = maxOffset;
1848        dvPtr->scanMarkX = x;
1849    } else if (newOffset < 0) {
1850        newOffset = dvPtr->scanMarkXStart = 0;
1851        dvPtr->scanMarkX = x;
1852    }
1853    newOffset -= newOffset % dvPtr->xScrollUnit;
1854
1855    if (newOffset != dvPtr->xOffset) {
1856        dvPtr->xOffset = newOffset;
1857        dvPtr->flags |= UPDATE_H_SCROLLBAR;
1858        EventuallyRedraw(dvPtr);
1859    }
1860}
1861
1862/*
1863 * ----------------------------------------------------------------------
1864 * DiffviewUpdateLayout()
1865 *
1866 * Called whenever the widget is about to access layout information
1867 * to make sure that the latest info is in place and up-to-date.
1868 * Computes the line start/end for each line in each buffer, then
1869 * computes the diffs and figures out how many lines to show.
1870 * ----------------------------------------------------------------------
1871 */
1872static void
1873DiffviewUpdateLayout(dvPtr)
1874    Diffview *dvPtr;          /* widget data */
1875{
1876    int changes = 0;   /* no layout changes yet */
1877    DiffviewBuffer* bufferPtr;
1878    DiffviewDiffOp *currOp;
1879    DiffviewLayoutLine line;
1880    int i, bnum, bline, pixelWidth, maxWidth, ySize;
1881    int numLines1, numLines2, lnum1, lnum2, dnum, lastdnum, pastDiff;
1882    char *textPtr, *textPtr2; int textLen;
1883    Tk_FontMetrics fm;
1884
1885    /* if the font changed, then re-measure everything */
1886    changes = ((dvPtr->flags & FONT_CHANGED) != 0);
1887
1888    /* fill in line limits for any new data */
1889    for (bnum=0; bnum < 2; bnum++) {
1890        bufferPtr = &dvPtr->buffer[bnum];
1891
1892        if (bufferPtr->textObj != NULL && bufferPtr->lineLimits == NULL) {
1893            textPtr = Tcl_GetStringFromObj(bufferPtr->textObj, &textLen);
1894            bufferPtr->lineLimits = DiffviewLinesCreate(textPtr, textLen);
1895            changes = 1;
1896        }
1897    }
1898
1899    if (changes) {
1900        /* recompute the diffs between the buffers */
1901        if (dvPtr->diffsPtr) {
1902            DiffviewDiffsFree(dvPtr->diffsPtr);
1903            dvPtr->diffsPtr = NULL;
1904        }
1905
1906        if (dvPtr->buffer[0].textObj && dvPtr->buffer[1].textObj) {
1907            textPtr = Tcl_GetStringFromObj(dvPtr->buffer[0].textObj, &textLen);
1908            textPtr2 = Tcl_GetStringFromObj(dvPtr->buffer[1].textObj, &textLen);
1909
1910            dvPtr->diffsPtr = DiffviewDiffsCreate(
1911                textPtr, dvPtr->buffer[0].lineLimits,
1912                textPtr2, dvPtr->buffer[1].lineLimits);
1913        }
1914
1915        /*
1916         * Compute the layout of all lines according to the current
1917         * view mode.  Each line in the world view is stored in the
1918         * array dvPtr->worldview.lines.  Each line has a style (color
1919         * for normal, add, delete, etc.) and an indication of which
1920         * buffer it comes from.
1921         */
1922        DiffviewLayoutClear(&dvPtr->worldview);
1923
1924        /*
1925         * March through the lines and figure out the source and style
1926         * of each line based on the diffs:
1927         *   'n' = normal (common) line
1928         *   'a' = draw with the "added" style
1929         *   'd' = draw with the "deleted" style
1930         *   'c' = draw with the "changed" style
1931         */
1932        numLines1 = (dvPtr->buffer[0].lineLimits)
1933                       ? dvPtr->buffer[0].lineLimits->numLines : 0;
1934        numLines2 = (dvPtr->buffer[1].lineLimits)
1935                       ? dvPtr->buffer[1].lineLimits->numLines : 0;
1936
1937        dnum = 0;  /* current difference in diffsPtr */
1938        lnum1 = lnum2 = 0;
1939
1940        while (lnum1 < numLines1 || lnum2 < numLines2) {
1941            /* assume it's a normal-looking line (buffers are the same) */
1942            line.style = 'n';
1943            line.bnum = 0;
1944            line.bline = lnum1;
1945            line.diffNum = -1;
1946
1947            /* is there a diff that contains this line? */
1948            if (dvPtr->diffsPtr && dnum < dvPtr->diffsPtr->numDiffs) {
1949              currOp = &dvPtr->diffsPtr->ops[dnum];
1950
1951              if ( (lnum1 >= currOp->fromIndex1
1952                     && lnum1 <= currOp->toIndex1)
1953                || (lnum2 >= currOp->fromIndex2
1954                     && lnum2 <= currOp->toIndex2) ) {
1955
1956                line.diffNum = dnum;  /* line is part of this diff */
1957
1958                switch (currOp->op) {
1959                    case 'c': {
1960                        if (dvPtr->layout == LAYOUT_INLINE) {
1961                            if (dvPtr->diffdir == DIFF_1TO2) {
1962                                /* show the buffer #1 lines first, then #2 */
1963                                if (lnum1 <= currOp->toIndex1) {
1964                                    line.style = 'd';
1965                                    line.bnum = 0;
1966                                    line.bline = lnum1++;
1967                                } else {
1968                                    line.style = 'a';
1969                                    line.bnum = 1;
1970                                    line.bline = lnum2++;
1971                                }
1972                            } else {
1973                                /* reverse -- show #2 lines first, then #1 */
1974                                if (lnum2 <= currOp->toIndex2) {
1975                                    line.style = 'd';
1976                                    line.bnum = 1;
1977                                    line.bline = lnum2++;
1978                                } else {
1979                                    line.style = 'a';
1980                                    line.bnum = 0;
1981                                    line.bline = lnum1++;
1982                                }
1983                            }
1984                        } else {
1985                            if (dvPtr->diffdir == DIFF_1TO2) {
1986                                /* show final lines in buf #2 as "changed" */
1987                                line.style = 'c';
1988                                line.bnum = 1;
1989                                line.bline = lnum2++;
1990                                lnum1 = currOp->toIndex1+1;
1991                            } else {
1992                                /* show final lines in buf #1 as "changed" */
1993                                line.style = 'c';
1994                                line.bnum = 0;
1995                                line.bline = lnum1++;
1996                                lnum2 = currOp->toIndex2+1;
1997                            }
1998                        }
1999                        break;
2000                    }
2001                    case 'a': {
2002                        if (dvPtr->diffdir == DIFF_1TO2) {
2003                            /* show lines in buffer #2 as 'added' */
2004                            line.style = 'a';
2005                            line.bnum = 1;
2006                            line.bline = lnum2++;
2007                            lnum1 = currOp->toIndex1;
2008                        } else {
2009                            /* reverse diff -- like we're deleting lines */
2010                            if (dvPtr->layout == LAYOUT_INLINE) {
2011                                line.style = 'd';
2012                                line.bnum = 1;
2013                                line.bline = lnum2++;
2014                                lnum1 = currOp->toIndex1;
2015                            } else {
2016                                /* show lines from buffer #2 as empty */
2017                                line.style = 'd';
2018                                line.bnum = 0;
2019                                line.bline = -1;
2020                                lnum2++;
2021                                lnum1 = currOp->toIndex1;
2022                            }
2023                        }
2024                        break;
2025                    }
2026                    case 'd': {
2027                        if (dvPtr->diffdir == DIFF_1TO2) {
2028                            if (dvPtr->layout == LAYOUT_INLINE) {
2029                                line.style = 'd';
2030                                line.bnum = 0;
2031                                line.bline = lnum1++;
2032                                lnum2 = currOp->toIndex2;
2033                            } else {
2034                                /* show lines from buffer #2 as empty */
2035                                line.style = 'd';
2036                                line.bnum = 0;
2037                                line.bline = -1;
2038                                lnum1++;
2039                                lnum2 = currOp->toIndex2;
2040                            }
2041                        } else {
2042                            /* reverse diff -- like we're adding lines */
2043                            line.style = 'a';
2044                            line.bnum = 0;
2045                            line.bline = lnum1++;
2046                            lnum2 = currOp->toIndex2;
2047                        }
2048                        break;
2049                    }
2050                    default: {
2051                        Tcl_Panic("bad diff type '%c' found in layout",
2052                            currOp->op);
2053                        break;
2054                    }
2055                }
2056              } else {
2057                /* normal line -- keep moving forward */
2058                lnum1++; lnum2++;
2059              }
2060
2061              /* have we reached the end of the diff? then move on */
2062              pastDiff = 0;
2063              switch (currOp->op) {
2064                  case 'c':
2065                      pastDiff = (lnum1 > currOp->toIndex1
2066                               && lnum2 > currOp->toIndex2);
2067                      break;
2068                  case 'a':
2069                      pastDiff = (lnum2 > currOp->toIndex2);
2070                      break;
2071                  case 'd':
2072                      pastDiff = (lnum1 > currOp->toIndex1);
2073                      break;
2074              }
2075              if (pastDiff) {
2076                  dnum++;
2077              }
2078            } else {
2079                /* no more diffs -- keep moving forward */
2080                lnum1++; lnum2++;
2081            }
2082
2083            /* add this new line to the layout */
2084            DiffviewLayoutAdd(&dvPtr->worldview, &line);
2085        }
2086
2087        /*
2088         * Figure out where the diffs are located, and put that info
2089         * back into the diffs.  This makes it easy later to refer to
2090         * diff "#3" and understand what lines we're talking about.
2091         */
2092        lastdnum = -1;
2093        for (i=0; i < dvPtr->worldview.numLines; i++) {
2094            dnum = dvPtr->worldview.lines[i].diffNum;
2095            if (dnum != lastdnum) {
2096                if (lastdnum < 0) {
2097                    /* leading edge -- catch the "from" line */
2098                    lnum1 = i;
2099                } else {
2100                    /* trailing edge -- save diff info */
2101                    dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
2102                    dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
2103                }
2104            }
2105            lastdnum = dnum;
2106        }
2107        if (lastdnum >= 0) {
2108            dvPtr->diffsPtr->ops[lastdnum].fromWorld = lnum1;
2109            dvPtr->diffsPtr->ops[lastdnum].toWorld = i-1;
2110        }
2111
2112        /* compute overall text width for all lines */
2113        maxWidth = 0;
2114        for (i=0; i < dvPtr->worldview.numLines; i++) {
2115            bline = dvPtr->worldview.lines[i].bline;
2116            if (bline >= 0) {
2117                bnum = dvPtr->worldview.lines[i].bnum;
2118                bufferPtr = &dvPtr->buffer[bnum];
2119
2120                if (bufferPtr->lineLimits) {
2121                    textPtr = bufferPtr->lineLimits->startPtr[bline];
2122                    textLen = bufferPtr->lineLimits->lenPtr[bline];
2123                    pixelWidth = Tk_TextWidth(dvPtr->tkfont, textPtr, textLen);
2124
2125                    if (pixelWidth > maxWidth) {
2126                        maxWidth = pixelWidth;
2127                    }
2128                }
2129            }
2130        }
2131        dvPtr->maxWidth = maxWidth;
2132
2133        /* compute overall height of the widget */
2134        Tk_GetFontMetrics(dvPtr->tkfont, &fm);
2135        dvPtr->lineHeight = fm.linespace;
2136        dvPtr->lineAscent = fm.ascent;
2137        dvPtr->maxHeight = dvPtr->worldview.numLines * fm.linespace;
2138
2139        /* figure out the limits of the current view */
2140        ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
2141        dvPtr->topLine = dvPtr->yOffset/fm.linespace;
2142        dvPtr->btmLine = (dvPtr->yOffset + ySize)/fm.linespace + 1;
2143        dvPtr->fullLines = ySize/fm.linespace;
2144
2145        dvPtr->flags &= ~FONT_CHANGED;
2146    }
2147}
2148
2149/*
2150 * ----------------------------------------------------------------------
2151 * DiffviewUpdateVScrollbar()
2152 *
2153 * Used to invoke the -yscrollcommand whenever the y-view has changed.
2154 * This updates the scrollbar so that it shows the proper bubble.
2155 * If there is no -yscrollcommand option, this does nothing.
2156 * ----------------------------------------------------------------------
2157 */
2158static void
2159DiffviewUpdateVScrollbar(dvPtr)
2160    Diffview *dvPtr;          /* widget data */
2161{
2162    char string[TCL_DOUBLE_SPACE*2 + 2];
2163    double first, last;
2164    int result, ySize;
2165
2166    /* make sure the layout info is up to date */
2167    DiffviewUpdateLayout(dvPtr);
2168
2169    /* update the limits of the current view */
2170    ySize = Tk_Height(dvPtr->tkwin) - 2*dvPtr->inset;
2171    if (dvPtr->lineHeight > 0) {
2172        dvPtr->topLine = dvPtr->yOffset/dvPtr->lineHeight;
2173        dvPtr->btmLine = (dvPtr->yOffset + ySize)/dvPtr->lineHeight + 1;
2174        dvPtr->fullLines = ySize/dvPtr->lineHeight;
2175    }
2176
2177    /* if there's no scroll command, then there's nothing left to do */
2178    if (dvPtr->yScrollCmd == NULL) {
2179        return;
2180    }
2181
2182    if (dvPtr->maxHeight == 0) {
2183        first = 0.0;
2184        last = 1.0;
2185    } else {
2186        first = dvPtr->yOffset/((double)dvPtr->maxHeight);
2187        last = (dvPtr->yOffset + ySize)/((double)dvPtr->maxHeight);
2188        if (last > 1.0) {
2189            last = 1.0;
2190        }
2191    }
2192    sprintf(string, " %g %g", first, last);
2193
2194    /* preserve/release the interp, in case it gets destroyed during the call */
2195    Tcl_Preserve((ClientData)dvPtr->interp);
2196
2197    result = Tcl_VarEval(dvPtr->interp, dvPtr->yScrollCmd, string,
2198            (char*)NULL);
2199    if (result != TCL_OK) {
2200        Tcl_AddErrorInfo(dvPtr->interp,
2201                "\n    (vertical scrolling command executed by diffview)");
2202        Tcl_BackgroundError(dvPtr->interp);
2203    }
2204
2205    Tcl_Release((ClientData)dvPtr->interp);
2206}
2207
2208/*
2209 * ----------------------------------------------------------------------
2210 * DiffviewUpdateHScrollbar()
2211 *
2212 * Used to invoke the -xscrollcommand whenever the x-view has changed.
2213 * This updates the scrollbar so that it shows the proper bubble.
2214 * If there is no -xscrollcommand option, this does nothing.
2215 * ----------------------------------------------------------------------
2216 */
2217static void
2218DiffviewUpdateHScrollbar(dvPtr)
2219    Diffview *dvPtr;          /* widget data */
2220{
2221    char string[TCL_DOUBLE_SPACE*2 + 3];
2222    int result, viewWidth;
2223    double first, last;
2224
2225    /* make sure the layout info is up to date */
2226    DiffviewUpdateLayout(dvPtr);
2227
2228    /* if there's no scroll command, then there's nothing left to do */
2229    if (dvPtr->xScrollCmd == NULL) {
2230        return;
2231    }
2232
2233    viewWidth = Tk_Width(dvPtr->tkwin) - 2*dvPtr->inset;
2234    if (dvPtr->maxWidth == 0) {
2235        first = 0;
2236        last = 1.0;
2237    } else {
2238        first = dvPtr->xOffset/((double)dvPtr->maxWidth);
2239        last = (dvPtr->xOffset + viewWidth)/((double) dvPtr->maxWidth);
2240        if (last > 1.0) {
2241            last = 1.0;
2242        }
2243    }
2244    sprintf(string, " %g %g", first, last);
2245
2246    /* preserve/release the interp, in case it gets destroyed during the call */
2247    Tcl_Preserve((ClientData)dvPtr->interp);
2248
2249    result = Tcl_VarEval(dvPtr->interp, dvPtr->xScrollCmd, string,
2250            (char*)NULL);
2251    if (result != TCL_OK) {
2252        Tcl_AddErrorInfo(dvPtr->interp,
2253                "\n    (horizontal scrolling command executed by diffview)");
2254        Tcl_BackgroundError(dvPtr->interp);
2255    }
2256
2257    Tcl_Release((ClientData)dvPtr->interp);
2258}
2259
2260/*
2261 * ----------------------------------------------------------------------
2262 * DiffviewLinesCreate()
2263 *
2264 * Used to breakup a Tcl string object into the indices for the start
2265 * and end of each line.  This is the first step in the computation
2266 * of diffs and the world view of the widget.
2267 *
2268 * Returns a pointer to a description of the line geometry, which should
2269 * be freed by calling DiffviewLinesFree() when it is no longer needed.
2270 * ----------------------------------------------------------------------
2271 */
2272static DiffviewLines*
2273DiffviewLinesCreate(textPtr, textLen)
2274    char *textPtr;                  /* text string being broken up */
2275    int textLen;                    /* length of the string */
2276{
2277    int numLines = 0;
2278    DiffviewLines *lineLimitsPtr;
2279    char **newStarts;
2280    int *newLens;
2281    unsigned int len;
2282
2283    lineLimitsPtr = (DiffviewLines*)ckalloc(sizeof(DiffviewLines));
2284    lineLimitsPtr->maxLines = 0;
2285    lineLimitsPtr->startPtr = NULL;
2286    lineLimitsPtr->lenPtr = NULL;
2287
2288    while (textLen > 0) {
2289        /*
2290         * If we're out of space, double the size and copy existing
2291         * info over.
2292         */
2293        if (numLines >= lineLimitsPtr->maxLines) {
2294            if (lineLimitsPtr->maxLines == 0) {
2295                lineLimitsPtr->maxLines = 100;
2296            } else {
2297                lineLimitsPtr->maxLines *= 2;
2298            }
2299
2300            /* resize the start point array */
2301            newStarts = (char**)ckalloc(lineLimitsPtr->maxLines*sizeof(char*));
2302            if (lineLimitsPtr->startPtr) {
2303                memcpy((VOID*)newStarts, (VOID*)lineLimitsPtr->startPtr,
2304                    numLines*sizeof(char*));
2305                ckfree((char*)lineLimitsPtr->startPtr);
2306            }
2307            lineLimitsPtr->startPtr = newStarts;
2308
2309            /* resize the end point array */
2310            newLens = (int*)ckalloc(lineLimitsPtr->maxLines*sizeof(int));
2311            if (lineLimitsPtr->lenPtr) {
2312                memcpy((VOID*)newLens, (VOID*)lineLimitsPtr->lenPtr,
2313                    numLines*sizeof(int));
2314                ckfree((char*)lineLimitsPtr->lenPtr);
2315            }
2316            lineLimitsPtr->lenPtr = newLens;
2317        }
2318
2319        /* mark the start of this line, then search for the end */
2320        lineLimitsPtr->startPtr[numLines] = textPtr;
2321        lineLimitsPtr->lenPtr[numLines] = 0;
2322
2323        while (*textPtr != '\n' && textLen > 0) {
2324            textPtr++;  textLen--;
2325        }
2326        if (textPtr > lineLimitsPtr->startPtr[numLines]) {
2327            len = textPtr - lineLimitsPtr->startPtr[numLines];
2328            lineLimitsPtr->lenPtr[numLines] = len;
2329        }
2330        numLines++;
2331
2332        /* skip over the newline and start with next line */
2333        if (*textPtr == '\n') {
2334            textPtr++;  textLen--;
2335        }
2336    }
2337    lineLimitsPtr->numLines = numLines;
2338
2339    return lineLimitsPtr;
2340}
2341
2342/*
2343 * ----------------------------------------------------------------------
2344 * DiffviewLinesFree()
2345 *
2346 * Used to free up the data structure created by DiffviewLinesCreate().
2347 * ----------------------------------------------------------------------
2348 */
2349static void
2350DiffviewLinesFree(lineLimitsPtr)
2351    DiffviewLines *lineLimitsPtr;   /* data structure being freed */
2352{
2353    if (lineLimitsPtr) {
2354        if (lineLimitsPtr->startPtr) {
2355            ckfree((char*)lineLimitsPtr->startPtr);
2356        }
2357        if (lineLimitsPtr->lenPtr) {
2358            ckfree((char*)lineLimitsPtr->lenPtr);
2359        }
2360        ckfree((char*)lineLimitsPtr);
2361    }
2362}
2363
2364/*
2365 * ----------------------------------------------------------------------
2366 * DiffviewDiffsCreate()
2367 *
2368 * Considers two strings textPtr1 and textPtr2 (divided into segments
2369 * according to limsPtr1 and limsPtr2), and computes the longest common
2370 * subsequences between the two strings.  This is the first step in
2371 * computing the differences between the two strings.
2372 *
2373 * Returns a data structure that contains a series of "diff" operations.
2374 * This should be freed when it is no longer needed by calling
2375 * DiffviewDiffsFree().
2376 *
2377 *   REFERENCE:
2378 *   J. W. Hunt and M. D. McIlroy, "An algorithm for differential
2379 *   file comparison," Comp. Sci. Tech. Rep. #41, Bell Telephone
2380 *   Laboratories (1976). Available on the Web at the second
2381 *   author's personal site: http://www.cs.dartmouth.edu/~doug/
2382 *
2383 * ----------------------------------------------------------------------
2384 */
2385static DiffviewDiffs*
2386DiffviewDiffsCreate(textPtr1, limsPtr1, textPtr2, limsPtr2)
2387    char *textPtr1;           /* text from buffer #1 */
2388    DiffviewLines *limsPtr1;  /* limits of individual strings in textPtr1 */
2389    char *textPtr2;           /* text from buffer #2 */
2390    DiffviewLines *limsPtr2;  /* limits of individual strings in textPtr2 */
2391{
2392    DiffviewDiffs *diffPtr = NULL;
2393
2394    Tcl_HashTable eqv;
2395    Tcl_HashSearch iter;
2396    Tcl_HashEntry *entryPtr;
2397    Tcl_DString buffer;
2398    DiffviewSubseq *K, *newK, newCandidate;
2399    int Kmax; int Klen;
2400    Tcl_Obj *listPtr, **objv;
2401    int i, j, candidateIdx, subseqLen, longestMatch;
2402    int max, min, mid, midval, sLen, del;
2403    int len, created, o, objc, index1, *lcsIndex1, index2, *lcsIndex2;
2404    char *key;
2405
2406    newCandidate.index1 = -1;           /* Suppress compiler warning. */
2407    newCandidate.index2 = -1;
2408    newCandidate.next = -1;
2409
2410    /*
2411     * Build a set of equivalence classes.  Scan through all of
2412     * buffer #2 and map each string to a list of indices for the
2413     * lines that have that string.
2414     */
2415    Tcl_DStringInit(&buffer);
2416    Tcl_InitHashTable(&eqv, TCL_STRING_KEYS);
2417    for (i=0; i < limsPtr2->numLines; i++) {
2418        len = limsPtr2->lenPtr[i];
2419        Tcl_DStringSetLength(&buffer, len);
2420        key = Tcl_DStringValue(&buffer);
2421        memcpy((VOID*)key, (VOID*)limsPtr2->startPtr[i], len);
2422
2423        entryPtr = Tcl_CreateHashEntry(&eqv, key, &created);
2424        if (created) {
2425            listPtr = Tcl_NewListObj(0, (Tcl_Obj**)NULL);
2426            Tcl_IncrRefCount(listPtr);
2427            Tcl_SetHashValue(entryPtr, (ClientData)listPtr);
2428        }
2429
2430        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2431        Tcl_ListObjAppendElement((Tcl_Interp*)NULL, listPtr, Tcl_NewIntObj(i));
2432    }
2433
2434    /*
2435     * Build a list K that holds the descriptions of the common
2436     * subsequences.  At first, there is one common subsequence of
2437     * length 0 with a fence that includes line -1 of both files.
2438     */
2439    Kmax = 10;
2440    K = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
2441    K[0].index1 = -1;
2442    K[0].index2 = -1;
2443    K[0].next = -1;
2444    K[1].index1 = limsPtr1->numLines;
2445    K[1].index2 = limsPtr2->numLines;
2446    K[1].next = -1;
2447    Klen = 2;
2448    longestMatch = 0;
2449
2450    /*
2451     * Step through the first buffer line by line.
2452     */
2453    for (i=0; i < limsPtr1->numLines; i++) {
2454        len = limsPtr1->lenPtr[i];
2455        Tcl_DStringSetLength(&buffer, len);
2456        key = Tcl_DStringValue(&buffer);
2457        memcpy((VOID*)key, (VOID*)limsPtr1->startPtr[i], len);
2458
2459        /* look at each possible line j in second buffer */
2460        entryPtr = Tcl_FindHashEntry(&eqv, key);
2461        if (entryPtr) {
2462            subseqLen = 0;
2463            candidateIdx = 0;
2464
2465            listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2466            Tcl_ListObjGetElements((Tcl_Interp*)NULL, listPtr, &objc, &objv);
2467            for (o=0; o < objc; o++) {
2468                Tcl_GetIntFromObj((Tcl_Interp*)NULL, objv[o], &j);
2469
2470                /*
2471                 * Binary search to find a candidate common subsequence.
2472                 * This match may get appended to that.
2473                 */
2474                max  = longestMatch;
2475                min  = subseqLen;
2476                mid  = subseqLen;
2477                sLen = longestMatch + 1;
2478                while (max >= min) {
2479                    mid = (max+min)/2;
2480                    midval = K[mid].index2;
2481                    if (j == midval) {
2482                        break;
2483                    } else if (j < midval) {
2484                        max = mid-1;
2485                    } else {
2486                        sLen = mid;
2487                        min = mid+1;
2488                    }
2489                }
2490
2491                /* no good candidate? then go to the next match point */
2492                if (j == K[mid].index2 || sLen > longestMatch) {
2493                    continue;
2494                }
2495
2496                /*
2497                 * sLen = sequence length of the longest sequence that
2498                 *        this match point can be appended to
2499                 *
2500                 * Make a new candidate match and store the old on in K.
2501                 * Set subseqLen to the length of the new candidate match.
2502                 */
2503                if (subseqLen >= 0) {
2504                    if (candidateIdx >= 0) {
2505                        K[subseqLen].index1 = K[candidateIdx].index1;
2506                        K[subseqLen].index2 = K[candidateIdx].index2;
2507                        K[subseqLen].next   = K[candidateIdx].next;
2508                    } else {
2509                        K[subseqLen].index1 = newCandidate.index1;
2510                        K[subseqLen].index2 = newCandidate.index2;
2511                        K[subseqLen].next   = newCandidate.next;
2512                    }
2513                }
2514                newCandidate.index1 = i;
2515                newCandidate.index2 = j;
2516                newCandidate.next = sLen;
2517                candidateIdx = -1;  /* use newCandidate info */
2518                subseqLen = sLen + 1;
2519
2520                /*
2521                 * If we've extended the length of the longest match,
2522                 * we don't need to keep checking candidate lines.
2523                 * We're done.  Move the fence.
2524                 */
2525                if (sLen >= longestMatch) {
2526                    if (Klen >= Kmax) {
2527                        Kmax *= 2;
2528                        newK = (DiffviewSubseq*)ckalloc(Kmax*sizeof(DiffviewSubseq));
2529                        memcpy((VOID*)newK, (VOID*)K, Klen*sizeof(DiffviewSubseq));
2530                        ckfree((char*)K);
2531                        K = newK;
2532                    }
2533                    K[Klen].index1 = K[Klen-1].index1;
2534                    K[Klen].index2 = K[Klen-1].index2;
2535                    K[Klen].next   = K[Klen-1].next;
2536                    Klen++;
2537
2538                    longestMatch++;
2539                    break;
2540                }
2541            }
2542
2543            /* put the last candidate into the array */
2544            if (candidateIdx >= 0) {
2545                K[subseqLen].index1 = K[candidateIdx].index1;
2546                K[subseqLen].index2 = K[candidateIdx].index2;
2547                K[subseqLen].next   = K[candidateIdx].next;
2548            } else {
2549                K[subseqLen].index1 = newCandidate.index1;
2550                K[subseqLen].index2 = newCandidate.index2;
2551                K[subseqLen].next   = newCandidate.next;
2552            }
2553        }
2554    }
2555
2556    /*
2557     * Translate the resulting info into a list of common subseq indices.
2558     */
2559    lcsIndex1 = (int*)ckalloc(longestMatch*sizeof(int));
2560    memset((void*)lcsIndex1, 0, (longestMatch*sizeof(int)));
2561    lcsIndex2 = (int*)ckalloc(longestMatch*sizeof(int));
2562    memset((void*)lcsIndex2, 0, (longestMatch*sizeof(int)));
2563    len = longestMatch;
2564
2565    candidateIdx = longestMatch;
2566    while (K[candidateIdx].index1 >= 0) {
2567        if (longestMatch-- < 0) {
2568            Tcl_Panic("internal mismatch in diff algorithm");
2569        }
2570        lcsIndex1[longestMatch] = K[candidateIdx].index1;
2571        lcsIndex2[longestMatch] = K[candidateIdx].index2;
2572        candidateIdx = K[candidateIdx].next;
2573    }
2574
2575    /*
2576     * Now, march through all lines in both buffers and convert the
2577     * lcs indices into a sequence of diff-style operations.
2578     */
2579    diffPtr = (DiffviewDiffs*)ckalloc(sizeof(DiffviewDiffs));
2580    diffPtr->maxDiffs = 0;
2581    diffPtr->numDiffs = 0;
2582    diffPtr->ops = NULL;
2583
2584    i = j = 0;
2585    for (o=0; o < len; o++) {
2586        index1 = lcsIndex1[o];
2587        index2 = lcsIndex2[o];
2588
2589        if (index1-i == index2-j && index1-i > 0) {
2590            del = index1-i;
2591            DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
2592            i += del; j += del;
2593        } else {
2594            del = index1-i;
2595            if (del > 0) {
2596                DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
2597                i += del;
2598            }
2599
2600            del = index2-j;
2601            if (del > 0) {
2602                DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
2603                j += del;
2604            }
2605        }
2606        i++; j++;
2607    }
2608
2609    /* lines left over? mark as many "changed" as possible */
2610    if (limsPtr1->numLines-i > 0 && limsPtr2->numLines-j > 0) {
2611        del = (limsPtr1->numLines-i < limsPtr2->numLines-j)
2612                ? limsPtr1->numLines-i : limsPtr2->numLines-j;
2613        DiffviewDiffsAppend(diffPtr, 'c', i, i+del-1, j, j+del-1);
2614        i += del;
2615        j += del;
2616    }
2617
2618    /* still have lines left over? then mark them added or deleted */
2619    if (i < limsPtr1->numLines) {
2620        del = limsPtr1->numLines - i;
2621        DiffviewDiffsAppend(diffPtr, 'd', i, i+del-1, j, j);
2622        i += del;
2623    }
2624    if (j < limsPtr2->numLines) {
2625        del = limsPtr2->numLines - j;
2626        DiffviewDiffsAppend(diffPtr, 'a', i, i, j, j+del-1);
2627        j += del;
2628    }
2629
2630    /*
2631     * Clean up all memory used during the diff.
2632     */
2633    ckfree((char*)K);
2634    ckfree((char*)lcsIndex1);
2635    ckfree((char*)lcsIndex2);
2636
2637    entryPtr = Tcl_FirstHashEntry(&eqv, &iter);
2638    while (entryPtr) {
2639        listPtr = (Tcl_Obj*)Tcl_GetHashValue(entryPtr);
2640        Tcl_DecrRefCount(listPtr);
2641        entryPtr = Tcl_NextHashEntry(&iter);
2642    }
2643    Tcl_DeleteHashTable(&eqv);
2644
2645    return diffPtr;
2646}
2647
2648/*
2649 * ----------------------------------------------------------------------
2650 * DiffviewDiffsAppend()
2651 *
2652 * Appends a "diff" operation onto a list of diffs.  The list is extended
2653 * automatically, if need be, to store the new element.
2654 * ----------------------------------------------------------------------
2655 */
2656static void
2657DiffviewDiffsAppend(diffsPtr, op, fromIndex1, toIndex1, fromIndex2, toIndex2)
2658    DiffviewDiffs *diffsPtr;  /* diffs being updated */
2659    int op;                   /* diff operation: a/d/c */
2660    int fromIndex1;           /* starts at this line in buffer 1 */
2661    int toIndex1;             /* ends at this line in buffer 1 */
2662    int fromIndex2;           /* starts at this line in buffer 2 */
2663    int toIndex2;             /* ends at this line in buffer 2 */
2664{
2665    DiffviewDiffOp *newOpArray;
2666    int last;
2667
2668    if (diffsPtr->numDiffs >= diffsPtr->maxDiffs) {
2669        if (diffsPtr->maxDiffs == 0) {
2670            diffsPtr->maxDiffs = 10;
2671        } else {
2672            diffsPtr->maxDiffs *= 2;
2673        }
2674        newOpArray = (DiffviewDiffOp*)ckalloc(
2675            diffsPtr->maxDiffs*sizeof(DiffviewDiffOp));
2676
2677        if (diffsPtr->ops) {
2678            memcpy((VOID*)newOpArray, (VOID*)diffsPtr->ops,
2679                diffsPtr->numDiffs*sizeof(DiffviewDiffOp));
2680            ckfree((char*)diffsPtr->ops);
2681        }
2682        diffsPtr->ops = newOpArray;
2683    }
2684
2685    last = diffsPtr->numDiffs;
2686    diffsPtr->ops[last].op         = op;
2687    diffsPtr->ops[last].fromIndex1 = fromIndex1;
2688    diffsPtr->ops[last].toIndex1   = toIndex1;
2689    diffsPtr->ops[last].fromIndex2 = fromIndex2;
2690    diffsPtr->ops[last].toIndex2   = toIndex2;
2691    diffsPtr->numDiffs++;
2692}
2693
2694/*
2695 * ----------------------------------------------------------------------
2696 * DiffviewDiffsFree()
2697 *
2698 * Frees up the storage previously created by calling
2699 * DiffviewDiffsAppend().
2700 * ----------------------------------------------------------------------
2701 */
2702static void
2703DiffviewDiffsFree(diffsPtr)
2704    DiffviewDiffs *diffsPtr;  /* diffs being freed */
2705{
2706    if (diffsPtr->ops) {
2707        ckfree((char*)diffsPtr->ops);
2708    }
2709    ckfree((char*)diffsPtr);
2710}
2711
2712/*
2713 * ----------------------------------------------------------------------
2714 * DiffviewLayoutAdd()
2715 *
2716 * Clears out the layout storage in preparation for building another
2717 * layout.  If the previous number of lines was much less than the
2718 * maximum allocated, then this routine reallocates a smaller storage
2719 * space.  This provides a way to give back a large chunk of memory
2720 * if the contents of the widget is nulled out, for example.
2721 * ----------------------------------------------------------------------
2722 */
2723static void
2724DiffviewLayoutAdd(layoutPtr, linePtr)
2725    DiffviewLayout *layoutPtr;    /* line layout being updated */
2726    DiffviewLayoutLine *linePtr;  /* line being added to layout */
2727{
2728    DiffviewLayoutLine *newLineArray;
2729
2730    /* expand the array as needed */
2731    if (layoutPtr->numLines >= layoutPtr->maxLines) {
2732        if (layoutPtr->maxLines == 0) {
2733            layoutPtr->maxLines = 100;
2734        } else {
2735            layoutPtr->maxLines *= 2;
2736        }
2737        newLineArray = (DiffviewLayoutLine*)ckalloc(
2738            (unsigned)(layoutPtr->maxLines*sizeof(DiffviewLayoutLine)));
2739
2740        if (layoutPtr->lines) {
2741            memcpy((VOID*)newLineArray, (VOID*)layoutPtr->lines,
2742                layoutPtr->numLines*sizeof(DiffviewLayoutLine));
2743            ckfree((char*)layoutPtr->lines);
2744        }
2745        layoutPtr->lines = newLineArray;
2746    }
2747
2748    /* copy the latest line in and bump the count */
2749    memcpy((VOID*)&layoutPtr->lines[layoutPtr->numLines],
2750        (VOID*)linePtr, sizeof(DiffviewLayoutLine));
2751
2752    layoutPtr->numLines++;
2753}
2754
2755/*
2756 * ----------------------------------------------------------------------
2757 * DiffviewLayoutClear()
2758 *
2759 * Clears out the layout storage in preparation for building another
2760 * layout.  If the previous number of lines was much less than the
2761 * maximum allocated, then this routine reallocates a smaller storage
2762 * space.  This provides a way to give back a large chunk of memory
2763 * if the contents of the widget is nulled out, for example.
2764 * ----------------------------------------------------------------------
2765 */
2766static void
2767DiffviewLayoutClear(layoutPtr)
2768    DiffviewLayout *layoutPtr;  /* line layout being freed */
2769{
2770    if (layoutPtr->numLines > 0
2771          && layoutPtr->numLines < layoutPtr->maxLines/3
2772          && layoutPtr->maxLines > 100) {
2773        ckfree((char*)layoutPtr->lines);
2774        layoutPtr->lines = (DiffviewLayoutLine*)ckalloc(
2775            (unsigned)(layoutPtr->numLines * sizeof(DiffviewLayoutLine)));
2776        layoutPtr->maxLines = layoutPtr->numLines;
2777    }
2778    layoutPtr->numLines = 0;
2779}
2780
2781/*
2782 * ----------------------------------------------------------------------
2783 * DiffviewLayoutFree()
2784 *
2785 * Frees the storage associated with the layout of all lines within
2786 * the widget.
2787 * ----------------------------------------------------------------------
2788 */
2789static void
2790DiffviewLayoutFree(layoutPtr)
2791    DiffviewLayout *layoutPtr;  /* line layout being freed */
2792{
2793    if (layoutPtr->lines) {
2794        ckfree((char*)layoutPtr->lines);
2795        layoutPtr->lines = NULL;
2796    }
2797}
Note: See TracBrowser for help on using the repository browser.