[net.sources] Directory Browser / Changer

majka@ubc-cs.UUCP (Marc Majka) (09/04/86)

Here is the C source for yet another directory changer.  This one has
EMACS-style name completion and help facility.  It actually is in two 
parts:  a directory browser that does the work, and a shell alias that
actually does the "cd".  You will have to add the alias to your .alias 
or .cshrc or whatever.  See the README file for details.  Included are
source, makefile, and manual.

The name completion routine included in the source is independent of any
of the directory stuff, and makes a very handy utility.  See the "NOTE TO
HACKERS" in the code.

Follow the standard litany: cut, sh, make, and enjoy!

---
Marc Majka


- - - CUT - - - CUT - - - CUT - - - CUT - - - CUT - - - CUT - - - CUT - - -
#!/bin/sh
#
# shell archive - extract with /bin/sh
#
echo 
echo extracting file README
sed 's/^X//' > README <<'!FUNKY!STUFF!'
XThis package contains C source for a directory browser called chd, together
Xwith a manual and makefile.  The browser can be used as an interactive 
Xchange-directory utility with the use of a shell alias like this:
X
Xalias cx 'set nwd = `chd`; cd $nwd'
X
!FUNKY!STUFF!
echo extracting file Makefile
sed 's/^X//' > Makefile <<'!FUNKY!STUFF!'
X# Makefile for chd
X#
Xchd: chd.c
X	cc -o chd chd.c
!FUNKY!STUFF!
echo extracting file chd.c
sed 's/^X//' > chd.c <<'!FUNKY!STUFF!'
X/************************************************************/
X/*                                                          */
X/*  Copyright (c) 1986                                      */
X/*  Marc S. Majka - UBC Laboratory for Computational Vision */
X/*                                                          */
X/*  Permission is hereby granted to copy all or any part of */
X/*  this program for free distribution.   The author's name */
X/*  and this copyright notice must be included in any copy. */
X/*                                                          */
X/************************************************************/
X
X#include <stdio.h>
X#include <sgtty.h>
X#include <ctype.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <sys/dir.h>
X
Xmain(argc,argv)
Xint argc;
Xchar *argv[];
X{
X    struct sgttyb iobasic; /* Terminal stuff */
X    char c, *dirs[128], *malloc(), prompt[256], curr[256];
X    DIR *cwd, *try, *opendir();
X    struct direct *dent, *readdir();
X    struct stat sb;
X    int dn, i, j, comx, rc;
X
X    /* cbreak mode, no echo */
X    ioctl(fileno(stdin),TIOCGETP,&iobasic);
X    iobasic.sg_flags |= CBREAK;
X    iobasic.sg_flags &= ~ECHO;
X    ioctl(fileno(stdin),TIOCSETN,&iobasic);
X
X    getwd(curr);
X    strcat(curr,"/");
X    sprintf(prompt,"cd %s",curr);
X    fprintf(stderr,"? for help, ESC to quit\n\n");
X    fprintf(stderr,"%s",prompt);
X    
X    comx = 1;
X
X    while (comx >= 0) {
X        /* read current directory, save a list of subdirectories */
X        cwd = opendir(curr,"r");
X
X        dn = 0;
X
X        dent = readdir(cwd); /* skip "." */
X
X        while ((dent = readdir(cwd)) != NULL) {
X            stat(dent->d_name,&sb);
X
X            if ((sb.st_mode & S_IFMT) == S_IFDIR) { /* subdirectory */
X                /* Insert directory name in sorted list */
X                for (i = 0; i < dn && strcmp(dent->d_name,dirs[i]) > 0; i++);
X                for (j = dn; j >= i; j--) dirs[j+1] = dirs[j];
X                dirs[i] = malloc(dent->d_namlen + 1);
X                strcpy(dirs[i],dent->d_name);
X                dn++;
X            }
X        }
X        closedir(cwd);
X        
X        /* let user choose a subdirectory */
X        comx = getcom(dirs,dn,prompt);
X
X        if (comx >= 0) {
X
X            rc = chdir(dirs[comx]);
X            if (rc < 0) {
X                fprintf(stderr,"\n%s: Permission denied\n",dirs[comx]);
X                fprintf(stderr,"cd %s",curr);
X                comx = -1;
X            }
X
X            fprintf(stderr,"/");
X            sprintf(curr,"%s%s/",curr,dirs[comx]);
X        
X            if (!strcmp(dirs[comx],"..")) {
X                j = strlen(curr) - 2;
X                fprintf(stderr,"\b \b");
X                for (; (j > 0) && (curr[j] != '/'); j--) fprintf(stderr,"\b \b");
X                if (j > 0) { j--; fprintf(stderr,"\b \b"); }
X                for (; (j > 0) && (curr[j] != '/'); j--) fprintf(stderr,"\b \b");
X                curr[++j] = '\0';
X            }
X        
X            sprintf(prompt,"cd %s",curr);
X        }
X
X        for (i = 0; i < dn; i++) free(dirs[i]);
X    }
X    
X    fprintf(stderr,"\n");
X    curr[strlen(curr) - 1] = '\0';
X    printf("%s\n",curr);
X
X    /* reset and exit */
X    ioctl(fileno(stdin),TIOCGETP,&iobasic);
X    iobasic.sg_flags &= ~CBREAK;
X    iobasic.sg_flags |= ECHO;
X    ioctl(fileno(stdin),TIOCSETN,&iobasic);
X    exit(0);
X}
X
X/****************************************************************/
X/*                                                              */
X/* getcom: get a command (character string) from standard input */
X/* author: Marc Majka                                           */
X/* Copyright (c) Marc Majka 1985                                */
X/*                                                              */
X/* getcom takes a list of character strings in the same format  */
X/* as that used for argv, and a count of the number of strings  */
X/* in the list, as in argc.  It does EMACS-style string         */
X/* completion.  Getcom returns as its value, the index of the   */
X/* command.  -1 is returned if the user aborts the completion.  */
X/* Note that the command list "clist" must contain its strings  */
X/* in sorted order.  Also note that getcom expects to have the  */
X/* terminal in raw or cbreak mode, with no character echoing.   */
X/*                                                              */
X/* Characters are read and appended to a command string "cmd".  */
X/* Certain characters have special meanings:                    */
X/*                                                              */
X/* ? causes getcom to print all possible completions            */
X/* ^G and ESC abort and return -1                               */
X/* <space>, <tab>, and <return> cause getcom to complete        */
X/* <linefeed> causes completion to the first possible string    */
X/* <backspace> has the expected result                          */
X/* DEL clears the cmd string and restarts completion            */
X/*                                                              */
X/* NOTE TO HACKERS                                              */
X/*                                                              */
X/*    A print has been commented out in the "case '?'" near     */
X/*    the beginning of the routine.  Throw out the following    */
X/*    print and remove the comments.  The only difference is    */
X/*    that the first one includes a space.                      */
X/*                                                              */
X/****************************************************************/
X#include <stdio.h>
X
Xgetcom(clist,ncmd,prompt)
Xchar **clist;
Xint ncmd;
Xchar *prompt;
X{
X    int cn,first,last,match,endpt,start,i,ref,c;
X    char *entry,cmd[256];
X
X    match = 0;
X    endpt = 0;
X    start = 0;
X    first = 0;
X    last = ncmd - 1;
X    cmd[0] = '\0';
X    
X    while (!match) {
X        c = getchar();
X        switch(c) {
X
X            case '?':
X                match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
X                if (!match) {
X                    fprintf(stderr,"\n\n");
X                    helpcom(clist,first,last);
X/* HERE IT IS:      fprintf(stderr,"\n%s %s",prompt,cmd);        VANILLA */
X                    fprintf(stderr,"\n%s%s",prompt,cmd);  /* NON-VANILLA */
X                }
X                else {
X                    entry = clist[cn];
X                    for (i = endpt; entry[i] != '\0'; i++) 
X                        fprintf(stderr,"%c",entry[i]);
X                }
X                break;
X
X            case '\07': /* ^G */
X            case '\033': /* ESC */
X            case '\04': /* ^D */
X                return(-1);
X
X            case ' ': /* blank */
X            case '\t':  /* tab */
X            case '\r': /* return */
X                match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
X                if (match) {
X                    entry = clist[cn];
X                    for (i = endpt; entry[i] != '\0'; i++) 
X                        fprintf(stderr,"%c",entry[i]);
X                }
X                else fprintf(stderr,"%c",7);
X                break;
X
X            case '\n': /* newline */
X                match = try_match(&first,&last,&start,&endpt,&cn,clist,cmd);
X                if (!strcmp(clist[first],cmd)) {
X                    cn = first;
X                    match = 1;
X                }
X                if (!match) fprintf(stderr,"%c",7);
X                break;
X
X            case '\b': /* backspace */
X                if (endpt > 0) {
X                    fprintf(stderr,"\b \b");
X                    endpt--;
X                    cmd[endpt] = '\0';
X                }
X                start = 0;
X                first = 0;
X                last = ncmd - 1;
X                break;
X
X            case '\025':  /* ^U */
X                for (i = 0; i < endpt; i++) fprintf(stderr,"\b \b");
X                match = 0;
X                endpt = 0;
X                start = 0;
X                first = 0;
X                last = ncmd - 1;
X                cmd[0] = '\0';
X                break;
X
X            default: /* anything else */
X                fprintf(stderr,"%c",c);
X                cmd[endpt++] = c;
X                cmd[endpt] = '\0';
X                break;
X        }
X    }
X    return(cn);
X}
X
Xtry_match(f,l,s,p,n,clist,cmd)
Xint *f,*l,*s,*p,*n;
Xchar **clist,*cmd;
X{
X    int i,k,tf,tl;
X    char *fent,*lent;
X
X    if (*p == 0) return(0);
X    tf = *f; 
X    tl = *l;
X    k = *p + 1;
X    fent = clist[tf];
X    lent = clist[tl];
X
X    for (i = *s + 1; i < k; i++) {
X        fent = clist[tf];
X        while ((tf < tl) && (strncmp(cmd,fent,i) > 0)) {
X            tf += 1;
X            fent = clist[tf];
X        }
X        
X        lent = clist[tl];
X        while ((tl > tf) && (strncmp(cmd,lent,i) < 0)) {
X            tl -= 1;
X            lent = clist[tl];
X        }
X    }
X
X    if (tf == tl) {
X        if (strncmp(cmd,fent,*p) == 0) {
X            *f = tf; *l = tl; *s = *p; *n = tf;
X            return(1);
X        }
X        else {
X            i = *s;
X            while ((i < *p) && (cmd[i] == fent[i])) i++;
X            cmd[i] = '\0';
X            k = i;
X            for (; i < *p; i++) fprintf(stderr,"\b \b");
X            *p = k;
X            return(0);
X        }
X    }
X    else {
X        i = *p;
X        while (fent[i] == lent[i]) {
X            cmd[i] = fent[i];
X            fprintf(stderr,"%c",cmd[i]);
X            i++;
X        }
X        cmd[i] = '\0';
X        *f = tf; *l = tl; *p = i; *s = *p;
X        return(0);
X    }
X}
X
Xhelpcom(w,f,l)
Xchar **w;
Xint f,l;
X{
X    int max, i, len, cols, j;
X
X    max = 0;
X    for (i = f; i <= l; i++) {
X        len = strlen(w[i]) + 1;
X        if (len > max) max = len;
X    }
X    
X    cols = 78 / max;
X    
X    fprintf(stderr,"? choose one of the following\n");
X    
X    for (i = f; i <= l; i++) {
X        if (0 == ((i-f) % cols)) fprintf(stderr,"\n  ");
X        len = strlen(w[i]);
X        fprintf(stderr,"%s",w[i]);
X        for (j = len; j < max; j++) fprintf(stderr," ");
X    }
X    
X    fprintf(stderr,"\n\n");
X    fprintf(stderr,"? ^D, ^G or ESC to exit, ^U to clear\n");
X    fprintf(stderr,"? <space>, <tab> or <return> to complete\n");
X    fprintf(stderr,"? <linefeed> to insist on first choice\n");
X}
!FUNKY!STUFF!
echo extracting file chd.1
sed 's/^X//' > chd.1 <<'!FUNKY!STUFF!'
X.TH CHD 1
X.SH NAME
Xchd - choose directory with name completion
X.SH SYNOPSIS
X.B chd
X.SH DESCRIPTION
X.I chd
Xallows you to jump around in a directory tree in order to choose a new
Xdirectory.  The current directory is read and a list of subdirectories is
Xcompiled.  A name completion routine is used to allow the user to choose
Xa subdirectory, or .. for the parent directory.
X.sp
XThe name completion routine appends input characters to a string.  An
Xinput of <space>, <tab> or <return> causes it to try to match the current
Xstring against a list of known strings, in this case directory names.  The
Xinput string will be completed to the best possible match.  A <linefeed> will
Xcause the first possible match to be selected.  ESC, ^D, and ^G cause
Xthe routine to exit.
X.sp
XWhen the name completion routine exits, the
Xcurrectly chosen directory path is printed.  This can then be fed to cd
Xin the shell to accomplish the change.  For example:
X.br
Xalias cx 'set nwd=`chd`; cd $nwd'
X.SH LIMITATIONS
XAlways starts at the current directory.
X.SH AUTHOR
XMarc Majka
!FUNKY!STUFF!
echo Done\!
echo
exit 0