/*
 * Copyright (c) 2000, 2001 by Martin C. Shepherd.
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR 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.
 * 
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 * of the copyright holder.
 */

/*
 * Standard includes.
 */
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

/*
 * Local includes.
 */
#include "libtecla.h"
#include "direader.h"
#include "homedir.h"
#include "pathutil.h"
#include "cplfile.h"

/*
 * Set the maximum length allowed for usernames.
 * names.
 */
#define USR_LEN 100

/*
 * Set the maximum length allowed for environment variable names.
 */
#define ENV_LEN 100

/*
 * Set the max length of the error-reporting string. There is no point
 * in this being longer than the width of a typical terminal window.
 * In composing error messages, I have assumed that this number is
 * at least 80, so you don't decrease it below this number.
 */
#define ERRLEN 200

/*
 * The resources needed to complete a filename are maintained in objects
 * of the following type.
 */
struct CompleteFile {
  DirReader *dr;               /* A directory reader */
  HomeDir *home;               /* A home directory expander */
  PathName *path;              /* The buffer in which to accumulate the path */
  PathName *buff;              /* A pathname work buffer */
  char usrnam[USR_LEN+1];      /* The buffer used when reading the names of */
                               /*  users. */
  char envnam[ENV_LEN+1];      /* The buffer used when reading the names of */
                               /*  environment variables. */
  char errmsg[ERRLEN+1];       /* The error-report buffer */
};

static int cf_expand_home_dir(CompleteFile *cf, const char *user);
static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl,
				const char *prefix, const char *line,
				int word_start, int word_end, int escaped);
static HOME_DIR_FN(cf_homedir_callback);
static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl,
			     const char *line, int word_start, int word_end,
			     int escaped, CplCheckFn *check_fn,
			     void *check_data);
static char *cf_read_name(CompleteFile *cf, const char *type,
			  const char *string, int slen,
			  char *nambuf, int nammax);
static int cf_prepare_suffix(CompleteFile *cf, const char *suffix,
			     int add_escapes);

/*
 * A stack based object of the following type is used to pass data to the
 * cf_homedir_callback() function.
 */
typedef struct {
  CompleteFile *cf;    /* The file-completion resource object */
  WordCompletion *cpl; /* The string-completion rsource object */
  const char *prefix;  /* The username prefix to be completed */
  const char *line;    /* The line from which the prefix was extracted */
  int word_start;      /* The index in line[] of the start of the username */
  int word_end;        /* The index in line[] following the end of the prefix */
  int escaped;         /* If true, add escapes to the completion suffixes */
} CfHomeArgs;

/*.......................................................................
 * Create a new file-completion object.
 *
 * Output:
 *  return  CompleteFile *  The new object, or NULL on error.
 */
CompleteFile *_new_CompleteFile(void)
{
  CompleteFile *cf;  /* The object to be returned */
/*
 * Allocate the container.
 */
  cf = (CompleteFile *) malloc(sizeof(CompleteFile));
  if(!cf) {
    fprintf(stderr, "_new_CompleteFile: Insufficient memory.\n");
    return NULL;
  };
/*
 * Before attempting any operation that might fail, initialize the
 * container at least up to the point at which it can safely be passed
 * to _del_CompleteFile().
 */
  cf->dr = NULL;
  cf->home = NULL;
  cf->path = NULL;
  cf->buff = NULL;
  cf->usrnam[0] = '\0';
  cf->envnam[0] = '\0';
  cf->errmsg[0] = '\0';
/*
 * Create the object that is used for reading directories.
 */
  cf->dr = _new_DirReader();
  if(!cf->dr)
    return _del_CompleteFile(cf);
/*
 * Create the object that is used to lookup home directories.
 */
  cf->home = _new_HomeDir();
  if(!cf->home)
    return _del_CompleteFile(cf);
/*
 * Create the buffer in which the completed pathname is accumulated.
 */
  cf->path = _new_PathName();
  if(!cf->path)
    return _del_CompleteFile(cf);
/*
 * Create a pathname work buffer.
 */
  cf->buff = _new_PathName();
  if(!cf->buff)
    return _del_CompleteFile(cf);
  return cf;
}

