/* vi:set ts=8 sts=4 sw=4 noet: */

/*
 * Copyright 1989 Software Research Associates, Inc., Tokyo, Japan
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.  Software Research Associates
 * makes no representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * SOFTWARE RESEARCH ASSOCIATES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS,
 * IN NO EVENT SHALL SOFTWARE RESEARCH ASSOCIATES BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Author: Erik M. van der Poel
 *	   Software Research Associates, Inc., Tokyo, Japan
 *	   erik@sra.co.jp
 */
/*
 * Author's addresses:
 *	erik@sra.co.jp
 *	erik%sra.co.jp@uunet.uu.net
 *	erik%sra.co.jp@mcvax.uucp
 *	try junet instead of co.jp
 *	Erik M. van der Poel
 *	Software Research Associates, Inc.
 *	1-1-1 Hirakawa-cho, Chiyoda-ku
 *	Tokyo 102 Japan. TEL +81-3-234-2692
 */

/*
 * Heavely modified for Vim by Bram Moolenaar
 */

#include "vim.h"

// Only include this when using the file browser

#ifdef FEAT_BROWSE

// Weird complication: for "make lint" Text.h doesn't combine with Xm.h
#if defined(FEAT_GUI_MOTIF) && defined(FMT8BIT)
# undef FMT8BIT
#endif

#ifndef FEAT_GUI_NEXTAW
# include "gui_at_sb.h"
#endif

////////////////// SFinternal.h

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xos.h>
#ifdef FEAT_GUI_NEXTAW
# include <X11/neXtaw/Text.h>
# include <X11/neXtaw/AsciiText.h>
# include <X11/neXtaw/Scrollbar.h>
#else
# include <X11/Xaw/Text.h>
# include <X11/Xaw/AsciiText.h>
#endif

#define SEL_FILE_CANCEL		-1
#define SEL_FILE_OK		0
#define SEL_FILE_NULL		1
#define SEL_FILE_TEXT		2

#define SF_DO_SCROLL		1
#define SF_DO_NOT_SCROLL	0

typedef struct
{
    int		statDone;
    char	*real;
    char	*shown;
} SFEntry;

typedef struct
{
    char	*dir;
    char	*path;
    SFEntry	*entries;
    int		nEntries;
    int		vOrigin;
    int		nChars;
    int		hOrigin;
    int		changed;
    int		beginSelection;
    int		endSelection;
    time_t	mtime;
} SFDir;

static char	SFstartDir[MAXPATHL],
		SFcurrentPath[MAXPATHL],
		SFcurrentDir[MAXPATHL];

static Widget	selFile,
		selFileField,
		selFileForm,
		selFileHScroll,
		selFileHScrolls[3],
		selFileLists[3],
		selFileOK,
		selFileCancel,
		selFilePrompt,
		selFileVScrolls[3];

static Display	*SFdisplay;

static int	SFcharWidth, SFcharAscent, SFcharHeight;

static SFDir	*SFdirs = NULL;

static int	SFdirEnd;
static int	SFdirPtr;

static Pixel	SFfore, SFback;

static Atom	SFwmDeleteWindow;

static XSegment SFsegs[2], SFcompletionSegs[2];

static XawTextPosition SFtextPos;

static int	SFupperX, SFlowerY, SFupperY;

static int	SFtextX, SFtextYoffset;

static int	SFentryWidth, SFentryHeight;

static int	SFlineToTextH = 3;
static int	SFlineToTextV = 3;

static int	SFbesideText = 3;
static int	SFaboveAndBelowText = 2;

static int	SFcharsPerEntry = 15;

static int	SFlistSize = 10;

static int	SFcurrentInvert[3] = { -1, -1, -1 };

static int	SFworkProcAdded = 0;

static XtAppContext SFapp;

static int	SFpathScrollWidth, SFvScrollHeight, SFhScrollWidth;

#ifdef FEAT_XFONTSET
static char	SFtextBuffer[MAXPATHL*sizeof(wchar_t)];
#else
static char	SFtextBuffer[MAXPATHL];
#endif

static int	SFbuttonPressed = 0;

static XtIntervalId SFdirModTimerId;

static int	(*SFfunc)();

static int	SFstatus = SEL_FILE_NULL;

///////////////// forward declare static functions

static void SFsetText(char *path);
static void SFtextChanged(void);
static int SFgetDir(SFDir *dir);
static void SFdrawLists(int doScroll);
static void SFdrawList(int n, int doScroll);
static void SFclearList(int n, int doScroll);
static char SFstatChar(stat_T *statBuf);
static void SFmotionList(Widget w, XtPointer np, XMotionEvent *event, Boolean *cont);
static void SFvSliderMovedCallback(Widget w, int n, int nw);
static Boolean SFworkProc(void *);
static int SFcompareEntries(const void *p, const void *q);

////////////////// xstat.h

#ifndef S_IXUSR
# define S_IXUSR 0100
#endif
#ifndef S_IXGRP
# define S_IXGRP 0010
#endif
#ifndef S_IXOTH
# define S_IXOTH 0001
#endif

#define S_ISXXX(m) ((m) & (S_IXUSR | S_IXGRP | S_IXOTH))

////////////////// Path.c

#include <pwd.h>

typedef struct
{
    char	*name;
    char	*dir;
} SFLogin;

static int	SFdoNotTouchDirPtr = 0;

static int	SFdoNotTouchVorigin = 0;

static SFDir	SFrootDir, SFhomeDir;

static SFLogin	*SFlogins;

static int	SFtwiddle = 0;

    static int
SFchdir(char *path)
{
    int		result;

    result = 0;

    if (strcmp(path, SFcurrentDir))
    {
	result = mch_chdir(path);
	if (!result)
	    (void) strcpy(SFcurrentDir, path);
    }

    return result;
}

    static void
SFfree(int i)
{
    SFDir	*dir;
    int		j;

    dir = &(SFdirs[i]);

    for (j = dir->nEntries - 1; j >= 0; j--)
    {
	if (dir->entries[j].shown != dir->entries[j].real)
	    XtFree(dir->entries[j].shown);
	XtFree(dir->entries[j].real);
    }

    XtFree((char *)dir->entries);
    XtFree(dir->dir);

    dir->dir = NULL;
}

    static void
SFstrdup(char **s1, char *s2)
{
    *s1 = strcpy(XtMalloc((unsigned)(strlen(s2) + 1)), s2);
}

    static void
SFunreadableDir(SFDir *dir)
{
    char	*cannotOpen = _("<cannot open> ");

    dir->entries = (SFEntry *) XtMalloc(sizeof(SFEntry));
    dir->entries[0].statDone = 1;
    SFstrdup(&dir->entries[0].real, cannotOpen);
    dir->entries[0].shown = dir->entries[0].real;
    dir->nEntries = 1;
    dir->nChars = strlen(cannotOpen);
}

    static void
SFreplaceText(SFDir *dir, char *str)
{
    int	len;

    *(dir->path) = 0;
    len = strlen(str);
    if (str[len - 1] == '/')
	(void) strcat(SFcurrentPath, str);
    else
	(void) strncat(SFcurrentPath, str, len - 1);
    if (strncmp(SFcurrentPath, SFstartDir, strlen(SFstartDir)))
	SFsetText(SFcurrentPath);
    else
	SFsetText(&(SFcurrentPath[strlen(SFstartDir)]));

    SFtextChanged();
}

    static void
SFexpand(char *str)
{
    int		len;
    int		cmp;
    char	*name, *growing;
    SFDir	*dir;
    SFEntry	*entry, *max;

    len = strlen(str);

    dir = &(SFdirs[SFdirEnd - 1]);

    if (dir->beginSelection == -1)
    {
	SFstrdup(&str, str);
	SFreplaceText(dir, str);
	XtFree(str);
	return;
    }
    else if (dir->beginSelection == dir->endSelection)
    {
	SFreplaceText(dir, dir->entries[dir->beginSelection].shown);
	return;
    }

    max = &(dir->entries[dir->endSelection + 1]);

    name = dir->entries[dir->beginSelection].shown;
    SFstrdup(&growing, name);

    cmp = 0;
    while (!cmp)
    {
	entry = &(dir->entries[dir->beginSelection]);
	while (entry < max)
	{
	    if ((cmp = strncmp(growing, entry->shown, len)))
		break;
	    entry++;
	}
	len++;
    }

    /*
     * SFreplaceText() expects filename
     */
    growing[len - 2] = ' ';

    growing[len - 1] = 0;
    SFreplaceText(dir, growing);
    XtFree(growing);
}

    static int
SFfindFile(SFDir *dir, char *str)
{
    int		i, last, max;
    char	*name, save;
    SFEntry	*entries;
    int		len;
    int		begin, end;
    int		result;

    len = strlen(str);

    if (str[len - 1] == ' ')
    {
	SFexpand(str);
	return 1;
    }
    else if (str[len - 1] == '/')
	len--;

    max = dir->nEntries;

    entries = dir->entries;

    i = 0;
    while (i < max)
    {
	name = entries[i].shown;
	last = strlen(name) - 1;
	save = name[last];
	name[last] = 0;

	result = strncmp(str, name, len);

	name[last] = save;
	if (result <= 0)
	    break;
	i++;
    }
    begin = i;
    while (i < max)
    {
	name = entries[i].shown;
	last = strlen(name) - 1;
	save = name[last];
	name[last] = 0;

	result = strncmp(str, name, len);

	name[last] = save;
	if (result)
	    break;
	i++;
    }
    end = i;

    if (begin != end)
    {
	if ((dir->beginSelection != begin) || (dir->endSelection != end - 1))
	{
	    dir->changed = 1;
	    dir->beginSelection = begin;
	    if (str[strlen(str) - 1] == '/')
		dir->endSelection = begin;
	    else
		dir->endSelection = end - 1;
	}
    }
    else if (dir->beginSelection != -1)
    {
	dir->changed = 1;
	dir->beginSelection = -1;
	dir->endSelection = -1;
    }

    if (SFdoNotTouchVorigin
	    || ((begin > dir->vOrigin) && (end < dir->vOrigin + SFlistSize)))
    {
	SFdoNotTouchVorigin = 0;
	return 0;
    }

    i = begin - 1;
    if (i > max - SFlistSize)
	i = max - SFlistSize;
    if (i < 0)
	i = 0;

    if (dir->vOrigin != i)
    {
	dir->vOrigin = i;
	dir->changed = 1;
    }

    return 0;
}

    static void
