<commit_before>/***************************************************************

  Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 2001.
  Unpublished work.  All Rights Reserved.

  The software contained on this media is the property of the
  DSTC Pty Ltd.  Use of this software is strictly in accordance
  with the license agreement in the accompanying LICENSE.DOC
  file.  If your distribution of this software does not contain
  a LICENSE.DOC file then you have no rights to use this
  software in any manner and should contact DSTC at the address
  below to determine an appropriate licensing arrangement.

     DSTC Pty Ltd
     Level 7, GP South
     University of Queensland
     St Lucia, 4072
     Australia
     Tel: +61 7 3365 4310
     Fax: +61 7 3365 4311
     Email: <EMAIL>

  This software is being provided "AS IS" without warranty of
  any kind.  In no event shall DSTC Pty Ltd be liable for
  damage of any kind arising out of or in connection with
  the use or performance of this software.

****************************************************************/

#ifndef lint
static const char cvsid[] = "$Id: History.c,v 1.56 2002/04/11 11:34:03 phelps Exp $";
#endif /* lint */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/IntrinsicP.h>
#include <X11/Xresource.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <X11/Xaw/SimpleP.h>
#include <Xm/XmAll.h>
#include <Xm/PrimitiveP.h>
#include "message.h"
#include "message_view.h"
#include "History.h"
#include "HistoryP.h"


#ifdef DEBUG
#define dprintf(x) printf x
#else
#define dprintf(x) ;
#endif

#if !defined(MIN)
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif

#if !defined(MAX)
# define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif


/*
 *
 * Resources
 *
 */