/*.......................................................................
 * Delete a file-completion object.
 *
 * Input:
 *  cf     CompleteFile *  The object to be deleted.
 * Output:
 *  return CompleteFile *  The deleted object (always NULL).
 */
CompleteFile *_del_CompleteFile(CompleteFile *cf)
{
  if(cf) {
    cf->dr = _del_DirReader(cf->dr);
    cf->home = _del_HomeDir(cf->home);
    cf->path = _del_PathName(cf->path);
    cf->buff = _del_PathName(cf->buff);
    free(cf);
  };
  return NULL;
}

/*.......................................................................
 * Look up the possible completions of the incomplete filename that
 * lies between specified indexes of a given command-line string.
 *
 * Input:
 *  cpl   WordCompletion *  The object in which to record the completions.
 *  cf      CompleteFile *  The filename-completion resource object.
 *  line      const char *  The string containing the incomplete filename.
 *  word_start       int    The index of the first character in line[]
 *                          of the incomplete filename.
 *  word_end         int    The index of the character in line[] that
 *                          follows the last character of the incomplete
 *                          filename.
 *  escaped          int    If true, backslashes in line[] are
 *                          interpreted as escaping the characters
 *                          that follow them, and any spaces, tabs,
 *                          backslashes, or wildcard characters in the
 *                          returned suffixes will be similarly escaped.
 *                          If false, backslashes will be interpreted as
 *                          literal parts of the file name, and no
 *                          backslashes will be added to the returned
 *                          suffixes.
 *  check_fn  CplCheckFn *  If not zero, this argument specifies a
 *                          function to call to ask whether a given
 *                          file should be included in the list
 *                          of completions.
 *  check_data      void *  Anonymous data to be passed to check_fn().
 * Output:
 *  return           int    0 - OK.
 *                          1 - Error. A description of the error can be
 *                                     acquired by calling _cf_last_error(cf).
 */
int _cf_complete_file(WordCompletion *cpl, CompleteFile *cf,
		     const char *line, int word_start, int word_end,
		     int escaped, CplCheckFn *check_fn, void *check_data)
{
  const char *lptr; /* A pointer into line[] */
  int nleft;        /* The number of characters still to be processed */
                    /*  in line[]. */
/*
 * Check the arguments.
 */
  if(!cpl || !cf || !line || word_end < word_start) {
    if(cf)
      strncpy(cf->errmsg, "_cf_complete_file: Invalid arguments", sizeof(cf->errmsg));
    return 1;
  };
/*
 * Clear the buffer in which the filename will be constructed.
 */
  _pn_clear_path(cf->path);
/*
 * How many characters are to be processed?
 */
  nleft = word_end - word_start;
/*
 * Get a pointer to the start of the incomplete filename.
 */
  lptr = line + word_start;
/*
 * If the first character is a tilde, then perform home-directory
 * interpolation.
 */
  if(nleft > 0 && *lptr == '~') {
    int slen;
    if(!cf_read_name(cf, "User", ++lptr, --nleft, cf->usrnam, USR_LEN))
      return 1;
/*
 * Advance over the username in the input line.
 */
    slen = strlen(cf->usrnam);
    lptr += slen;
    nleft -= slen;
/*
 * If we haven't hit the end of the input string then we have a complete
 * username to translate to the corresponding home directory.
 */
    if(nleft > 0) {
      if(cf_expand_home_dir(cf, cf->usrnam))
	return 1;
/*
 * ~user and ~ are usually followed by a directory separator to
 * separate them from the file contained in the home directory.
 * If the home directory is the root directory, then we don't want
 * to follow the home directory by a directory separator, so we should
 * skip over it so that it doesn't get copied into the filename.
 */
      if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
	 strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
	lptr += FS_DIR_SEP_LEN;
	nleft -= FS_DIR_SEP_LEN;
      };
/*
 * If we have reached the end of the input string, then the username
 * may be incomplete, and we should attempt to complete it.
 */
    } else {
/*
 * Look up the possible completions of the username.
 */
      return cf_complete_username(cf, cpl, cf->usrnam, line, word_start+1,
				  word_end, escaped);
    };
  };