SFunselect(void)
{
    SFDir	*dir;

    dir = &(SFdirs[SFdirEnd - 1]);
    if (dir->beginSelection != -1)
	dir->changed = 1;
    dir->beginSelection = -1;
    dir->endSelection = -1;
}

    static int
SFcompareLogins(const void *p, const void *q)
{
    return strcmp(((SFLogin *)p)->name, ((SFLogin *)q)->name);
}

    static void
SFgetHomeDirs(void)
{
    struct	passwd	*pw;
    int		Alloc;
    int		i;
    SFEntry	*entries = NULL;
    int		len;
    int		maxChars;

    Alloc = 1;
    i = 1;
    entries = (SFEntry *)XtMalloc(sizeof(SFEntry));
    SFlogins = (SFLogin *)XtMalloc(sizeof(SFLogin));
    entries[0].real = XtMalloc(3);
    (void) strcpy(entries[0].real, "~");
    entries[0].shown = entries[0].real;
    entries[0].statDone = 1;
    SFlogins[0].name = "";
    pw = getpwuid((int) getuid());
    SFstrdup(&SFlogins[0].dir, pw ? pw->pw_dir : "/");
    maxChars = 0;

    (void) setpwent();

    while ((pw = getpwent()) && (*(pw->pw_name)))
    {
	if (i >= Alloc)
	{
	    Alloc *= 2;
	    entries = (SFEntry *) XtRealloc((char *)entries,
					 (unsigned)(Alloc * sizeof(SFEntry)));
	    SFlogins = (SFLogin *) XtRealloc((char *)SFlogins,
					 (unsigned)(Alloc * sizeof(SFLogin)));
	}
	len = strlen(pw->pw_name);
	entries[i].real = XtMalloc((unsigned)(len + 3));
	(void) strcat(strcpy(entries[i].real, "~"), pw->pw_name);
	entries[i].shown = entries[i].real;
	entries[i].statDone = 1;
	if (len > maxChars)
	    maxChars = len;
	SFstrdup(&SFlogins[i].name, pw->pw_name);
	SFstrdup(&SFlogins[i].dir, pw->pw_dir);
	i++;
    }

    SFhomeDir.dir		= XtMalloc(1);
    SFhomeDir.dir[0]		= 0;
    SFhomeDir.path		= SFcurrentPath;
    SFhomeDir.entries		= entries;
    SFhomeDir.nEntries		= i;
    SFhomeDir.vOrigin		= 0;	// :-)
    SFhomeDir.nChars		= maxChars + 2;
    SFhomeDir.hOrigin		= 0;
    SFhomeDir.changed		= 1;
    SFhomeDir.beginSelection	= -1;
    SFhomeDir.endSelection	= -1;

    qsort((char *)entries, (size_t)i, sizeof(SFEntry), SFcompareEntries);
    qsort((char *)SFlogins, (size_t)i, sizeof(SFLogin), SFcompareLogins);

    for (i--; i >= 0; i--)
	(void)strcat(entries[i].real, "/");
}

    static int
SFfindHomeDir(char *begin, char *end)
{
    char	save;
    char	*theRest;
    int	i;

    save = *end;
    *end = 0;

    for (i = SFhomeDir.nEntries - 1; i >= 0; i--)
    {
	if (!strcmp(SFhomeDir.entries[i].real, begin))
	{
	    *end = save;
	    SFstrdup(&theRest, end);
	    (void) strcat(strcat(strcpy(SFcurrentPath,
					SFlogins[i].dir), "/"), theRest);
	    XtFree(theRest);
	    SFsetText(SFcurrentPath);
	    SFtextChanged();
	    return 1;
	}
    }

    *end = save;

    return 0;
}

    static void
SFupdatePath(void)
{
    static int	Alloc;
    static int	wasTwiddle = 0;
    char	*begin, *end;
    int		i, j;
    int		prevChange;
    int		SFdirPtrSave, SFdirEndSave;
    SFDir	*dir;

    if (!SFdirs)
    {
	SFdirs = (SFDir *) XtMalloc((Alloc = 10) * sizeof(SFDir));
	dir = &(SFdirs[0]);
	SFstrdup(&dir->dir, "/");
	(void) SFchdir("/");
	(void) SFgetDir(dir);
	for (j = 1; j < Alloc; j++)
	    SFdirs[j].dir = NULL;
	dir->path = SFcurrentPath + 1;
	dir->vOrigin = 0;
	dir->hOrigin = 0;
	dir->changed = 1;
	dir->beginSelection = -1;
	dir->endSelection = -1;
	SFhomeDir.dir = NULL;
    }

    SFdirEndSave = SFdirEnd;
    SFdirEnd = 1;

    SFdirPtrSave = SFdirPtr;
    SFdirPtr = 0;

    begin = NULL;

    if (SFcurrentPath[0] == '~')
    {
	if (!SFtwiddle)
	{
	    SFtwiddle = 1;
	    dir = &(SFdirs[0]);
	    SFrootDir = *dir;
	    if (!SFhomeDir.dir)
		SFgetHomeDirs();
	    *dir = SFhomeDir;
	    dir->changed = 1;
	}
	end = SFcurrentPath;
	SFdoNotTouchDirPtr = 1;
	wasTwiddle = 1;
    }
    else
    {
	if (SFtwiddle)
	{
	    SFtwiddle = 0;
	    dir = &(SFdirs[0]);
	    *dir = SFrootDir;
	    dir->changed = 1;
	}
	end = SFcurrentPath + 1;
    }

    i = 0;

    prevChange = 0;

    while (*end)
    {
	while (*end++ == '/')
	    ;
	end--;
	begin = end;
	while ((*end) && (*end++ != '/'))
	    ;
	if ((end - SFcurrentPath <= SFtextPos) && (*(end - 1) == '/'))
	{
	    SFdirPtr = i - 1;
	    if (SFdirPtr < 0)
		SFdirPtr = 0;
	}
	if (*begin)
	{
	    if (*(end - 1) == '/')
	    {
		char save = *end;

		if (SFtwiddle)
		{
		    if (SFfindHomeDir(begin, end))
			return;
		}
		*end = 0;
		i++;
		SFdirEnd++;
		if (i >= Alloc)
		{
		    SFdirs = (SFDir *) XtRealloc((char *) SFdirs,
				    (unsigned)((Alloc *= 2) * sizeof(SFDir)));
		    for (j = Alloc / 2; j < Alloc; j++)
			SFdirs[j].dir = NULL;
		}
		dir = &(SFdirs[i]);
		if ((!(dir->dir)) || prevChange || strcmp(dir->dir, begin))
		{
		    if (dir->dir)
			SFfree(i);
		    prevChange = 1;
		    SFstrdup(&dir->dir, begin);
		    dir->path = end;
		    dir->vOrigin = 0;
		    dir->hOrigin = 0;
		    dir->changed = 1;
		    dir->beginSelection = -1;
		    dir->endSelection = -1;
		    (void)SFfindFile(dir - 1, begin);
		    if (SFchdir(SFcurrentPath) || SFgetDir(dir))
		    {
			SFunreadableDir(dir);
			break;
		    }
		}
		*end = save;
		if (!save)
		    SFunselect();
	    }
	    else
	    {
		if (SFfindFile(&(SFdirs[SFdirEnd-1]), begin))
		    return;
	    }
	}
	else
	    SFunselect();
    }

    if ((end == SFcurrentPath + 1) && (!SFtwiddle))
	SFunselect();

    for (i = SFdirEnd; i < Alloc; i++)
	if (SFdirs[i].dir)
	    SFfree(i);

    if (SFdoNotTouchDirPtr)
    {
	if (wasTwiddle)
	{
	    wasTwiddle = 0;
	    SFdirPtr = SFdirEnd - 2;
	    if (SFdirPtr < 0)
		SFdirPtr = 0;
	}
	else
	    SFdirPtr = SFdirPtrSave;
	SFdoNotTouchDirPtr = 0;
    }

    if ((SFdirPtr != SFdirPtrSave) || (SFdirEnd != SFdirEndSave))
    {
#ifdef FEAT_GUI_NEXTAW
	XawScrollbarSetThumb( selFileHScroll,
		(float) (((double) SFdirPtr) / SFdirEnd),
		(float) (((double) ((SFdirEnd < 3) ? SFdirEnd : 3)) /
			 SFdirEnd));
#else
	vim_XawScrollbarSetThumb( selFileHScroll,
		(float) (((double) SFdirPtr) / SFdirEnd),
		(float) (((double) ((SFdirEnd < 3) ? SFdirEnd : 3)) /
			 SFdirEnd),
		(double)SFdirEnd);
#endif
    }

    if (SFdirPtr != SFdirPtrSave)
	SFdrawLists(SF_DO_SCROLL);
    else
	for (i = 0; i < 3; i++)
	{
	    if (SFdirPtr + i < SFdirEnd)
	    {
		if (SFdirs[SFdirPtr + i].changed)
		{
		    SFdirs[SFdirPtr + i].changed = 0;
		    SFdrawList(i, SF_DO_SCROLL);
		}
	    }
	    else
		SFclearList(i, SF_DO_SCROLL);
	}
}