#define offset(field) XtOffsetOf(HistoryRec, field)
static XtResource resources[] =
{
    /* XtCallbackProc callback */
    {
	XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XtCallbackList attachment_callbacks */
    {
	XtNattachmentCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.attachment_callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XtCallbackList motion_callbacks */
    {
	XtNmotionCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.motion_callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XFontStruct *font */
    {
	XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
	offset(history.font), XtRString, XtDefaultFont
    },

    /* Pixel timestamp_pixel */
    {
	XtNtimestampPixel, XtCTimestampPixel, XtRPixel, sizeof(Pixel),
	offset(history.timestamp_pixel), XtRString, "Black"
    },

    /* Pixel group_pixel */
    {
	XtNgroupPixel, XtCGroupPixel, XtRPixel, sizeof(Pixel),
	offset(history.group_pixel), XtRString, "Blue"
    },

    /* Pixel user_pixel */
    {
	XtNuserPixel, XtCUserPixel, XtRPixel, sizeof(Pixel),
	offset(history.user_pixel), XtRString, "Green"
    },

    /* Pixel string_pixel */
    {
	XtNstringPixel, XtCStringPixel, XtRPixel, sizeof(Pixel),
	offset(history.string_pixel), XtRString, "Red"
    },

    /* Pixel separator_pixel */
    {
	XtNseparatorPixel, XtCSeparatorPixel, XtRPixel, sizeof(Pixel),
	offset(history.separator_pixel), XtRString, XtDefaultForeground
    },

    /* Dimension margin_width */
    {
	XtNmarginWidth, XtCMarginWidth, XtRDimension, sizeof(Dimension),
	offset(history.margin_width), XtRImmediate, (XtPointer)5
    },

    /* Dimension margin_height */
    {
	XtNmarginHeight, XtCMarginHeight, XtRDimension, sizeof(Dimension),
	offset(history.margin_height), XtRImmediate, (XtPointer)5
    },

    /* unsigned int message_capacity */
    {
	XtNmessageCapacity, XtCMessageCapacity, XtRDimension, sizeof(unsigned int),
	offset(history.message_capacity), XtRImmediate, (XtPointer)32
    },

    /* Pixel selection_pixel */
    {
	XtNselectionPixel, XtCSelectionPixel, XtRPixel, sizeof(Pixel),
	offset(history.selection_pixel), XtRString, XtDefaultForeground
    },

    /* int drag_delay */
    {
	XtNdragDelay, XtCDragDelay, XtRInt, sizeof(int),
	offset(history.drag_delay), XtRImmediate, (XtPointer)100
    }
};
#undef offset


/* Action declarations */
static void drag(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void drag_done(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void do_select(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void toggle_selection(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void show_attachment(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void select_previous(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void select_next(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void scroll_left(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void scroll_right(Widget widget, XEvent *event, String *params, Cardinal *nparams);

static XtActionsRec actions[] =
{
    { "drag", drag },
    { "drag-done", drag_done },
    { "select", do_select },
    { "toggle-selection", toggle_selection },
    { "show-attachment", show_attachment },
    { "select-previous", select_previous },
    { "select-next", select_next },
    { "scroll-left", scroll_left },
    { "scroll-right", scroll_right }
};


/* We move the History window around by calling XCopyArea to move the
 * visible portion of the screen and rely on GraphicsExpose events to
 * tell us what to repaint.  It occasionally happens that we want to
 * move the window again before the X server has processed our
 * XCopyArea request.  When this happens we need to tweak the
 * coordinates of the Expose events generated by the first XCopyArea
 * because the exposed areas will have moved by the time the packets
 * which redraw them are processed by the server.  Since there's no
 * upper limit on the number of XCopyAreas that haven't yet been
 * processed we end up using a linked list to represent all them */
struct delta_queue
{
    /* The next item in the queue */
    delta_queue_t next;

    /* The sequence number of the XCopyArea request */
    unsigned long request_id;

    /* The displacement in the X direction */
    long dx;

    /* The displacement in the Y direction */
    long dy;
};


/* We store the history of messages as a tree, according to
 * message-ids and in-reply-to ids */
struct node
{
    /* The message recorded by the node */
    message_t message;

    /* The node's youngest child */
    node_t child;

    /* The node's youngest elder sibling */
    node_t sibling;
};

/* Allocates and returns a new node */
static node_t node_alloc(message_t message)
{
    node_t self;

    /* Allocate memory for the node */
    if ((self = malloc(sizeof(struct node))) == NULL)
    {
	return NULL;
    }

    /* Initialize its fields to sane values */
    memset(self, 0, sizeof(struct node));

    /* Record the message */
    self -> message = message_alloc_reference(message);
    return self;
}

/* Frees a node */
static void node_free(node_t self)
{
    /* Make sure we decrement the message's reference count */
    if (self -> message != NULL)
    {
	message_free(self -> message);
	self -> message = NULL;
    }

    dprintf(("node_free(): %p\n", self));
    free(self);
}

/* Returns the id of the node's message */
static char *node_get_id(node_t self)
{
    return message_get_id(self -> message);
}


/* Add a node to the tree.  WARNING: this is not a friendly function.
 * To avoid having to traverse the tree more than once when adding a
 * node, this function both finds a parent and adds a child to it and
 * also discards any nodes which no longer have visible children.
 *
 * self
 *    The pointer to the root node of the tree.  This will be updated,
 *    so it should really be the official pointer to the tree.
 *
 * parent_id
 *    The Message-Id of the parent of the node to add.
 *
 * child
 *    The child node to be added.  This will be set to NULL, so don't
 *    use a pointer you want to use later.
 *
 * index
 *    The display index of the root node.  This should initially be
 *    one less than the number of visible nodes.  Note that this value
 *    will be overwritten, so don't point to a value you wish to keep.
 *
 * index_out
 *    Will be set to the index of the newly added node.
 *
 * depth
 *    The depth (indentation level) of self.  Should initially be 0.
 *
 * depth_out
 *    Will be set to the depth of the newly added node.
 */
static void node_add1(node_t *self,
		      char *parent_id, node_t *child,
		      int *index, int *index_out,
		      int depth, int *depth_out)
{
    /* Traverse all of our siblings */
    while (*self != NULL)
    {
	char *id;

	/* If no match yet, then check for one here */
	if (parent_id && *child != NULL &&
	    (id = node_get_id(*self)) != NULL &&
	    strcmp(id, parent_id) == 0)
	{
	    /* Match!  Add the child to this node */
	    (*child) -> sibling = (*self) -> child;
	    (*self) -> child = *child;
	    *child = NULL;

	    /* Record output information */
	    *index_out = *index;
	    *depth_out = depth + 1;

	    /* The next node we visit will be the new child.  We don't
	     * want it to update the index to be consistent with the
	     * case where we can't find its parent, so we increment
	     * the index here to compensate for it being decremented
	     * when we traverse the children next. */
	    (*index)++;
	}

	/* Check with the descendents */
	if ((*self) -> child != NULL)
	{
	    node_add1(&(*self) -> child,
		     parent_id, child,
		     index, index_out,
		     depth + 1, depth_out);
	}

	/* We've traversed a node */
	(*index)--;

	/* If this node has scrolled off the top of the history and
	 * has no children then we can safely remove it from the tree.
	 * Note: this check is simplistic, but it will work since the
	 * new node must always be added after any nodes that have
	 * scrolled off the top. */
	if (*index < 0 && (*self) -> child == NULL)
	{
	    /* If the above statement is true, then it follows that
	     * the node may not have any elder siblings.  Hence this
	     * is a good sanity check. */
	    assert((*self) -> sibling == NULL);

	    /* Free the node */
	    node_free(*self);
	    *self = NULL;
	    return;
	}

	/* The pointer we want to update if this node disappears */
	self = &(*self) -> sibling;
    }
}

/* Add a node to the tree.  This is just a friendly wrapper around
 * node_add1() above.
 *
 * self
 *    The pointer to the variable holding the root of the tree.  This
 *    will commonly be modified, so use the actual pointer.
 *
 * parent_id
 *    The Message-Id of the parent node; the In-Reply-To field.
 *
 * child
 *    The node to be added to the tree.
 *
 * count
 *    The number of nodes which should be visible in the tree view
 *    once the child is added to the tree.
 *
 * index_out
 *    Will be set to the index of the newly added node.
 *
 * depth_out
 *    Will be set to the depth of the newly added node.
 */
static void node_add(node_t *self,
		     char *parent_id,
		     node_t child,
		     int count,
		     int *index_out,
		     int *depth_out)
{
    int index;

    /* The last index will be count - 1 */
    index = count - 1;

    /* Trim the older nodes in the tree, and add the node if its
     * parent is in the tree. */
    node_add1(self, parent_id, &child, &index, index_out, 0, depth_out);

    /* Add the child now if it didn't get added already */
    if (child != NULL)
    {
	/* Add the node to the tree */
	child -> sibling = *self;
	(*self) = child;
	*index_out = count - 1;
	*depth_out = 0;
    }
}

#ifdef DEBUG
static void node_dump(node_t self, int depth)
{
    int i;

    /* Nothing to print... */
    if (self == NULL)
    {
	return;
    }

    /* Print our siblings */
    node_dump(self -> sibling, depth);

    /* Indent */
    for (i = 0; i < depth; i++)
    {
	printf("  ");
    }

    /* Print this node's message */
    printf("%p: %s\n", self, message_get_string(self -> message));

    /* Print our children */
    node_dump(self -> child, depth + 1);
}
#endif


/* Populate an array with message views */
static void node_populate(node_t self,
			  XFontStruct *font,
			  message_view_t *array,
			  int depth, int *index,
			  message_t selection,
			  unsigned int *selection_index_out)
{
    /* Go through each of our siblings */
    while (self != NULL)
    {
	/* Add the children first */
	if (self -> child)
	{
	    node_populate(self -> child, font, array, depth + 1, index, selection, selection_index_out);
	}

	/* Bail if the index goes negative */
	if (index < 0)
	{
	    printf("bailing out!\n");
	    return;
	}

	/* Is this the selection? */
	if (self -> message == selection)
	{
	    *selection_index_out = *index;
	}

	/* Wrap the message in a message view */
	array[(*index)--] =  message_view_alloc(self -> message, font, depth);

	/* Move on to the next node */
	self = self -> sibling;
    }
}
		     
<commit_msg>Let's compare the variable to zero, not the pointer.<commit_after>/***************************************************************

  Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 2001.
  Unpublished work.  All Rights Reserved.

  The software contained on this media is the property of the
  DSTC Pty Ltd.  Use of this software is strictly in accordance
  with the license agreement in the accompanying LICENSE.DOC
  file.  If your distribution of this software does not contain
  a LICENSE.DOC file then you have no rights to use this
  software in any manner and should contact DSTC at the address
  below to determine an appropriate licensing arrangement.

     DSTC Pty Ltd
     Level 7, GP South
     University of Queensland
     St Lucia, 4072
     Australia
     Tel: +61 7 3365 4310
     Fax: +61 7 3365 4311
     Email: <EMAIL>

  This software is being provided "AS IS" without warranty of
  any kind.  In no event shall DSTC Pty Ltd be liable for
  damage of any kind arising out of or in connection with
  the use or performance of this software.

****************************************************************/

#ifndef lint
static const char cvsid[] = "$Id: History.c,v 1.57 2002/04/11 15:30:22 phelps Exp $";
#endif /* lint */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/IntrinsicP.h>
#include <X11/Xresource.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <X11/Xaw/SimpleP.h>
#include <Xm/XmAll.h>
#include <Xm/PrimitiveP.h>
#include "message.h"
#include "message_view.h"
#include "History.h"
#include "HistoryP.h"


#ifdef DEBUG
#define dprintf(x) printf x
#else
#define dprintf(x) ;
#endif

#if !defined(MIN)
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif

#if !defined(MAX)
# define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif


/*
 *
 * Resources
 *
 */

#define offset(field) XtOffsetOf(HistoryRec, field)
static XtResource resources[] =
{
    /* XtCallbackProc callback */
    {
	XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XtCallbackList attachment_callbacks */
    {
	XtNattachmentCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.attachment_callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XtCallbackList motion_callbacks */
    {
	XtNmotionCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(history.motion_callbacks), XtRCallback, (XtPointer)NULL
    },

    /* XFontStruct *font */
    {
	XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
	offset(history.font), XtRString, XtDefaultFont
    },

    /* Pixel timestamp_pixel */
    {
	XtNtimestampPixel, XtCTimestampPixel, XtRPixel, sizeof(Pixel),
	offset(history.timestamp_pixel), XtRString, "Black"
    },

    /* Pixel group_pixel */
    {
	XtNgroupPixel, XtCGroupPixel, XtRPixel, sizeof(Pixel),
	offset(history.group_pixel), XtRString, "Blue"
    },

    /* Pixel user_pixel */
    {
	XtNuserPixel, XtCUserPixel, XtRPixel, sizeof(Pixel),
	offset(history.user_pixel), XtRString, "Green"
    },

    /* Pixel string_pixel */
    {
	XtNstringPixel, XtCStringPixel, XtRPixel, sizeof(Pixel),
	offset(history.string_pixel), XtRString, "Red"
    },

    /* Pixel separator_pixel */
    {
	XtNseparatorPixel, XtCSeparatorPixel, XtRPixel, sizeof(Pixel),
	offset(history.separator_pixel), XtRString, XtDefaultForeground
    },

    /* Dimension margin_width */
    {
	XtNmarginWidth, XtCMarginWidth, XtRDimension, sizeof(Dimension),
	offset(history.margin_width), XtRImmediate, (XtPointer)5
    },

    /* Dimension margin_height */
    {
	XtNmarginHeight, XtCMarginHeight, XtRDimension, sizeof(Dimension),
	offset(history.margin_height), XtRImmediate, (XtPointer)5
    },

    /* unsigned int message_capacity */
    {
	XtNmessageCapacity, XtCMessageCapacity, XtRDimension, sizeof(unsigned int),
	offset(history.message_capacity), XtRImmediate, (XtPointer)32
    },

    /* Pixel selection_pixel */
    {
	XtNselectionPixel, XtCSelectionPixel, XtRPixel, sizeof(Pixel),
	offset(history.selection_pixel), XtRString, XtDefaultForeground
    },

    /* int drag_delay */
    {
	XtNdragDelay, XtCDragDelay, XtRInt, sizeof(int),
	offset(history.drag_delay), XtRImmediate, (XtPointer)100
    }
};
#undef offset


/* Action declarations */
static void drag(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void drag_done(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void do_select(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void toggle_selection(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void show_attachment(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void select_previous(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void select_next(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void scroll_left(Widget widget, XEvent *event, String *params, Cardinal *nparams);
static void scroll_right(Widget widget, XEvent *event, String *params, Cardinal *nparams);

static XtActionsRec actions[] =
{
    { "drag", drag },
    { "drag-done", drag_done },
    { "select", do_select },
    { "toggle-selection", toggle_selection },
    { "show-attachment", show_attachment },
    { "select-previous", select_previous },
    { "select-next", select_next },
    { "scroll-left", scroll_left },
    { "scroll-right", scroll_right }
};


/* We move the History window around by calling XCopyArea to move the
 * visible portion of the screen and rely on GraphicsExpose events to
 * tell us what to repaint.  It occasionally happens that we want to
 * move the window again before the X server has processed our
 * XCopyArea request.  When this happens we need to tweak the
 * coordinates of the Expose events generated by the first XCopyArea
 * because the exposed areas will have moved by the time the packets
 * which redraw them are processed by the server.  Since there's no
 * upper limit on the number of XCopyAreas that haven't yet been
 * processed we end up using a linked list to represent all them */
struct delta_queue
{
    /* The next item in the queue */
    delta_queue_t next;

    /* The sequence number of the XCopyArea request */
    unsigned long request_id;

    /* The displacement in the X direction */
    long dx;

    /* The displacement in the Y direction */
    long dy;
};


/* We store the history of messages as a tree, according to
 * message-ids and in-reply-to ids */
struct node
{
    /* The message recorded by the node */
    message_t message;

    /* The node's youngest child */
    node_t child;

    /* The node's youngest elder sibling */
    node_t sibling;
};

/* Allocates and returns a new node */
static node_t node_alloc(message_t message)
{
    node_t self;

    /* Allocate memory for the node */
    if ((self = malloc(sizeof(struct node))) == NULL)
    {
	return NULL;
    }

    /* Initialize its fields to sane values */
    memset(self, 0, sizeof(struct node));

    /* Record the message */
    self -> message = message_alloc_reference(message);
    return self;
}

/* Frees a node */
static void node_free(node_t self)
{
    /* Make sure we decrement the message's reference count */
    if (self -> message != NULL)
    {
	message_free(self -> message);
	self -> message = NULL;
    }

    dprintf(("node_free(): %p\n", self));
    free(self);
}

/* Returns the id of the node's message */
static char *node_get_id(node_t self)
{
    return message_get_id(self -> message);
}


/* Add a node to the tree.  WARNING: this is not a friendly function.
 * To avoid having to traverse the tree more than once when adding a
 * node, this function both finds a parent and adds a child to it and
 * also discards any nodes which no longer have visible children.
 *
 * self
 *    The pointer to the root node of the tree.  This will be updated,
 *    so it should really be the official pointer to the tree.
 *
 * parent_id
 *    The Message-Id of the parent of the node to add.
 *
 * child
 *    The child node to be added.  This will be set to NULL, so don't
 *    use a pointer you want to use later.
 *
 * index
 *    The display index of the root node.  This should initially be
 *    one less than the number of visible nodes.  Note that this value
 *    will be overwritten, so don't point to a value you wish to keep.
 *
 * index_out
 *    Will be set to the index of the newly added node.
 *
 * depth
 *    The depth (indentation level) of self.  Should initially be 0.
 *
 * depth_out
 *    Will be set to the depth of the newly added node.
 */
static void node_add1(node_t *self,
		      char *parent_id, node_t *child,
		      int *index, int *index_out,
		      int depth, int *depth_out)
{
    /* Traverse all of our siblings */
    while (*self != NULL)
    {
	char *id;

	/* If no match yet, then check for one here */
	if (parent_id && *child != NULL &&
	    (id = node_get_id(*self)) != NULL &&
	    strcmp(id, parent_id) == 0)
	{
	    /* Match!  Add the child to this node */
	    (*child) -> sibling = (*self) -> child;
	    (*self) -> child = *child;
	    *child = NULL;

	    /* Record output information */
	    *index_out = *index;
	    *depth_out = depth + 1;

	    /* The next node we visit will be the new child.  We don't
	     * want it to update the index to be consistent with the
	     * case where we can't find its parent, so we increment
	     * the index here to compensate for it being decremented
	     * when we traverse the children next. */
	    (*index)++;
	}

	/* Check with the descendents */
	if ((*self) -> child != NULL)
	{
	    node_add1(&(*self) -> child,
		     parent_id, child,
		     index, index_out,
		     depth + 1, depth_out);
	}

	/* We've traversed a node */
	(*index)--;

	/* If this node has scrolled off the top of the history and
	 * has no children then we can safely remove it from the tree.
	 * Note: this check is simplistic, but it will work since the
	 * new node must always be added after any nodes that have
	 * scrolled off the top. */
	if (*index < 0 && (*self) -> child == NULL)
	{
	    /* If the above statement is true, then it follows that
	     * the node may not have any elder siblings.  Hence this
	     * is a good sanity check. */
	    assert((*self) -> sibling == NULL);

	    /* Free the node */
	    node_free(*self);
	    *self = NULL;
	    return;
	}

	/* The pointer we want to update if this node disappears */
	self = &(*self) -> sibling;
    }
}

/* Add a node to the tree.  This is just a friendly wrapper around
 * node_add1() above.
 *
 * self
 *    The pointer to the variable holding the root of the tree.  This
 *    will commonly be modified, so use the actual pointer.
 *
 * parent_id
 *    The Message-Id of the parent node; the In-Reply-To field.
 *
 * child
 *    The node to be added to the tree.
 *
 * count
 *    The number of nodes which should be visible in the tree view
 *    once the child is added to the tree.
 *
 * index_out
 *    Will be set to the index of the newly added node.
 *
 * depth_out
 *    Will be set to the depth of the newly added node.
 */
static void node_add(node_t *self,
		     char *parent_id,
		     node_t child,
		     int count,
		     int *index_out,
		     int *depth_out)
{
    int index;

    /* The last index will be count - 1 */
    index = count - 1;

    /* Trim the older nodes in the tree, and add the node if its
     * parent is in the tree. */
    node_add1(self, parent_id, &child, &index, index_out, 0, depth_out);

    /* Add the child now if it didn't get added already */
    if (child != NULL)
    {
	/* Add the node to the tree */
	child -> sibling = *self;
	(*self) = child;
	*index_out = count - 1;
	*depth_out = 0;
    }
}

#ifdef DEBUG
static void node_dump(node_t self, int depth)
{
    int i;

    /* Nothing to print... */
    if (self == NULL)
    {
	return;
    }

    /* Print our siblings */
    node_dump(self -> sibling, depth);

    /* Indent */
    for (i = 0; i < depth; i++)
    {
	printf("  ");
    }

    /* Print this node's message */
    printf("%p: %s\n", self, message_get_string(self -> message));

    /* Print our children */
    node_dump(self -> child, depth + 1);
}
#endif


/* Populate an array with message views */
static void node_populate(node_t self,
			  XFontStruct *font,
			  message_view_t *array,
			  int depth, int *index,
			  message_t selection,
			  unsigned int *selection_index_out)
{
    /* Go through each of our siblings */
    while (self != NULL)
    {
	/* Add the children first */
	if (self -> child)
	{
	    node_populate(self -> child, font, array, depth + 1, index, selection, selection_index_out);
	}

	/* Bail if the index goes negative */
	if (*index < 0)
	{
	    printf("bailing out!\n");
	    return;
	}

	/* Is this the selection? */
	if (self -> message == selection)
	{
	    *selection_index_out = *index;
	}

	/* Wrap the message in a message view */
	array[(*index)--] =  message_view_alloc(self -> message, font, depth);

	/* Move on to the next node */
	self = self -> sibling;
    }
}

{'fraction_non_alphanumeric': 0.11634980988593156, 'fraction_numerical': 0.007224334600760456, 'mean_word_length': 3.0890858208955225, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6084965', 'n_tokens_mistral': 8700, 'n_tokens_neox': 8202, 'n_words': 3002}