/*
 * Copy the rest of the path, stopping to expand $envvar expressions
 * where encountered.
 */
  while(nleft > 0) {
    int seglen;   /* The length of the next segment to be copied */
/*
 * Find the length of the next segment to be copied, stopping if an
 * unescaped '$' is seen, or the end of the path is reached.
 */
    for(seglen=0; seglen < nleft; seglen++) {
      int c = lptr[seglen];
      if(escaped && c == '\\')
	seglen++;
      else if(c == '$')
	break;
/*
 * We will be completing the last component of the file name,
 * so whenever a directory separator is seen, assume that it
 * might be the start of the last component, and mark the character
 * that follows it as the start of the name that is to be completed.
 */
      if(nleft >= FS_DIR_SEP_LEN &&
	 strncmp(lptr + seglen, FS_DIR_SEP, FS_DIR_SEP_LEN)==0) {
	word_start = (lptr + seglen) - line + FS_DIR_SEP_LEN;
      };
    };
/*
 * We have reached either the end of the filename or the start of
 * $environment_variable expression. Record the newly checked
 * segment of the filename in the output filename, removing
 * backslash-escapes where needed.
 */
    if(_pn_append_to_path(cf->path, lptr, seglen, escaped) == NULL) {
      strncpy(cf->errmsg, "Insufficient memory to complete filename", sizeof(cf->errmsg));
      return 1;
    };
    lptr += seglen;
    nleft -= seglen;
/*
 * If the above loop finished before we hit the end of the filename,
 * then this was because an unescaped $ was seen. In this case, interpolate
 * the value of the environment variable that follows it into the output
 * filename.
 */
    if(nleft > 0) {
      char *value;    /* The value of the environment variable */
      int vlen;       /* The length of the value string */
      int nlen;       /* The length of the environment variable name */
/*
 * Read the name of the environment variable.
 */
      if(!cf_read_name(cf, "Environment", ++lptr, --nleft, cf->envnam, ENV_LEN))
	return 1;
/*
 * Advance over the environment variable name in the input line.
 */
      nlen = strlen(cf->envnam);
      lptr += nlen;
      nleft -= nlen;
/*
 * Get the value of the environment variable.
 */
      value = getenv(cf->envnam);
      if(!value) {
	const char *fmt = "Unknown environment variable: %.*s";
	snprintf(cf->errmsg, sizeof(cf->errmsg), fmt, ERRLEN - strlen(fmt),
		 cf->envnam);
	return 1;
      };
      vlen = strlen(value);
/*
 * If we are at the start of the filename and the first character of the
 * environment variable value is a '~', attempt home-directory
 * interpolation.
 */
      if(cf->path->name[0] == '\0' && value[0] == '~') {
	if(!cf_read_name(cf, "User", value+1, vlen-1, cf->usrnam, USR_LEN) ||
	   cf_expand_home_dir(cf, cf->usrnam))
	  return 1;
/*
 * If the home directory is the root directory, and the ~usrname expression
 * was followed by a directory separator, prevent the directory separator
 * from being appended to the root directory by skipping it in the
 * input line.
 */
	if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
	   strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
	  lptr += FS_DIR_SEP_LEN;
	  nleft -= FS_DIR_SEP_LEN;
	};
      } else {
/*
 * Append the value of the environment variable to the output path.
 */
	if(_pn_append_to_path(cf->path, value, strlen(value), escaped)==NULL) {
	  strncpy(cf->errmsg, "Insufficient memory to complete filename", sizeof(cf->errmsg));
	  return 1;
	};
/*
 * Prevent extra directory separators from being added.
 */
	if(nleft >= FS_DIR_SEP_LEN &&
	   strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
	   strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
	  lptr += FS_DIR_SEP_LEN;
	  nleft -= FS_DIR_SEP_LEN;
	} else if(vlen > FS_DIR_SEP_LEN &&
		  strcmp(value + vlen - FS_DIR_SEP_LEN, FS_DIR_SEP)==0) {
	  cf->path->name[vlen-FS_DIR_SEP_LEN] = '\0';
	};
      };
/*
 * If adding the environment variable didn't form a valid directory,
 * we can't complete the line, since there is no way to separate append
 * a partial filename to an environment variable reference without
 * that appended part of the name being seen later as part of the
 * environment variable name. Thus if the currently constructed path
 * isn't a directory, quite now with no completions having been
 * registered.
 */
      if(!_pu_path_is_dir(cf->path->name))
	return 0;
/*
 * For the reasons given above, if we have reached the end of the filename
 * with the expansion of an environment variable, the only allowed
 * completion involves the addition of a directory separator.
 */
      if(nleft == 0) {
	if(cpl_add_completion(cpl, line, lptr-line, word_end, FS_DIR_SEP,
			      "", "")) {
	  strncpy(cf->errmsg, cpl_last_error(cpl), ERRLEN);
	  cf->errmsg[ERRLEN] = '\0';
	  return 1;
	};
	return 0;
      };
    };
  };