#ifdef XtNinternational
    static int
WcsLen(wchar_t *p)
{
    int i = 0;
    while (*p++ != 0)
	i++;
    return i;
}
#endif

    static void
SFsetText(char *path)
{
    XawTextBlock	text;

    text.firstPos = 0;
    text.length = strlen(path);
    text.ptr = path;
    text.format = FMT8BIT;

#ifdef XtNinternational
    if ((unsigned long)_XawTextFormat((TextWidget)selFileField) == XawFmtWide)
    {
	XawTextReplace(selFileField, (XawTextPosition)0,
				    (XawTextPosition)WcsLen((wchar_t *)&SFtextBuffer[0]), &text);
	XawTextSetInsertionPoint(selFileField,
					   (XawTextPosition)WcsLen((wchar_t *)&SFtextBuffer[0]));
    }
    else
    {
	XawTextReplace(selFileField, (XawTextPosition)0,
				    (XawTextPosition)strlen(SFtextBuffer), &text);
	XawTextSetInsertionPoint(selFileField,
					   (XawTextPosition)strlen(SFtextBuffer));
    }
#else
    XawTextReplace(selFileField, (XawTextPosition)0,
				(XawTextPosition)strlen(SFtextBuffer), &text);
    XawTextSetInsertionPoint(selFileField,
				       (XawTextPosition)strlen(SFtextBuffer));
#endif
}

    static void
SFbuttonPressList(
    Widget	w UNUSED,
    XtPointer	np UNUSED,
    XEvent	*event UNUSED,
    Boolean	*cont UNUSED)
{
    SFbuttonPressed = 1;
}

    static void
SFbuttonReleaseList(
    Widget	w UNUSED,
    XtPointer	np,
    XEvent	*event UNUSED,
    Boolean	*cont UNUSED)
{
    long	n = (long)np;
    SFDir	*dir;

    SFbuttonPressed = 0;

    if (SFcurrentInvert[n] != -1)
    {
	if (n < 2)
	    SFdoNotTouchDirPtr = 1;
	SFdoNotTouchVorigin = 1;
	dir = &(SFdirs[SFdirPtr + n]);
	SFreplaceText(dir,
		       dir->entries[dir->vOrigin + SFcurrentInvert[n]].shown);
	SFmotionList(w, (XtPointer)(long)n, (XMotionEvent *)event, 0);
    }
}

    static int
SFcheckDir(int n, SFDir *dir)
{
    stat_T	statBuf;
    int		i;

    if ((!mch_stat(".", &statBuf)) && (statBuf.st_mtime != dir->mtime))
    {
	/*
	 * If the pointer is currently in the window that we are about
	 * to update, we must warp it to prevent the user from
	 * accidentally selecting the wrong file.
	 */
	if (SFcurrentInvert[n] != -1)
	{
	    XWarpPointer(
		    SFdisplay,
		    None,
		    XtWindow(selFileLists[n]),
		    0,
		    0,
		    0,
		    0,
		    0,
		    0);
	}

	for (i = dir->nEntries - 1; i >= 0; i--)
	{
	    if (dir->entries[i].shown != dir->entries[i].real)
		XtFree(dir->entries[i].shown);
	    XtFree(dir->entries[i].real);
	}
	XtFree((char *) dir->entries);
	if (SFgetDir(dir))
	    SFunreadableDir(dir);
	if (dir->vOrigin > dir->nEntries - SFlistSize)
	    dir->vOrigin = dir->nEntries - SFlistSize;
	if (dir->vOrigin < 0)
	    dir->vOrigin = 0;
	if (dir->hOrigin > dir->nChars - SFcharsPerEntry)
	    dir->hOrigin = dir->nChars - SFcharsPerEntry;
	if (dir->hOrigin < 0)
	    dir->hOrigin = 0;
	dir->beginSelection = -1;
	dir->endSelection = -1;
	SFdoNotTouchVorigin = 1;
	if ((dir + 1)->dir)
	    (void) SFfindFile(dir, (dir + 1)->dir);
	else
	    (void) SFfindFile(dir, dir->path);

	if (!SFworkProcAdded)
	{
	    (void) XtAppAddWorkProc(SFapp, (XtWorkProc)SFworkProc, NULL);
	    SFworkProcAdded = 1;
	}
	return 1;
    }
    return 0;
}

    static int
SFcheckFiles(SFDir *dir)
{
    int		from, to;
    int		result;
    char	oldc, newc;
    int		i;
    char	*str;
    int		last;
    stat_T	statBuf;

    result = 0;

    from = dir->vOrigin;
    to = dir->vOrigin + SFlistSize;
    if (to > dir->nEntries)
	to = dir->nEntries;

    for (i = from; i < to; i++)
    {
	str = dir->entries[i].real;
	last = strlen(str) - 1;
	oldc = str[last];
	str[last] = 0;
	if (mch_stat(str, &statBuf))
	    newc = ' ';
	else
	    newc = SFstatChar(&statBuf);
	str[last] = newc;
	if (newc != oldc)
	    result = 1;
    }

    return result;
}

    static void
SFdirModTimer(XtPointer cl UNUSED, XtIntervalId *id UNUSED)
{
    static int		n = -1;
    static int		f = 0;
    char		save;
    SFDir		*dir;

    if ((!SFtwiddle) && (SFdirPtr < SFdirEnd))
    {
	n++;
	if ((n > 2) || (SFdirPtr + n >= SFdirEnd))
	{
	    n = 0;
	    f++;
	    if ((f > 2) || (SFdirPtr + f >= SFdirEnd))
		f = 0;
	}
	dir = &(SFdirs[SFdirPtr + n]);
	save = *(dir->path);
	*(dir->path) = 0;
	if (SFchdir(SFcurrentPath))
	{
	    *(dir->path) = save;

	    /*
	     * force a re-read
	     */
	    *(dir->dir) = 0;

	    SFupdatePath();
	}
	else
	{
	    *(dir->path) = save;
	    if (SFcheckDir(n, dir) || ((f == n) && SFcheckFiles(dir)))
		SFdrawList(n, SF_DO_SCROLL);
	}
    }

    SFdirModTimerId = XtAppAddTimeOut(SFapp, (unsigned long) 1000,
	    SFdirModTimer, (XtPointer) NULL);
}

// Return a single character describing what kind of file STATBUF is.

    static char
SFstatChar(stat_T *statBuf)
{
    if (S_ISDIR (statBuf->st_mode))
	return '/';
    if (S_ISREG (statBuf->st_mode))
	return S_ISXXX (statBuf->st_mode) ? '*' : ' ';
#ifdef S_ISSOCK
    if (S_ISSOCK (statBuf->st_mode))
	return '=';
#endif // S_ISSOCK
    return ' ';
}

////////////////// Draw.c

#ifdef FEAT_GUI_NEXTAW
# include <X11/neXtaw/Cardinals.h>
#else
# include <X11/Xaw/Cardinals.h>
#endif

#ifdef FEAT_XFONTSET
# define SF_DEFAULT_FONT "-misc-fixed-medium-r-normal--14-*"
#else
# define SF_DEFAULT_FONT "9x15"
#endif

#ifdef ABS
# undef ABS
#endif
#define ABS(x) (((x) < 0) ? (-(x)) : (x))

typedef struct
{
    char *fontname;
} TextData;

static GC SFlineGC, SFscrollGC, SFinvertGC, SFtextGC;

static XtResource textResources[] =
{
#ifdef FEAT_XFONTSET
	{XtNfontSet, XtCFontSet, XtRString, sizeof (char *),
		XtOffsetOf(TextData, fontname), XtRString, SF_DEFAULT_FONT},
#else
	{XtNfont, XtCFont, XtRString, sizeof (char *),
		XtOffsetOf(TextData, fontname), XtRString, SF_DEFAULT_FONT},
#endif
};

#ifdef FEAT_XFONTSET
static XFontSet SFfont;
#else
static XFontStruct *SFfont;
#endif

static int SFcurrentListY;

static XtIntervalId SFscrollTimerId;

    static void
SFinitFont(void)
{
    TextData	*data;
#ifdef FEAT_XFONTSET
    XFontSetExtents *extents;
    char **missing, *def_str;
    int  num_missing;
#endif

    data = XtNew(TextData);

    XtGetApplicationResources(selFileForm, (XtPointer) data, textResources,
	    XtNumber(textResources), (Arg *) NULL, ZERO);

#ifdef FEAT_XFONTSET
    SFfont = XCreateFontSet(SFdisplay, data->fontname,
			    &missing, &num_missing, &def_str);
#else
    SFfont = XLoadQueryFont(SFdisplay, data->fontname);
#endif
    if (!SFfont)
    {
#ifdef FEAT_XFONTSET
	SFfont = XCreateFontSet(SFdisplay, SF_DEFAULT_FONT,
					    &missing, &num_missing, &def_str);
#else
	SFfont = XLoadQueryFont(SFdisplay, SF_DEFAULT_FONT);
#endif
	if (!SFfont)
	{
	    semsg(_("E616: vim_SelFile: can't get font %s"), SF_DEFAULT_FONT);
	    SFstatus = SEL_FILE_CANCEL;
	    return;
	}
    }

#ifdef FEAT_XFONTSET
    extents = XExtentsOfFontSet(SFfont);
    SFcharWidth = extents->max_logical_extent.width;
    SFcharAscent = -extents->max_logical_extent.y;
    SFcharHeight = extents->max_logical_extent.height;
#else
    SFcharWidth = (SFfont->max_bounds.width + SFfont->min_bounds.width) / 2;
    SFcharAscent = SFfont->max_bounds.ascent;
    SFcharHeight = SFcharAscent + SFfont->max_bounds.descent;
#endif
}

    static void
