text: /*
  complete.c
  
  Created:	July 1995 by Philip Homburg <philip@cs.vu.nl>
  */
  
  #include <ctype.h>
  #include <dirent.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/stat.h>
  #include "myhistedit.h"
  #include "shell.h"
  
  #include "complete.h"
  #include "error.h"
  #include "expand.h"
  #include "nodes.h"
  #include "memalloc.h"
  
  static char **getlist(EditLine *el, int *baselen, int *isdir);
  static char **getlist_tilde(char *prefix);
  static int vstrcmp(const void *v1, const void *v2);
  static void print_list(char **list);
  static int install_extra(EditLine *el, char **list, int baselen, int isdir);
  
  unsigned char complete(EditLine *el, int ch)
  {
  	struct stackmark mark;
  	const LineInfo *lf;
  	char **list;
  	int baselen, prefix, isdir;
  
  	/* Direct the cursor the the end of the word. */
  	for(;;)
  	{
  		lf = el_line(el);
  		if (lf->cursor < lf->lastchar &&
  			!isspace((unsigned char)*lf->cursor))
  		{
  			(*(char **)&lf->cursor)++;	/* XXX */
  		}
  		else
  			break;
  	}
  
  	setstackmark(&mark);
  	list= getlist(el, &baselen, &isdir);
  	if (list)
  	{
  		prefix= install_extra(el, list, baselen, isdir);
  		el_push(el, "i");
  	}
  	popstackmark(&mark);
  	if (list)
  		return CC_REFRESH;
  	else
  		return CC_ERROR;
  }
  
  unsigned char complete_list(EditLine *el, int ch)
  {
  	struct stackmark mark;
  	char **list;
  
  	setstackmark(&mark);
  	list= getlist(el, NULL, NULL);
  	if (list)
  	{
  		print_list(list);
  		re_goto_bottom(el);
  	}
  	popstackmark(&mark);
  	if (list)
  	{
  		return CC_REFRESH;
  	}
  	else
  		return CC_ERROR;
  }
  
  unsigned char complete_or_list(EditLine *el, int ch)
  {
  	struct stackmark mark;
  	const LineInfo *lf;
  	char **list;
  	int baselen, prefix, isdir;
  
  	setstackmark(&mark);
  	list= getlist(el, &baselen, &isdir);
  	if (list)
  	{
  		prefix= install_extra(el, list, baselen, isdir);
  		if (prefix == baselen)
  		{
  			print_list(list);
  			re_goto_bottom(el);
  		}
  	}
  	popstackmark(&mark);
  	if (list)
  		return CC_REFRESH;
  	else
  		return CC_ERROR;
  }
  
  unsigned char complete_expand(EditLine *el, int ch)
  {
  	printf("complete_expand\n");
  	return CC_ERROR;
  }
  
  static char **getlist(EditLine *el, int *baselen, int *isdir)
  {
  	const LineInfo *lf;
  	const char *begin, *end;
  	char *dirnam, *basenam;
  	union node arg;
  	struct arglist arglist;
  	DIR *dir;
  	struct dirent *dirent;
  	int i, l, n;
  	char *p, **list;
  	struct strlist *slp, *nslp;
  	struct stat sb;
  
  	lf = el_line(el);
  
  	/* Try to find to begin and end of the word that we have to comple. */
  	begin= lf->cursor;
  	while (begin > lf->buffer && !isspace((unsigned char)begin[-1]))
  		begin--;
  	end= lf->cursor;
  	while (end < lf->lastchar && !isspace((unsigned char)end[0]))
  		end++;
  
  	*(const char **)&lf->cursor= end; /* XXX */
  
  	/* Copy the word to a string */
  	dirnam= stalloc(end-begin+1);
  	strncpy(dirnam, begin, end-begin);
  	dirnam[end-begin]= '\0';
  
  	/* Cut the word in two pieces: a path and a (partial) component. */
  	basenam= strrchr(dirnam, '/');
  	if (basenam)
  	{
  		basenam++;
  		p= stalloc(strlen(basenam) + 1);
  		strcpy(p, basenam);
  		*basenam= '\0';
  		basenam= p;
  	}
  	else
  	{
  		if (dirnam[0] == '~')
  			return getlist_tilde(dirnam);
  		basenam= dirnam;
  		dirnam= "./";
  	}
  	if (baselen)
  		*baselen= strlen(basenam);
  
  	arg.type= NARG;
  	arg.narg.next= NULL;
  	arg.narg.text= dirnam;
  	arg.narg.backquote= NULL;
  	arglist.list= NULL;
  	arglist.lastp= &arglist.list;
  	expandarg(&arg, &arglist, EXP_TILDE);
  
  	INTOFF;
  	list= NULL;
  	dir= opendir(arglist.list->text);
  	if (dir)
  	{
  		slp= NULL;
  		n= 0;
  		l= strlen(basenam);
  		while(dirent= readdir(dir))
  		{
  			if (strncmp(dirent->d_name, basenam, l) != 0)
  				continue;
  			if (l == 0 && dirent->d_name[0] == '.')
  				continue;
  			nslp= stalloc(sizeof(*nslp));
  			nslp->next= slp;
  			slp= nslp;
  			slp->text= stalloc(strlen(dirent->d_name)+1);
  			strcpy(slp->text, dirent->d_name);
  			n++;
  			if (n == 1 && isdir != NULL)
  			{
  				/* Try to findout whether this entry is a
  				 * file or a directory.
  				 */
  				p= stalloc(strlen(arglist.list->text) +
  					strlen(dirent->d_name) + 1);
  				strcpy(p, arglist.list->text);
  				strcat(p, dirent->d_name);
  				if (stat(p, &sb) == -1)
  					printf("stat '%s' failed: %s\n",
  						p, strerror(errno));
  				if (stat(p, &sb) == 0 && S_ISDIR(sb.st_mode))
  					*isdir= 1;
  				else
  					*isdir= 0;
  			}
  		}
  		closedir(dir);
  		if (n != 0)
  		{
  			list= stalloc((n+1)*sizeof(*list));
  			for(i= 0; slp; i++, slp= slp->next)
  				list[i]= slp->text;
  			if (i != n)
  				error("complete'make_list: i != n");
  			list[i]= NULL;
  			qsort(list, n, sizeof(*list), vstrcmp);
  		}
  	}
  	INTON;
  	return list;
  }
  
  static char **getlist_tilde(char *prefix)
  {
  	printf("should ~-complete '%s'\n", prefix);
  	return NULL;
  }
  
  static int vstrcmp(const void *v1, const void *v2)
  {
  	return strcmp(*(char **)v1, *(char **)v2);
  }
  
  #define MAXCOLS 40
  #define SEPWIDTH 4
  
  static void print_list(char **list)
  {
  	struct
  	{
  		int cols;
  		int start[MAXCOLS+1];
  		int width[MAXCOLS];
  	} best, next;
  	int e, i, j, l, n, o, cols, maxw, width;
  	int linewidth= 80;
  
  	/* Count the number of entries. */
  	for (n= 0; list[n]; n++)
  		;	/* do nothing */
  	if (n == 0)
  		error("complete'print_list: n= 0");
  
  	/* Try to maximize the number of columns */
  	for (cols= 1; cols<= MAXCOLS; cols++)
  	{
  		next.cols= cols;
  
  		o= 0;
  		width= 0;
  		for(j= 0; j<cols; j++)
  		{
  			next.start[j]= o;
  
  			/* Number of entries in this column. */
  			e= (n-o)/(cols-j);
  			if ((n-o)%(cols-j))
  				e++;
  
  			maxw= 0;
  			for (i= 0; i<e; i++)
  			{
  				l= strlen(list[o+i]);
  				if (l < 6)
  					l= 6;
  				l += SEPWIDTH;
  				if (l > maxw)
  					maxw= l;
  			}
  			next.width[j]= maxw;
  			width += maxw;
  			o += e;
  		}
  		next.start[j]= o;
  		if (cols > 1 && width-SEPWIDTH>linewidth)
  			break;
  		best= next;
  	}
  	cols= best.cols;
  	e= best.start[1];
  	printf("\n");
  	for(i= 0; i<e; i++)
  	{
  		for (j= 0; j<cols; j++)
  		{
  			if (best.start[j]+i == best.start[j+1])
  				continue;
  			if (j < cols-1)
  			{
  				printf("%-*s", best.width[j],
  					list[best.start[j]+i]);
  			}
  			else
  			{
  				printf("%s", list[best.start[j]+i]);
  			}
  		}
  		if (i < e-1)
  			printf("\n");
  	}
  	fflush(stdout);
  }
  
  static int install_extra(EditLine *el, char **list, int baselen, int isdir)
  {
  	int l;
  	char *p, **lp;
  
  	l= strlen(list[0]);
  	for (lp= &list[1]; *lp; lp++)
  	{
  		while(l>0)
  		{
  			if (strncmp(list[0], *lp, l) != 0)
  				l--;
  			else
  				break;
  		}
  	}
  	if (l > baselen || list[1] == NULL)
  	{
  		p= stalloc(l-baselen+2);
  		strncpy(p, list[0]+baselen, l-baselen);
  		if (list[1] == NULL)
  		{
  			p[l-baselen]= isdir ? '/' : ' ';
  			p[l-baselen+1]= '\0';
  			l++;
  		}
  		else
  			p[l-baselen]= '\0';
  		if (el_insertstr(el, p) == -1)
  			return -1;
  	}
  	return l;
  }
  
  /*
   * $PchId: complete.c,v 1.2 2006/04/10 14:35:53 philip Exp $
   */
  