/*
 * Complete the filename if possible.
 */
  return cf_complete_entry(cf, cpl, line, word_start, word_end, escaped,
			   check_fn, check_data);
}

/*.......................................................................
 * Return a description of the last path-completion error that occurred.
 *
 * Input:
 *  cf    CompleteFile *  The path-completion resource object.
 * Output:
 *  return  const char *  The description of the last error.
 */
const char *_cf_last_error(CompleteFile *cf)
{
  return cf ? cf->errmsg : "NULL CompleteFile argument";
}

/*.......................................................................
 * Lookup the home directory of the specified user, or the current user
 * if no name is specified, appending it to output pathname.
 *
 * Input:
 *  cf  CompleteFile *  The pathname completion resource object.
 *  user  const char *  The username to lookup, or "" to lookup the
 *                      current user.
 * Output:
 *  return        int    0 - OK.
 *                       1 - Error.
 */
static int cf_expand_home_dir(CompleteFile *cf, const char *user)
{
/*
 * Attempt to lookup the home directory.
 */
  const char *home_dir = _hd_lookup_home_dir(cf->home, user);
/*
 * Failed?
 */
  if(!home_dir) {
    strncpy(cf->errmsg, _hd_last_home_dir_error(cf->home), ERRLEN);
    cf->errmsg[ERRLEN] = '\0';
    return 1;
  };
/*
 * Append the home directory to the pathname string.
 */
  if(_pn_append_to_path(cf->path, home_dir, -1, 0) == NULL) {
    strncpy(cf->errmsg, "Insufficient memory for home directory expansion", sizeof(cf->errmsg));
    return 1;
  };
  return 0;
}

/*.......................................................................
 * Lookup and report all completions of a given username prefix.
 *
 * Input:
 *  cf     CompleteFile *  The filename-completion resource object.
 *  cpl  WordCompletion *  The object in which to record the completions.
 *  prefix   const char *  The prefix of the usernames to lookup.
 *  line     const char *  The command-line in which the username appears.
 *  word_start      int    The index within line[] of the start of the
 *                         username that is being completed.
 *  word_end        int    The index within line[] of the character which
 *                         follows the incomplete username.
 *  escaped         int    True if the completions need to have special
 *                         characters escaped.
 * Output:
 *  return          int    0 - OK.
 *                         1 - Error.
 */