SFcreateGC(void)
{
    XGCValues	gcValues;
    XRectangle	rectangles[1];

    gcValues.foreground = SFfore;

    SFlineGC = XtGetGC(
	    selFileLists[0],
	    (XtGCMask)GCForeground,
	    &gcValues);

    SFscrollGC = XtGetGC(
	    selFileLists[0],
	    (XtGCMask)0,
	    &gcValues);

    gcValues.function = GXxor;
    gcValues.foreground = SFfore ^ SFback;
    gcValues.background = SFfore ^ SFback;

    SFinvertGC = XtGetGC(
	    selFileLists[0],
	    (XtGCMask)GCFunction | GCForeground | GCBackground,
	    &gcValues);

    gcValues.foreground = SFfore;
    gcValues.background = SFback;
#ifndef FEAT_XFONTSET
    gcValues.font = SFfont->fid;
#endif

    SFtextGC = XCreateGC(
	    SFdisplay,
	    XtWindow(selFileLists[0]),
#ifdef FEAT_XFONTSET
	    (unsigned long)GCForeground | GCBackground,
#else
	    (unsigned long)GCForeground | GCBackground | GCFont,
#endif
	    &gcValues);

    rectangles[0].x = SFlineToTextH + SFbesideText;
    rectangles[0].y = 0;
    rectangles[0].width = SFcharsPerEntry * SFcharWidth;
    rectangles[0].height = SFupperY + 1;

    XSetClipRectangles(
	    SFdisplay,
	    SFtextGC,
	    0,
	    0,
	    rectangles,
	    1,
	    Unsorted);
}

    static void
SFclearList(int n, int doScroll)
{
    SFDir	*dir;

    SFcurrentInvert[n] = -1;

    XClearWindow(SFdisplay, XtWindow(selFileLists[n]));

    XDrawSegments(SFdisplay, XtWindow(selFileLists[n]), SFlineGC, SFsegs, 2);

    if (doScroll)
    {
	dir = &(SFdirs[SFdirPtr + n]);

	if ((SFdirPtr + n < SFdirEnd) && dir->nEntries && dir->nChars)
	{
#ifdef FEAT_GUI_NEXTAW
	    XawScrollbarSetThumb(
		    selFileVScrolls[n],
		    (float) (((double) dir->vOrigin) /
			     dir->nEntries),
		    (float) (((double) ((dir->nEntries < SFlistSize)
					? dir->nEntries : SFlistSize)) /
			     dir->nEntries));
#else
	    vim_XawScrollbarSetThumb(
		    selFileVScrolls[n],
		    (float) (((double) dir->vOrigin) /
			     dir->nEntries),
		    (float) (((double) ((dir->nEntries < SFlistSize)
					? dir->nEntries : SFlistSize)) /
			     dir->nEntries),
		    (double)dir->nEntries);
#endif

#ifdef FEAT_GUI_NEXTAW
	    XawScrollbarSetThumb(
		    selFileHScrolls[n],
		    (float) (((double) dir->hOrigin) / dir->nChars),
		    (float) (((double) ((dir->nChars <
					 SFcharsPerEntry) ? dir->nChars :
					SFcharsPerEntry)) / dir->nChars));
#else
	    vim_XawScrollbarSetThumb(
		    selFileHScrolls[n],
		    (float) (((double) dir->hOrigin) / dir->nChars),
		    (float) (((double) ((dir->nChars <
					 SFcharsPerEntry) ? dir->nChars :
					SFcharsPerEntry)) / dir->nChars),
		    (double)dir->nChars);
#endif
	}
	else
	{
#ifdef FEAT_GUI_NEXTAW
	    XawScrollbarSetThumb(selFileVScrolls[n], (float) 0.0,
		    (float) 1.0);
#else
	    vim_XawScrollbarSetThumb(selFileVScrolls[n], (float) 0.0,
		    (float) 1.0, 1.0);
#endif
#ifdef FEAT_GUI_NEXTAW
	    XawScrollbarSetThumb(selFileHScrolls[n], (float) 0.0,
		    (float) 1.0);
#else
	    vim_XawScrollbarSetThumb(selFileHScrolls[n], (float) 0.0,
		    (float) 1.0, 1.0);
#endif
	}
    }
}

    static void
SFdeleteEntry(SFDir *dir, SFEntry *entry)
{
    SFEntry	*e;
    SFEntry	*end;
    int		n;
    int		idx;

    idx = entry - dir->entries;

    if (idx < dir->beginSelection)
	dir->beginSelection--;
    if (idx <= dir->endSelection)
	dir->endSelection--;
    if (dir->beginSelection > dir->endSelection)
	dir->beginSelection = dir->endSelection = -1;

    if (idx < dir->vOrigin)
	dir->vOrigin--;

    XtFree(entry->real);

    end = &(dir->entries[dir->nEntries - 1]);

    for (e = entry; e < end; e++)
	*e = *(e + 1);

    if (!(--dir->nEntries))
	return;

    n = dir - &(SFdirs[SFdirPtr]);
    if ((n < 0) || (n > 2))
	return;

#ifdef FEAT_GUI_NEXTAW
    XawScrollbarSetThumb(
	    selFileVScrolls[n],
	    (float) (((double) dir->vOrigin) / dir->nEntries),
	    (float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries));
#else
    vim_XawScrollbarSetThumb(
	    selFileVScrolls[n],
	    (float) (((double) dir->vOrigin) / dir->nEntries),
	    (float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries),
	    (double)dir->nEntries);
#endif
}

    static void
SFwriteStatChar(
    char	*name,
    int		last,
    stat_T	*statBuf)
{
    name[last] = SFstatChar(statBuf);
}

    static int
SFstatAndCheck(SFDir *dir, SFEntry *entry)
{
    stat_T	statBuf;
    char	save;
    int		last;

    /*
     * must be restored before returning
     */
    save = *(dir->path);
    *(dir->path) = 0;

    if (!SFchdir(SFcurrentPath))
    {
	last = strlen(entry->real) - 1;
	entry->real[last] = 0;
	entry->statDone = 1;
	if ((!mch_stat(entry->real, &statBuf))
#ifdef S_IFLNK
		|| (!mch_lstat(entry->real, &statBuf))
#endif
	   )
	{
	    if (SFfunc)
	    {
		char *shown;

		shown = NULL;
		if (SFfunc(entry->real, &shown, &statBuf))
		{
		    if (shown)
		    {
			int len;

			len = strlen(shown);
			entry->shown = XtMalloc((unsigned)(len + 2));
			(void) strcpy(entry->shown, shown);
			SFwriteStatChar(entry->shown, len, &statBuf);
			entry->shown[len + 1] = 0;
		    }
		}
		else
		{
		    SFdeleteEntry(dir, entry);

		    *(dir->path) = save;
		    return 1;
		}
	    }
	    SFwriteStatChar(entry->real, last, &statBuf);
	}
	else
	    entry->real[last] = ' ';
    }

    *(dir->path) = save;
    return 0;
}


    static void
SFdrawStrings(
    Window	w,
    SFDir	*dir,
    int		from,
    int		to)
{
    int		i;
    SFEntry	*entry;
    int		x;

    x = SFtextX - dir->hOrigin * SFcharWidth;

    if (dir->vOrigin + to >= dir->nEntries)
	to = dir->nEntries - dir->vOrigin - 1;
    for (i = from; i <= to; i++)
    {
	entry = &(dir->entries[dir->vOrigin + i]);
	if (!(entry->statDone))
	{
	    if (SFstatAndCheck(dir, entry))
	    {
		if (dir->vOrigin + to >= dir->nEntries)
		    to = dir->nEntries - dir->vOrigin - 1;
		i--;
		continue;
	    }
	}
#ifdef FEAT_XFONTSET
	XmbDrawImageString(
		SFdisplay,
		w,
		SFfont,
		SFtextGC,
		x,
		SFtextYoffset + i * SFentryHeight,
		entry->shown,
		strlen(entry->shown));
#else
	XDrawImageString(
		SFdisplay,
		w,
		SFtextGC,
		x,
		SFtextYoffset + i * SFentryHeight,
		entry->shown,
		strlen(entry->shown));
#endif
	if (dir->vOrigin + i == dir->beginSelection)
	{
	    XDrawLine(
		    SFdisplay,
		    w,
		    SFlineGC,
		    SFlineToTextH + 1,
		    SFlowerY + i * SFentryHeight,
		    SFlineToTextH + SFentryWidth - 2,
		    SFlowerY + i * SFentryHeight);
	}
	if ((dir->vOrigin + i >= dir->beginSelection) &&
		(dir->vOrigin + i <= dir->endSelection))
	{
	    SFcompletionSegs[0].y1 = SFcompletionSegs[1].y1 =
		SFlowerY + i * SFentryHeight;
	    SFcompletionSegs[0].y2 = SFcompletionSegs[1].y2 =
		SFlowerY + (i + 1) * SFentryHeight - 1;
	    XDrawSegments(
		    SFdisplay,
		    w,
		    SFlineGC,
		    SFcompletionSegs,
		    2);
	}
	if (dir->vOrigin + i == dir->endSelection)
	{
	    XDrawLine(
		    SFdisplay,
		    w,
		    SFlineGC,
		    SFlineToTextH + 1,
		    SFlowerY + (i + 1) * SFentryHeight - 1,
		    SFlineToTextH + SFentryWidth - 2,
		    SFlowerY + (i + 1) * SFentryHeight - 1);
	}
    }
}

    static void
SFdrawList(int n, int doScroll)
{
    SFDir	*dir;
    Window	w;

    SFclearList(n, doScroll);

    if (SFdirPtr + n < SFdirEnd)
    {
	dir = &(SFdirs[SFdirPtr + n]);
	w = XtWindow(selFileLists[n]);
#ifdef FEAT_XFONTSET
	XmbDrawImageString(
		SFdisplay,
		w,
		SFfont,
		SFtextGC,
		SFtextX - dir->hOrigin * SFcharWidth,
		SFlineToTextV + SFaboveAndBelowText + SFcharAscent,
		dir->dir,
		strlen(dir->dir));
#else
	XDrawImageString(
		SFdisplay,
		w,
		SFtextGC,
		SFtextX - dir->hOrigin * SFcharWidth,
		SFlineToTextV + SFaboveAndBelowText + SFcharAscent,
		dir->dir,
		strlen(dir->dir));
#endif
	SFdrawStrings(w, dir, 0, SFlistSize - 1);
    }
}

    static void
SFdrawLists(int doScroll)
{
    int	i;

    for (i = 0; i < 3; i++)
	SFdrawList(i, doScroll);
}

    static void
SFinvertEntry(int n)
{
    XFillRectangle(
	    SFdisplay,
	    XtWindow(selFileLists[n]),
	    SFinvertGC,
	    SFlineToTextH,
	    SFcurrentInvert[n] * SFentryHeight + SFlowerY,
	    SFentryWidth,
	    SFentryHeight);
}

    static unsigned long
SFscrollTimerInterval(void)
{
    static int	maxVal = 200;
    static int	varyDist = 50;
    static int	minDist = 50;
    int		t;
    int		dist;

    if (SFcurrentListY < SFlowerY)
	dist = SFlowerY - SFcurrentListY;
    else if (SFcurrentListY > SFupperY)
	dist = SFcurrentListY - SFupperY;
    else
	return (unsigned long) 1;

    t = maxVal - ((maxVal / varyDist) * (dist - minDist));

    if (t < 1)
	t = 1;

    if (t > maxVal)
	t = maxVal;

    return (unsigned long)t;
}

    static void
SFscrollTimer(XtPointer p, XtIntervalId *id UNUSED)
{
    SFDir	*dir;
    int		save;
    int		n;

    n = (long)p;

    dir = &(SFdirs[SFdirPtr + n]);
    save = dir->vOrigin;

    if (SFcurrentListY < SFlowerY)
    {
	if (dir->vOrigin > 0)
	    SFvSliderMovedCallback(selFileVScrolls[n], n, dir->vOrigin - 1);
    }
    else if (SFcurrentListY > SFupperY)
    {
	if (dir->vOrigin < dir->nEntries - SFlistSize)
	    SFvSliderMovedCallback(selFileVScrolls[n], n, dir->vOrigin + 1);
    }

    if (dir->vOrigin != save)
    {
	if (dir->nEntries)
	{
#ifdef FEAT_GUI_NEXTAW
	    XawScrollbarSetThumb(
		    selFileVScrolls[n],
		    (float) (((double) dir->vOrigin) / dir->nEntries),
		    (float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries));
#else
	    vim_XawScrollbarSetThumb(
		    selFileVScrolls[n],
		    (float) (((double) dir->vOrigin) / dir->nEntries),
		    (float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries),
		    (double)dir->nEntries);
#endif
	}
    }

    if (SFbuttonPressed)
	SFscrollTimerId = XtAppAddTimeOut(SFapp,
		       SFscrollTimerInterval(), SFscrollTimer,
		       (XtPointer)(long_u)n);
}

    static int
SFnewInvertEntry(int n, XMotionEvent *event)
{
    int			x, y;
    int			nw;
    static int		SFscrollTimerAdded = 0;

    x = event->x;
    y = event->y;

    if (SFdirPtr + n >= SFdirEnd)
	return -1;

    if ((x >= 0) && (x <= SFupperX) && (y >= SFlowerY) && (y <= SFupperY))
    {
	SFDir *dir = &(SFdirs[SFdirPtr + n]);

	if (SFscrollTimerAdded)
	{
	    SFscrollTimerAdded = 0;
	    XtRemoveTimeOut(SFscrollTimerId);
	}

	nw = (y - SFlowerY) / SFentryHeight;
	if (dir->vOrigin + nw >= dir->nEntries)
	    return -1;
	return nw;
    }
    else
    {
	if (SFbuttonPressed)
	{
	    SFcurrentListY = y;
	    if (!SFscrollTimerAdded)
	    {
		SFscrollTimerAdded = 1;
		SFscrollTimerId = XtAppAddTimeOut(SFapp,
			SFscrollTimerInterval(), SFscrollTimer,
			(XtPointer)(long_u)n);
	    }
	}
	return -1;
    }
}

    static void
SFenterList(
    Widget	w UNUSED,
    XtPointer	np,
    XEvent	*event,
    Boolean	*cont UNUSED)
{
    long	n = (long)np;
    int		nw;

    // sanity
    if (SFcurrentInvert[n] != -1)
    {
	SFinvertEntry(n);
	SFcurrentInvert[n] = -1;
    }

    nw = SFnewInvertEntry(n, (XMotionEvent *) event);
    if (nw != -1)
    {
	SFcurrentInvert[n] = nw;
	SFinvertEntry(n);
    }
}

    static void
SFleaveList(
    Widget	w UNUSED,
    XtPointer	np,
    XEvent	*event UNUSED,
    Boolean	*cont UNUSED)
{
    long	n = (long)np;

    if (SFcurrentInvert[n] != -1)
    {
	SFinvertEntry(n);
	SFcurrentInvert[n] = -1;
    }
}

    static void
SFmotionList(
    Widget	    w UNUSED,
    XtPointer	    np,
    XMotionEvent    *event UNUSED,
    Boolean	    *cont UNUSED)
{
    long	n = (long)np;
    int		nw;

    nw = SFnewInvertEntry(n, event);

    if (nw != SFcurrentInvert[n])
    {
	if (SFcurrentInvert[n] != -1)
	    SFinvertEntry(n);
	SFcurrentInvert[n] = nw;
	if (nw != -1)
	    SFinvertEntry(n);
    }
}

    static void
SFvFloatSliderMovedCallback(Widget w, XtPointer n, XtPointer fnew)
{
    int		nw;

    nw = (*(float *)fnew) * SFdirs[SFdirPtr + (int)(long)n].nEntries;
    SFvSliderMovedCallback(w, (int)(long)n, nw);
}

    static void
SFvSliderMovedCallback(Widget w UNUSED, int n, int nw)
{
    int		old;
    Window	win;
    SFDir	*dir;

    dir = &(SFdirs[SFdirPtr + n]);

    old = dir->vOrigin;
    dir->vOrigin = nw;

    if (old == nw)
	return;

    win = XtWindow(selFileLists[n]);

    if (ABS(nw - old) < SFlistSize)
    {
	if (nw > old)
	{
	    XCopyArea(
		    SFdisplay,
		    win,
		    win,
		    SFscrollGC,
		    SFlineToTextH,
		    SFlowerY + (nw - old) * SFentryHeight,
		    SFentryWidth + SFlineToTextH,
		    (SFlistSize - (nw - old)) * SFentryHeight,
		    SFlineToTextH,
		    SFlowerY);
	    XClearArea(
		    SFdisplay,
		    win,
		    SFlineToTextH,
		    SFlowerY + (SFlistSize - (nw - old)) *
		    SFentryHeight,
		    SFentryWidth + SFlineToTextH,
		    (nw - old) * SFentryHeight,
		    False);
	    SFdrawStrings(win, dir, SFlistSize - (nw - old),
		    SFlistSize - 1);
	}
	else
	{
	    XCopyArea(
		    SFdisplay,
		    win,
		    win,
		    SFscrollGC,
		    SFlineToTextH,
		    SFlowerY,
		    SFentryWidth + SFlineToTextH,
		    (SFlistSize - (old - nw)) * SFentryHeight,
		    SFlineToTextH,
		    SFlowerY + (old - nw) * SFentryHeight);
	    XClearArea(
		    SFdisplay,
		    win,
		    SFlineToTextH,
		    SFlowerY,
		    SFentryWidth + SFlineToTextH,
		    (old - nw) * SFentryHeight,
		    False);
	    SFdrawStrings(win, dir, 0, old - nw);
	}
    }
    else
    {
	XClearArea(
		SFdisplay,
		win,
		SFlineToTextH,
		SFlowerY,
		SFentryWidth + SFlineToTextH,
		SFlistSize * SFentryHeight,
		False);
	SFdrawStrings(win, dir, 0, SFlistSize - 1);
    }
}

    static void
SFvAreaSelectedCallback(Widget w, XtPointer n, XtPointer pnew)
{
    SFDir	*dir;
    int		nw = (int)(long)pnew;

    dir = &(SFdirs[SFdirPtr + (int)(long)n]);

#ifdef FEAT_GUI_NEXTAW
    if (nw < 0)
    {
	if (nw > -SFvScrollHeight)
	    nw = -1;
	else
	    nw = -SFlistSize;
    }
    else if (nw > 0)
    {
	if (nw < SFvScrollHeight)
	    nw = 1;
	else
	    nw = SFlistSize;
    }
#endif
    nw += dir->vOrigin;

    if (nw > dir->nEntries - SFlistSize)
	nw = dir->nEntries - SFlistSize;

    if (nw < 0)
	nw = 0;

    if (dir->nEntries)
    {
	float	f;

	f = ((double) nw) / dir->nEntries;

#ifdef FEAT_GUI_NEXTAW
	XawScrollbarSetThumb(
		w,
		f,
		(float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries));
#else
	vim_XawScrollbarSetThumb(
		w,
		f,
		(float) (((double) ((dir->nEntries < SFlistSize) ?
				dir->nEntries : SFlistSize)) / dir->nEntries),
		(double)dir->nEntries);
#endif
    }

    SFvSliderMovedCallback(w, (int)(long)n, nw);
}

    static void