static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl,
				const char *prefix, const char *line,
				int word_start, int word_end, int escaped)
{
/*
 * Set up a container of anonymous arguments to be sent to the
 * username-lookup iterator.
 */
  CfHomeArgs args;
  args.cf = cf;
  args.cpl = cpl;
  args.prefix = prefix;
  args.line = line;
  args.word_start = word_start;
  args.word_end = word_end;
  args.escaped = escaped;
/*
 * Iterate through the list of users, recording those which start
 * with the specified prefix.
 */
  if(_hd_scan_user_home_dirs(cf->home, &args, cf_homedir_callback)) {
    strncpy(cf->errmsg, _hd_last_home_dir_error(cf->home), ERRLEN);
    cf->errmsg[ERRLEN] = '\0';
    return 1;
  };
  return 0;
}

/*.......................................................................
 * The user/home-directory scanner callback function (see homedir.h)
 * used by cf_complete_username().
 */
static HOME_DIR_FN(cf_homedir_callback)
{
/*
 * Get the file-completion resources from the anonymous data argument.
 */
  CfHomeArgs *args = (CfHomeArgs *) data;
  WordCompletion *cpl = args->cpl;
  CompleteFile *cf = args->cf;
/*
 * Get the length of the username prefix.
 */
  int prefix_len = strlen(args->prefix);
/*
 * Get the length of the latest user name that is to be compared to
 * the prefix.
 */
  int name_len = strlen(usrnam);
/*
 * See if the latest username starts with the prefix that we are
 * searching for, and record its suffix in the array of matches if so.
 */
  if(name_len >= prefix_len && strncmp(args->prefix, usrnam, prefix_len)==0) {
/*
 * Copy the username into the pathname work buffer, adding backslash
 * escapes where needed.
 */
    if(cf_prepare_suffix(cf, usrnam+prefix_len, args->escaped)) {
      strncpy(errmsg, cf->errmsg, maxerr);
      errmsg[maxerr] = '\0';
      return 1;
    };
/*
 * Report the completion suffix that was copied above.
 */
    if(cpl_add_completion(cpl, args->line, args->word_start, args->word_end,
			  cf->buff->name, FS_DIR_SEP, FS_DIR_SEP)) {
      strncpy(errmsg, cpl_last_error(cpl), maxerr);
      errmsg[maxerr] = '\0';
      return 1;
    };
  };
  return 0;
}

/*.......................................................................
 * Report possible completions of the filename in cf->path->name[].
 *
 * Input:
 *  cf      CompleteFile *  The file-completion resource object.
 *  cpl   WordCompletion *  The object in which to record the completions.
 *  line      const char *  The input line, as received by the callback
 *                          function.
 *  word_start       int    The index within line[] of the start of the
 *                          last component of the filename that is being
 *                          completed.
 *  word_end         int    The index within line[] of the character which
 *                          follows the incomplete filename.
 *  escaped          int    If true, escape special characters in the
 *                          completion suffixes.
 *  check_fn  CplCheckFn *  If not zero, this argument specifies a
 *                          function to call to ask whether a given
 *                          file should be included in the list
 *                          of completions.
 *  check_data      void *  Anonymous data to be passed to check_fn().
 * Output:
 *  return           int    0 - OK.
 *                          1 - Error.
 */
static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl,
			     const char *line, int word_start, int word_end,
			     int escaped, CplCheckFn *check_fn,
			     void *check_data)
{
  const char *dirpath;   /* The name of the parent directory */
  int start;             /* The index of the start of the last filename */
                         /*  component in the transcribed filename. */
  const char *prefix;    /* The filename prefix to be completed */
  int prefix_len;        /* The length of the filename prefix */
  const char *file_name; /* The lastest filename being compared */
  int waserr = 0;        /* True after errors */
  int terminated=0;      /* True if the directory part had to be terminated */
/*
 * Get the pathname string and its current length.
 */
  char *pathname = cf->path->name;
  int pathlen = strlen(pathname);
/*
 * Locate the start of the final component of the pathname.
 */
  for(start=pathlen - 1; start >= 0 &&
      strncmp(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; start--)
    ;
/*
 * Is the parent directory the root directory?
 */
  if(start==0 ||
     (start < 0 && strncmp(pathname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0)) {
    dirpath = FS_ROOT_DIR;
    start += FS_ROOT_DIR_LEN;
/*
 * If we found a directory separator then the part which precedes the
 * last component is the name of the directory to be opened.
 */
  } else if(start > 0) {
/*
 * The _dr_open_dir() function requires the directory name to be '\0'
 * terminated, so temporarily do this by overwriting the first character
 * of the directory separator.
 */
    pathname[start] = '\0';
    dirpath = pathname;
    terminated = 1;
/*
 * We reached the start of the pathname before finding a directory
 * separator, so arrange to open the current working directory.
 */
  } else {
    start = 0;
    dirpath = FS_PWD;
  };
/*
 * Attempt to open the directory.
 */
  if(_dr_open_dir(cf->dr, dirpath, NULL)) {
    const char *fmt = "Can't open directory: %.*s";
    snprintf(cf->errmsg, sizeof(cf->errmsg), fmt, ERRLEN - strlen(fmt),
	     dirpath);
    return 1;
  };
/*
 * If removed above, restore the directory separator and skip over it
 * to the start of the filename.
 */
  if(terminated) {
    memcpy(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN);
    start += FS_DIR_SEP_LEN;
  };
/*
 * Get the filename prefix and its length.
 */
  prefix = pathname + start;
  prefix_len = strlen(prefix);
/*
 * Traverse the directory, looking for files who's prefixes match the
 * last component of the pathname.
 */
  while((file_name = _dr_next_file(cf->dr)) != NULL && !waserr) {
    int name_len = strlen(file_name);
/*
 * Is the latest filename a possible completion of the filename prefix?
 */
    if(name_len >= prefix_len && strncmp(prefix, file_name, prefix_len)==0) {
/*
 * When listing all files in a directory, don't list files that start
 * with '.'. This is how hidden files are denoted in UNIX.
 */
      if(prefix_len > 0 || file_name[0] != '.') {
/*
 * Copy the completion suffix into the work pathname cf->buff->name,
 * adding backslash escapes if needed.
 */
	if(cf_prepare_suffix(cf, file_name + prefix_len, escaped)) {
	  waserr = 1;
	} else {
/*
 * We want directories to be displayed with directory suffixes,
 * and other fully completed filenames to be followed by spaces.
 * To check the type of the file, append the current suffix
 * to the path being completed, check the filetype, then restore
 * the path to its original form.
 */
	  const char *cont_suffix = "";  /* The suffix to add if fully */
                                         /*  completed. */
	  const char *type_suffix = "";  /* The suffix to add when listing */
	  if(_pn_append_to_path(cf->path, file_name + prefix_len,
				-1, escaped) == NULL) {
	    strncpy(cf->errmsg, "Insufficient memory to complete filename.", sizeof(cf->errmsg));
	    return 1;
	  };
/*
 * Specify suffixes according to the file type.
 */
	  if(_pu_path_is_dir(cf->path->name)) {
	    cont_suffix = FS_DIR_SEP;
	    type_suffix = FS_DIR_SEP;
	  } else if(!check_fn || check_fn(check_data, cf->path->name)) {
	    cont_suffix = " ";
	  } else {
	    cf->path->name[pathlen] = '\0';
	    continue;
	  };
/*
 * Remove the temporarily added suffix.
 */
	  cf->path->name[pathlen] = '\0';
/*
 * Record the latest completion.
 */
	  if(cpl_add_completion(cpl, line, word_start, word_end, cf->buff->name,
				type_suffix, cont_suffix))
	    waserr = 1;
	};
      };
    };
  };
/*
 * Close the directory.
 */
  _dr_close_dir(cf->dr);
  return waserr;
}

/*.......................................................................
 * Read a username or environment variable name, stopping when a directory
 * separator is seen, when the end of the string is reached, or the
 * output buffer overflows.
 *
 * Input:
 *  cf   CompleteFile *  The file-completion resource object.
 *  type         char *  The capitalized name of the type of name being read.
 *  string       char *  The string who's prefix contains the name.
 *  slen          int    The number of characters in string[].
 *  nambuf       char *  The output name buffer.
 *  nammax        int    The longest string that will fit in nambuf[], excluding
 *                       the '\0' terminator.
 * Output:
 *  return       char *  A pointer to nambuf on success. On error NULL is
 *                       returned and a description of the error is recorded
 *                       in cf->errmsg[].
 */
static char *cf_read_name(CompleteFile *cf, const char *type,
			  const char *string, int slen,
			  char *nambuf, int nammax)
{
  int namlen;         /* The number of characters in nambuf[] */
  const char *sptr;   /* A pointer into string[] */
/*
 * Work out the max number of characters that should be copied.
 */
  int nmax = nammax < slen ? nammax : slen;
/*
 * Get the environment variable name that follows the dollar.
 */
  for(sptr=string,namlen=0;
      namlen < nmax && (slen-namlen < FS_DIR_SEP_LEN ||
			strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0);
      namlen++) {
    nambuf[namlen] = *sptr++;
  };
/*
 * Did the name overflow the buffer?
 */
  if(namlen >= nammax) {
    const char *fmt = "%.*s name too long";
    snprintf(cf->errmsg, sizeof(cf->errmsg), fmt, ERRLEN - strlen(fmt), type);
    return NULL;
  };
/*
 * Terminate the string.
 */
  nambuf[namlen] = '\0';
  return nambuf;
}

/*.......................................................................
 * Using the work buffer cf->buff, make a suitably escaped copy of a
 * given completion suffix, ready to be passed to cpl_add_completion().
 *
 * Input:
 *  cf   CompleteFile *  The file-completion resource object.
 *  suffix       char *  The suffix to be copied.
 *  add_escapes   int    If true, escape special characters.
 * Output:
 *  return        int    0 - OK.
 *                       1 - Error.
 */
static int cf_prepare_suffix(CompleteFile *cf, const char *suffix,
			     int add_escapes)
{
  const char *sptr; /* A pointer into suffix[] */
  int nbsl;         /* The number of backslashes to add to the suffix */
  int i;
/*
 * How long is the suffix?
 */
  int suffix_len = strlen(suffix);
/*
 * Clear the work buffer.
 */
  _pn_clear_path(cf->buff);
/*
 * Count the number of backslashes that will have to be added to
 * escape spaces, tabs, backslashes and wildcard characters.
 */
  nbsl = 0;
  if(add_escapes) {
    for(sptr = suffix; *sptr; sptr++) {
      switch(*sptr) {
      case ' ': case '\t': case '\\': case '*': case '?': case '[':
	nbsl++;
	break;
      };
    };
  };
/*
 * Arrange for the output path buffer to have sufficient room for the
 * both the suffix and any backslashes that have to be inserted.
 */
  if(_pn_resize_path(cf->buff, suffix_len + nbsl) == NULL) {
    strncpy(cf->errmsg, "Insufficient memory to complete filename", sizeof(cf->errmsg));
    return 1;
  };
/*
 * If the suffix doesn't need any escapes, copy it directly into the
 * work buffer.
 */
  if(nbsl==0) {
    strncpy(cf->buff->name, suffix, cf->buff->dim);
  } else {
/*
 * Make a copy with special characters escaped?
 */
    if(nbsl > 0) {
      const char *src = suffix;
      char *dst = cf->buff->name;
      for(i=0; i<suffix_len; i++) {
	switch(*src) {
	case ' ': case '\t': case '\\': case '*': case '?': case '[':
	  *dst++ = '\\';
	};
	*dst++ = *src++;
      };
      *dst = '\0';
    };
  };
  return 0;
}