SFhSliderMovedCallback(Widget w UNUSED, XtPointer n, XtPointer nw)
{
    SFDir	*dir;
    int	save;

    dir = &(SFdirs[SFdirPtr + (int)(long)n]);
    save = dir->hOrigin;
    dir->hOrigin = (*(float *)nw) * dir->nChars;
    if (dir->hOrigin == save)
	return;

    SFdrawList((int)(long)n, SF_DO_NOT_SCROLL);
}

    static void
SFhAreaSelectedCallback(Widget w, XtPointer n, XtPointer pnew)
{
    SFDir	*dir;
    int		nw = (int)(long)pnew;

    dir = &(SFdirs[SFdirPtr + (int)(long)n]);

#ifdef FEAT_GUI_NEXTAW
    if (nw < 0)
    {
	if (nw > -SFhScrollWidth)
	    nw = -1;
	else
	    nw = -SFcharsPerEntry;
    }
    else if (nw > 0)
    {
	if (nw < SFhScrollWidth)
	    nw = 1;
	else
	    nw = SFcharsPerEntry;
    }
#endif
    nw += dir->hOrigin;

    if (nw > dir->nChars - SFcharsPerEntry)
	nw = dir->nChars - SFcharsPerEntry;

    if (nw < 0)
	nw = 0;

    if (dir->nChars)
    {
	float	f;

	f = ((double) nw) / dir->nChars;

#ifdef FEAT_GUI_NEXTAW
	XawScrollbarSetThumb(
		w,
		f,
		(float) (((double) ((dir->nChars < SFcharsPerEntry) ?
			       dir->nChars : SFcharsPerEntry)) / dir->nChars));
#else
	vim_XawScrollbarSetThumb(
		w,
		f,
		(float) (((double) ((dir->nChars < SFcharsPerEntry) ?
			       dir->nChars : SFcharsPerEntry)) / dir->nChars),
		(double)dir->nChars);
#endif

	SFhSliderMovedCallback(w, n, (XtPointer)&f);
    }
}

    static void
SFpathSliderMovedCallback(
    Widget	w UNUSED,
    XtPointer	client_data UNUSED,
    XtPointer	nw)
{
    SFDir		*dir;
    int			n;
    XawTextPosition	pos;
    int			SFdirPtrSave;

    SFdirPtrSave = SFdirPtr;
    SFdirPtr = (*(float *)nw) * SFdirEnd;
    if (SFdirPtr == SFdirPtrSave)
	return;

    SFdrawLists(SF_DO_SCROLL);

    n = 2;
    while (SFdirPtr + n >= SFdirEnd)
	n--;

    dir = &(SFdirs[SFdirPtr + n]);

    pos = dir->path - SFcurrentPath;

    if (!strncmp(SFcurrentPath, SFstartDir, strlen(SFstartDir)))
    {
	pos -= strlen(SFstartDir);
	if (pos < 0)
	    pos = 0;
    }

    XawTextSetInsertionPoint(selFileField, pos);
}

    static void
SFpathAreaSelectedCallback(
    Widget	w,
    XtPointer	client_data UNUSED,
    XtPointer	pnew)
{
    int		nw = (int)(long)pnew;
    float	f;

#ifdef FEAT_GUI_NEXTAW
    if (nw < 0)
    {
	if (nw > -SFpathScrollWidth)
	    nw = -1;
	else
	    nw = -3;
    }
    else if (nw > 0)
    {
	if (nw < SFpathScrollWidth)
	    nw = 1;
	else
	    nw = 3;
    }
#endif
    nw += SFdirPtr;

    if (nw > SFdirEnd - 3)
	nw = SFdirEnd - 3;

    if (nw < 0)
	nw = 0;

    f = ((double) nw) / SFdirEnd;

#ifdef FEAT_GUI_NEXTAW
    XawScrollbarSetThumb(
	    w,
	    f,
	    (float) (((double) ((SFdirEnd < 3) ? SFdirEnd : 3)) / SFdirEnd));
#else
    vim_XawScrollbarSetThumb(
	    w,
	    f,
	    (float) (((double) ((SFdirEnd < 3) ? SFdirEnd : 3)) / SFdirEnd),
	    (double)SFdirEnd);
#endif

    SFpathSliderMovedCallback(w, (XtPointer) NULL, (XtPointer)&f);
}

    static Boolean
SFworkProc(void *arg UNUSED)
{
    SFDir	*dir;
    SFEntry	*entry;

    for (dir = &(SFdirs[SFdirEnd - 1]); dir >= SFdirs; dir--)
    {
	if (!(dir->nEntries))
	    continue;
	for (entry = &(dir->entries[dir->nEntries - 1]);
		entry >= dir->entries;
		entry--)
	{
	    if (!(entry->statDone))
	    {
		(void)SFstatAndCheck(dir, entry);
		return False;
	    }
	}
    }

    SFworkProcAdded = 0;

    return True;
}

////////////////// Dir.c

    static int
SFcompareEntries(const void *p, const void *q)
{
 return strcmp(((SFEntry *)p)->real, ((SFEntry *)q)->real);
}

    static int
SFgetDir(
    SFDir	*dir)
{
    SFEntry		*result = NULL;
    int			Alloc = 0;
    int			i;
    DIR			*dirp;
    struct dirent	*dp;
    char		*str;
    int			len;
    int			maxChars;
    stat_T		statBuf;

    maxChars = strlen(dir->dir) - 1;

    dir->entries = NULL;
    dir->nEntries = 0;
    dir->nChars = 0;

    result = NULL;
    i = 0;

    dirp = opendir(".");
    if (!dirp)
	return 1;

    (void)mch_stat(".", &statBuf);
    dir->mtime = statBuf.st_mtime;

    while ((dp = readdir(dirp)))
    {
	// Ignore "." and ".."
	if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
	    continue;
	if (i >= Alloc)
	{
	    Alloc = 2 * (Alloc + 1);
	    result = (SFEntry *) XtRealloc((char *) result,
		    (unsigned) (Alloc * sizeof(SFEntry)));
	}
	result[i].statDone = 0;
	str = dp->d_name;
	len = strlen(str);
	result[i].real = XtMalloc((unsigned)(len + 2));
	(void) strcat(strcpy(result[i].real, str), " ");
	if (len > maxChars)
	    maxChars = len;
	result[i].shown = result[i].real;
	i++;
    }

    qsort((char *) result, (size_t) i, sizeof(SFEntry), SFcompareEntries);

    dir->entries = result;
    dir->nEntries = i;
    dir->nChars = maxChars + 1;

    closedir(dirp);

    return 0;
}

////////////////// SFinternal.h

#include <sys/param.h>
#include <X11/cursorfont.h>
#include <X11/Composite.h>
#include <X11/Shell.h>
#ifdef FEAT_GUI_NEXTAW
# include <X11/neXtaw/Form.h>
# include <X11/neXtaw/Command.h>
# include <X11/neXtaw/Label.h>
#else
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Label.h>
#endif

static char *oneLineTextEditTranslations = "\
	<Key>Return:	redraw-display()\n\
	Ctrl<Key>M:	redraw-display()\n\
";

    static void
SFexposeList(
    Widget	w UNUSED,
    XtPointer	n,
    XEvent	*event,
    Boolean	*cont UNUSED)
{
    if ((event->type == NoExpose) || event->xexpose.count)
	return;

    SFdrawList((int)(long)n, SF_DO_NOT_SCROLL);
}

    static void
SFmodVerifyCallback(
    Widget		w UNUSED,
    XtPointer		client_data UNUSED,
    XEvent		*event,
    Boolean		*cont UNUSED)
{
    char	buf[2];

    if ((XLookupString(&(event->xkey), buf, 2, NULL, NULL) == 1) &&
	    ((*buf) == '\r'))
	SFstatus = SEL_FILE_OK;
    else
	SFstatus = SEL_FILE_TEXT;
}

    static void
SFokCallback(Widget w UNUSED, XtPointer cl UNUSED, XtPointer cd UNUSED)
{
    SFstatus = SEL_FILE_OK;
}

static XtCallbackRec SFokSelect[] =
{
    { SFokCallback, (XtPointer) NULL },
    { NULL, (XtPointer) NULL },
};

    static void
SFcancelCallback(Widget w UNUSED, XtPointer cl UNUSED, XtPointer cd UNUSED)
{
    SFstatus = SEL_FILE_CANCEL;
}

static XtCallbackRec SFcancelSelect[] =
{
    { SFcancelCallback, (XtPointer) NULL },
    { NULL, (XtPointer) NULL },
};

    static void
SFdismissAction(
    Widget	w UNUSED,
    XEvent	*event,
    String	*params UNUSED,
    Cardinal	*num_params UNUSED)
{
    if (event->type == ClientMessage
	    && (Atom)event->xclient.data.l[0] != SFwmDeleteWindow)
	return;

    SFstatus = SEL_FILE_CANCEL;
}

static char *wmDeleteWindowTranslation = "\
	<Message>WM_PROTOCOLS:	SelFileDismiss()\n\
";

static XtActionsRec actions[] =
{
    {"SelFileDismiss",	SFdismissAction},
};

    static void
SFsetColors(
    guicolor_T	bg,
    guicolor_T	fg,
    guicolor_T	scroll_bg,
    guicolor_T	scroll_fg)
{
    if (selFileForm)
    {
	XtVaSetValues(selFileForm, XtNbackground,  bg,
				   XtNforeground,  fg,
				   XtNborderColor, bg,
				   NULL);
    }
    {
	int i;

	for (i = 0; i < 3; ++i)
	{
	    if (selFileLists[i])
	    {
		XtVaSetValues(selFileLists[i], XtNbackground,  bg,
					       XtNforeground,  fg,
					       XtNborderColor, fg,
					       NULL);
	    }
	}
    }
    if (selFileOK)
    {
	XtVaSetValues(selFileOK, XtNbackground,  bg,
				 XtNforeground,  fg,
				 XtNborderColor, fg,
				 NULL);
    }
    if (selFileCancel)
    {
	XtVaSetValues(selFileCancel, XtNbackground, bg,
				     XtNforeground, fg,
				     XtNborderColor, fg,
				     NULL);
    }
    if (selFilePrompt)
    {
	XtVaSetValues(selFilePrompt, XtNbackground, bg,
				     XtNforeground, fg,
				     NULL);
    }
    if (gui.dpy)
    {
	XSetBackground(gui.dpy, SFtextGC, bg);
	XSetForeground(gui.dpy, SFtextGC, fg);
	XSetForeground(gui.dpy, SFlineGC, fg);

	// This is an xor GC, so combine the fg and background
	XSetBackground(gui.dpy, SFinvertGC, fg ^ bg);
	XSetForeground(gui.dpy, SFinvertGC, fg ^ bg);
    }
    if (selFileHScroll)
    {
	XtVaSetValues(selFileHScroll, XtNbackground, scroll_bg,
				      XtNforeground, scroll_fg,
				      XtNborderColor, fg,
				      NULL);
    }
    {
	int i;

	for (i = 0; i < 3; i++)
	{
	    XtVaSetValues(selFileVScrolls[i], XtNbackground, scroll_bg,
					      XtNforeground, scroll_fg,
					      XtNborderColor, fg,
					      NULL);
	    XtVaSetValues(selFileHScrolls[i], XtNbackground, scroll_bg,
					      XtNforeground, scroll_fg,
					      XtNborderColor, fg,
					      NULL);
	}
    }
}

    static void
SFcreateWidgets(
    Widget	toplevel,
    char	*prompt,
    char	*ok,
    char	*cancel)
{
    Cardinal	n;
    int		listWidth, listHeight;
    int		listSpacing = 10;
    int		scrollThickness = 15;
    int		hScrollX, hScrollY;
    int		vScrollX, vScrollY;

    selFile = XtVaAppCreateShell("selFile", "SelFile",
		transientShellWidgetClass, SFdisplay,
		XtNtransientFor, toplevel,
		XtNtitle, prompt,
		NULL);

    // Add WM_DELETE_WINDOW protocol
    XtAppAddActions(XtWidgetToApplicationContext(selFile),
	    actions, XtNumber(actions));
    XtOverrideTranslations(selFile,
	    XtParseTranslationTable(wmDeleteWindowTranslation));

    selFileForm = XtVaCreateManagedWidget("selFileForm",
		formWidgetClass, selFile,
		XtNdefaultDistance, 30,
		XtNforeground, SFfore,
		XtNbackground, SFback,
		XtNborderColor, SFback,
		NULL);

    selFilePrompt = XtVaCreateManagedWidget("selFilePrompt",
		labelWidgetClass, selFileForm,
		XtNlabel, prompt,
		XtNresizable, True,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		XtNborderWidth, 0,
		XtNforeground, SFfore,
		XtNbackground, SFback,
		NULL);

    /*
    XtVaGetValues(selFilePrompt,
		XtNforeground, &SFfore,
		XtNbackground, &SFback,
		NULL);
    */

    SFinitFont();

    SFentryWidth = SFbesideText + SFcharsPerEntry * SFcharWidth +
	SFbesideText;
    SFentryHeight = SFaboveAndBelowText + SFcharHeight +
	SFaboveAndBelowText;

    listWidth = SFlineToTextH + SFentryWidth + SFlineToTextH + 1 +
	scrollThickness;
    listHeight = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
	SFlineToTextV + SFlistSize * SFentryHeight +
	SFlineToTextV + 1 + scrollThickness;

    SFpathScrollWidth = 3 * listWidth + 2 * listSpacing + 4;

    hScrollX = -1;
    hScrollY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
	SFlineToTextV + SFlistSize * SFentryHeight +
	SFlineToTextV;
    SFhScrollWidth = SFlineToTextH + SFentryWidth + SFlineToTextH;

    vScrollX = SFlineToTextH + SFentryWidth + SFlineToTextH;
    vScrollY = SFlineToTextV + SFentryHeight + SFlineToTextV;
    SFvScrollHeight = SFlineToTextV + SFlistSize * SFentryHeight +
	SFlineToTextV;

    SFupperX = SFlineToTextH + SFentryWidth + SFlineToTextH - 1;
    SFlowerY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
	SFlineToTextV;
    SFupperY = SFlineToTextV + SFentryHeight + SFlineToTextV + 1 +
	SFlineToTextV + SFlistSize * SFentryHeight - 1;

    SFtextX = SFlineToTextH + SFbesideText;
    SFtextYoffset = SFlowerY + SFaboveAndBelowText + SFcharAscent;

    SFsegs[0].x1 = 0;
    SFsegs[0].y1 = vScrollY;
    SFsegs[0].x2 = vScrollX - 1;
    SFsegs[0].y2 = vScrollY;
    SFsegs[1].x1 = vScrollX;
    SFsegs[1].y1 = 0;
    SFsegs[1].x2 = vScrollX;
    SFsegs[1].y2 = vScrollY - 1;

    SFcompletionSegs[0].x1 = SFcompletionSegs[0].x2 = SFlineToTextH;
    SFcompletionSegs[1].x1 = SFcompletionSegs[1].x2 =
	SFlineToTextH + SFentryWidth - 1;

    selFileField = XtVaCreateManagedWidget("selFileField",
		asciiTextWidgetClass, selFileForm,
		XtNwidth, 3 * listWidth + 2 * listSpacing + 4,
		XtNborderColor, SFfore,
		XtNfromVert, selFilePrompt,
		XtNvertDistance, 10,
		XtNresizable, True,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		XtNstring, SFtextBuffer,
		XtNlength, MAXPATHL,
		XtNeditType, XawtextEdit,
		XtNwrap, XawtextWrapWord,
		XtNresize, XawtextResizeHeight,
		XtNuseStringInPlace, True,
		NULL);

    XtOverrideTranslations(selFileField,
	    XtParseTranslationTable(oneLineTextEditTranslations));
    XtSetKeyboardFocus(selFileForm, selFileField);

    selFileHScroll = XtVaCreateManagedWidget("selFileHScroll",
#ifdef FEAT_GUI_NEXTAW
		scrollbarWidgetClass, selFileForm,
#else
		vim_scrollbarWidgetClass, selFileForm,
#endif
		XtNorientation, XtorientHorizontal,
		XtNwidth, SFpathScrollWidth,
		XtNheight, scrollThickness,
		XtNborderColor, SFfore,
		XtNfromVert, selFileField,
		XtNvertDistance, 30,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		XtNforeground, gui.scroll_fg_pixel,
		XtNbackground, gui.scroll_bg_pixel,
#ifndef FEAT_GUI_NEXTAW
		XtNlimitThumb, 1,
#endif
		NULL);

    XtAddCallback(selFileHScroll, XtNjumpProc,
	    (XtCallbackProc) SFpathSliderMovedCallback, (XtPointer)NULL);
    XtAddCallback(selFileHScroll, XtNscrollProc,
	    (XtCallbackProc) SFpathAreaSelectedCallback, (XtPointer)NULL);

    selFileLists[0] = XtVaCreateManagedWidget("selFileList1",
		compositeWidgetClass, selFileForm,
		XtNwidth, listWidth,
		XtNheight, listHeight,
		XtNforeground,  SFfore,
		XtNbackground,  SFback,
		XtNborderColor, SFfore,
		XtNfromVert, selFileHScroll,
		XtNvertDistance, 10,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		NULL);

    selFileLists[1] = XtVaCreateManagedWidget("selFileList2",
		compositeWidgetClass, selFileForm,
		XtNwidth, listWidth,
		XtNheight, listHeight,
		XtNforeground,  SFfore,
		XtNbackground,  SFback,
		XtNborderColor, SFfore,
		XtNfromHoriz, selFileLists[0],
		XtNfromVert, selFileHScroll,
		XtNhorizDistance, listSpacing,
		XtNvertDistance, 10,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		NULL);

    selFileLists[2] = XtVaCreateManagedWidget("selFileList3",
		compositeWidgetClass, selFileForm,
		XtNwidth, listWidth,
		XtNheight, listHeight,
		XtNforeground,  SFfore,
		XtNbackground,  SFback,
		XtNborderColor, SFfore,
		XtNfromHoriz, selFileLists[1],
		XtNfromVert, selFileHScroll,
		XtNhorizDistance, listSpacing,
		XtNvertDistance, 10,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		NULL);

    for (n = 0; n < 3; n++)
    {
	selFileVScrolls[n] = XtVaCreateManagedWidget("selFileVScroll",
#ifdef FEAT_GUI_NEXTAW
		    scrollbarWidgetClass, selFileLists[n],
#else
		    vim_scrollbarWidgetClass, selFileLists[n],
#endif
		    XtNx, vScrollX,
		    XtNy, vScrollY,
		    XtNwidth, scrollThickness,
		    XtNheight, SFvScrollHeight,
		    XtNborderColor, SFfore,
		    XtNforeground, gui.scroll_fg_pixel,
		    XtNbackground, gui.scroll_bg_pixel,
#ifndef FEAT_GUI_NEXTAW
		    XtNlimitThumb, 1,
#endif
		    NULL);

	XtAddCallback(selFileVScrolls[n], XtNjumpProc,
		(XtCallbackProc)SFvFloatSliderMovedCallback,
		(XtPointer)(long_u)n);
	XtAddCallback(selFileVScrolls[n], XtNscrollProc,
		(XtCallbackProc)SFvAreaSelectedCallback, (XtPointer)(long_u)n);

	selFileHScrolls[n] = XtVaCreateManagedWidget("selFileHScroll",
#ifdef FEAT_GUI_NEXTAW
		    scrollbarWidgetClass, selFileLists[n],
#else
		    vim_scrollbarWidgetClass, selFileLists[n],
#endif
		    XtNorientation, XtorientHorizontal,
		    XtNx, hScrollX,
		    XtNy, hScrollY,
		    XtNwidth, SFhScrollWidth,
		    XtNheight, scrollThickness,
		    XtNborderColor, SFfore,
		    XtNforeground, gui.scroll_fg_pixel,
		    XtNbackground, gui.scroll_bg_pixel,
#ifndef FEAT_GUI_NEXTAW
		    XtNlimitThumb, 1,
#endif
		    NULL);

	XtAddCallback(selFileHScrolls[n], XtNjumpProc,
		(XtCallbackProc)SFhSliderMovedCallback,
		(XtPointer)(long_u)n);
	XtAddCallback(selFileHScrolls[n], XtNscrollProc,
		(XtCallbackProc)SFhAreaSelectedCallback, (XtPointer)(long_u)n);
    }

    selFileOK = XtVaCreateManagedWidget("selFileOK",
		commandWidgetClass, selFileForm,
		XtNlabel, ok,
		XtNresizable, True,
		XtNcallback, SFokSelect,
		XtNforeground,  SFfore,
		XtNbackground,  SFback,
		XtNborderColor, SFfore,
		XtNfromHoriz, selFileLists[0],
		XtNfromVert, selFileLists[0],
		XtNvertDistance, 30,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		NULL);

    selFileCancel = XtVaCreateManagedWidget("selFileCancel",
		commandWidgetClass, selFileForm,
		XtNlabel, cancel,
		XtNresizable, True,
		XtNcallback, SFcancelSelect,
		XtNforeground,  SFfore,
		XtNbackground,  SFback,
		XtNborderColor, SFfore,
		XtNfromHoriz, selFileOK,
		XtNfromVert, selFileLists[0],
		XtNhorizDistance, 30,
		XtNvertDistance, 30,
		XtNtop, XtChainTop,
		XtNbottom, XtChainTop,
		XtNleft, XtChainLeft,
		XtNright, XtChainLeft,
		NULL);

    XtSetMappedWhenManaged(selFile, False);
    XtRealizeWidget(selFile);

    // Add WM_DELETE_WINDOW protocol
    SFwmDeleteWindow = XInternAtom(SFdisplay, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(SFdisplay, XtWindow(selFile), &SFwmDeleteWindow, 1);

    SFcreateGC();

    for (n = 0; n < 3; n++)
    {
	XtAddEventHandler(selFileLists[n], ExposureMask, True,
		(XtEventHandler)SFexposeList, (XtPointer)(long_u)n);
	XtAddEventHandler(selFileLists[n], EnterWindowMask, False,
		(XtEventHandler)SFenterList, (XtPointer)(long_u)n);
	XtAddEventHandler(selFileLists[n], LeaveWindowMask, False,
		(XtEventHandler)SFleaveList, (XtPointer)(long_u)n);
	XtAddEventHandler(selFileLists[n], PointerMotionMask, False,
		(XtEventHandler)SFmotionList, (XtPointer)(long_u)n);
	XtAddEventHandler(selFileLists[n], ButtonPressMask, False,
		(XtEventHandler)SFbuttonPressList, (XtPointer)(long_u)n);
	XtAddEventHandler(selFileLists[n], ButtonReleaseMask, False,
		(XtEventHandler)SFbuttonReleaseList, (XtPointer)(long_u)n);
    }

    XtAddEventHandler(selFileField, KeyPressMask, False,
				       SFmodVerifyCallback, (XtPointer)NULL);

    SFapp = XtWidgetToApplicationContext(selFile);
}

    static void
SFtextChanged(void)
{
#if defined(FEAT_XFONTSET) && defined(XtNinternational)
    if ((unsigned long)_XawTextFormat((TextWidget)selFileField) == XawFmtWide)
    {
	wchar_t *wcbuf=(wchar_t *)SFtextBuffer;

	if ((wcbuf[0] == L'/') || (wcbuf[0] == L'~'))
	{
	    (void) wcstombs(SFcurrentPath, wcbuf, MAXPATHL);
	    SFtextPos = XawTextGetInsertionPoint(selFileField);
	}
	else
	{
	    strcpy(SFcurrentPath, SFstartDir);
	    (void) wcstombs(SFcurrentPath + strlen(SFcurrentPath), wcbuf, MAXPATHL);

	    SFtextPos = XawTextGetInsertionPoint(selFileField) + strlen(SFstartDir);
	}
    }
    else
#endif
    if ((SFtextBuffer[0] == '/') || (SFtextBuffer[0] == '~'))
    {
	(void) strcpy(SFcurrentPath, SFtextBuffer);
	SFtextPos = XawTextGetInsertionPoint(selFileField);
    }
    else
    {
	(void) strcat(strcpy(SFcurrentPath, SFstartDir), SFtextBuffer);

	SFtextPos = XawTextGetInsertionPoint(selFileField) + strlen(SFstartDir);
    }

    if (!SFworkProcAdded)
    {
	(void) XtAppAddWorkProc(SFapp, (XtWorkProc)SFworkProc, NULL);
	SFworkProcAdded = 1;
    }

    SFupdatePath();
}

    static char *
SFgetText(void)
{
#if defined(FEAT_XFONTSET) && defined(XtNinternational)
    char *buf;

    if ((unsigned long)_XawTextFormat((TextWidget)selFileField) == XawFmtWide)
    {
	wchar_t *wcbuf;
	int mbslength;

	XtVaGetValues(selFileField,
	    XtNstring, &wcbuf,
	NULL);
	mbslength = wcstombs(NULL, wcbuf, 0);
	// Hack: some broken wcstombs() returns zero, just get a large buffer
	if (mbslength == 0 && wcbuf != NULL && wcbuf[0] != 0)
	    mbslength = MAXPATHL;
	buf=(char *)XtMalloc(mbslength + 1);
	wcstombs(buf, wcbuf, mbslength +1);
	return buf;
    }
#endif
    return (char *)vim_strsave((char_u *)SFtextBuffer);
}

    static void
SFprepareToReturn(void)
{
    SFstatus = SEL_FILE_NULL;
    XtRemoveGrab(selFile);
    XtUnmapWidget(selFile);
    XtRemoveTimeOut(SFdirModTimerId);
    if (SFchdir(SFstartDir))
    {
	emsg(_("E614: vim_SelFile: can't return to current directory"));
	SFstatus = SEL_FILE_CANCEL;
    }
}

    char *
vim_SelFile(
    Widget	toplevel,
    char	*prompt,
    char	*init_path,
    int		(*show_entry)(),
    int		x,
    int		y,
    guicolor_T	fg,
    guicolor_T	bg,
    guicolor_T	scroll_fg,
    guicolor_T	scroll_bg) // The "Scrollbar" group colors
{
    static int	firstTime = 1;
    XEvent	event;
    char	*name_return;

    if (prompt == NULL)
	prompt = _("Pathname:");
    SFfore = fg;
    SFback = bg;

    if (mch_dirname((char_u *)SFstartDir, MAXPATHL) == FAIL)
    {
	emsg(_("E615: vim_SelFile: can't get current directory"));
	return NULL;
    }

    if (firstTime)
    {
	firstTime = 0;
	SFdisplay = XtDisplay(toplevel);
	SFcreateWidgets(toplevel, prompt, _("OK"), _("Cancel"));
    }
    else
    {
	XtVaSetValues(selFilePrompt, XtNlabel, prompt, NULL);
	XtVaSetValues(selFile, XtNtitle, prompt, NULL);
	SFsetColors(bg, fg, scroll_bg, scroll_fg);
    }

    XtVaSetValues(selFile, XtNx, x, XtNy, y, NULL);
    XtMapWidget(selFile);

    (void)strcat(SFstartDir, "/");
    (void)strcpy(SFcurrentDir, SFstartDir);

    if (init_path)
    {
	if (init_path[0] == '/')
	{
	    (void)strcpy(SFcurrentPath, init_path);
	    if (strncmp(SFcurrentPath, SFstartDir, strlen(SFstartDir)))
		SFsetText(SFcurrentPath);
	    else
		SFsetText(&(SFcurrentPath[strlen(SFstartDir)]));
	}
	else
	{
	    (void)strcat(strcpy(SFcurrentPath, SFstartDir), init_path);
	    SFsetText(&(SFcurrentPath[strlen(SFstartDir)]));
	}
    }
    else
	(void)strcpy(SFcurrentPath, SFstartDir);

    SFfunc = show_entry;

    SFtextChanged();

    XtAddGrab(selFile, True, True);

    SFdirModTimerId = XtAppAddTimeOut(SFapp, (unsigned long) 1000,
	    SFdirModTimer, (XtPointer) NULL);

    for (;;)
    {
	XtAppNextEvent(SFapp, &event);
	XtDispatchEvent(&event);
	switch (SFstatus)
	{
	    case SEL_FILE_TEXT:
		SFstatus = SEL_FILE_NULL;
		SFtextChanged();
		break;
	    case SEL_FILE_OK:
		name_return = SFgetText();
		SFprepareToReturn();
		return name_return;
	    case SEL_FILE_CANCEL:
		SFprepareToReturn();
		return NULL;
	    case SEL_FILE_NULL:
		break;
	}
    }
}
#endif // FEAT_BROWSE
