|
|
DataMuseum.dkPresents historical artifacts from the history of: DKUUG/EUUG Conference tapes |
This is an automatic "excavation" of a thematic subset of
See our Wiki for more about DKUUG/EUUG Conference tapes Excavated with: AutoArchaeologist - Free & Open Source Software. |
top - metrics - downloadIndex: T c
Length: 29213 (0x721d)
Types: TextFile
Names: »complete.c«
└─⟦a0efdde77⟧ Bits:30001252 EUUGD11 Tape, 1987 Spring Conference Helsinki
└─⟦this⟧ »EUUGD11/euug-87hel/sec8/mcp/src/complete.c«
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <signal.h>
#include <setjmp.h>
#include <strings.h>
#include <errno.h>
#include "sysdep.h"
#include "macros.h"
#include "mem.h"
#include "history.h"
#include "lists.h"
#ifdef BSD4_3
char *getwd();
#endif
#define swap(a, b) { char t; t=a; a=b; b=t; }
#define SCMPN(a, b) !strncmp((a), (b), strlen(a))
extern jmp_buf in_continue;
extern char Working_Directory[];
char *BACKSPACE = "\b \b", *dirof(), *fileof(), *pathcomplete();
static char line[BUFSIZ+1], hline[BUFSIZ+1];
struct hist currh = { hline, (char *)0, 0, 0, 0, 0, (struct list *)0 };
struct list History;
static char *exprv[] = { 0, 0 };
#ifdef SENDMAIL
extern struct list Aliases;
#endif
#ifdef HELPDIR
extern struct list Terms;
#endif
extern struct list AllCommands, Commands, Classes, Groups, Sigs;
extern struct list Null_List, Users, Vigs, Ranges;
extern int DevTty;
/*
* Get the last part of a command.
*/
char *
tail(s)
char *s;
{
char *p, *sp;
sp = rindex(s, ' ');
if (!sp) {
p = rindex(s, '-');
return p ? ++p : s;
}
else while (p = index(s, '-')) {
if (p > sp) break;
s = p + 1;
}
return s;
}
/*
* Count how many characters in s1 until s1 differs with s2
*/
int nmatch(s1, s2)
char *s1, *s2;
{
register int i;
for (i = 0; s1[i] && s2[i] && s1[i] == s2[i]; i++)
;
return(i);
}
/*
* Command and argument completion. The variable s will be changed to reflect
* what it was completed to. The variable *iscomplete will contain a 1 if s
* was expanded completely.
*
* One of these conditions will be satisfied and the apprpriate steps taken.
*
* 1) If c_list is empty return immediately.
* 2) If s is an empty string and c_list contains more than one string,
* return an empty string with no change.
* 3) If c_list only contains one string and s prefixes it then
* completely expand s to that string.
* 4) If s is exactly equal to one of the strings in c_list, merely
* report that s in completely expanded.
* 5) If s prefixes none of the strings in c_list then chop characters
* out of s until it will prefix at least one of the strings in c_list.
* 6) If s uniquely prefixes a string in c_list then completely expand
* s to that string.
* 7) If s prefixes more than one string in c_list expand s to the point
* where the strings differ.
*
* Note: the strings in c_list MUST ALREADY be sorted in collating
* sequence!
*/
char *
complete(s, c_list, iscomplete)
char *s;
struct list *c_list;
int *iscomplete;
{
static char delta[LONG_BUF * 2];
int i, lindex, n_matched, s_len, found;
delta[0] = '\0'; *iscomplete = 0;
/*
* Check for condition 1. If no completion list, forget it.
*/
if (c_list->l_count == 0)
return delta;
/*
* Check for condition 2. If s is empty and there is more than one
* word in the completion list, forget it.
*/
if (*s == '\0' && c_list->l_count > 1)
return delta;
/*
* Check for condition 3. If there is only one word in the
* completion list and s prefixes it, this is the one.
*/
s_len = strlen(s);
if (c_list->l_count==1&&!strncmp(s,(char *)c_list->l_list[0],s_len)) {
(void) strcpy(delta, &(((char *)c_list->l_list[0])[s_len]));
(void) strcat(delta, " ");
(void) strcat(s, delta);
*iscomplete = 1;
return delta;
}
/*
* Binary search the completion list.
* If s is not found, all the strings that s prefixes (if any)
* will have indices >= lindex .
*/
lindex = search_list(c_list, s, strcmp, &found);
/*
* Check for condition 4. If we got a perfect match, skidaddle.
*/
if (found) {
(void) strcpy(delta, " ");
(void) strcat(s, delta);
*iscomplete = 1;
return delta;
}
/*
* Check for condition 5. Hacksaw the garbage until we recognize
* something.
*/
n_matched = 0;
for (i=lindex-1; i < lindex+2; i++) {
if (i < 0)
continue;
if (i >= c_list->l_count)
break;
n_matched=max(n_matched, nmatch(s,(char *)c_list->l_list[i]));
/*
* If s prefixes c_list->l_list[lindex] or one of the words
* adjacent to it, we set lindex to its index so that lindex
* now indexes to the first word that s prefixes.
*/
if (n_matched == s_len) {
lindex = i;
break;
}
}
if (n_matched < s_len) {
for (i=0; i < s_len - n_matched; i++)
(void) strcat(delta, BACKSPACE);
s[n_matched] = '\0';
return delta;
}
/*
* Check for condition 6. If s can be unambigously expanded
* then do so.
*/
if (lindex+1 == c_list->l_count ||
strncmp((char *)c_list->l_list[lindex],
(char *)c_list->l_list[lindex+1], s_len)) {
(void) strcpy(delta, ((char *)c_list->l_list[lindex])+s_len);
(void) strcat(delta, " ");
(void) strcat(s, delta);
*iscomplete = 1;
return delta;
}
/*
* Gotta be condition 7. Expand as far as possible, but
* don't set *iscomplete.
*/
for (i=lindex+1 ;; i++) {
if (i == c_list->l_count) {
break;
}
if (strncmp(s, (char *)c_list->l_list[i], s_len) != 0)
break;
}
i--;
n_matched = nmatch((char *)c_list->l_list[lindex],
(char *)c_list->l_list[i]);
(void) strncpy(delta, ((char *)c_list->l_list[lindex])+s_len,
n_matched-s_len);
delta[n_matched - s_len] = '\0';
(void) strcat(s, delta);
return delta;
}
redraw(prompt, s)
char *prompt, *s;
{
char_scr('\r');
str_scr(prompt);
str_scr(s);
return;
}
#include <ctype.h>
/*
* If this variable is non-zero then it must contain the length of
* the latest visible completion help tag, e.g. "[Ambiguous]".
* If zero then there is no floating completion tag at this time.
*/
static int MustEraseTag;
char
GET()
{
extern int errno;
int count, nready, rmask = 1;
struct timeval t, *timeout = 0;
char c;
errno = 0;
for (;;) {
rmask = 1;
if (MustEraseTag) {
t.tv_sec = 1;
t.tv_usec = 250000;
timeout = &t;
}
#ifdef BSD4_3
nready = select(1, (fd_set*)&rmask, (fd_set *)0, (fd_set *)0, timeout);
#else
nready = select(1, &rmask, 0, 0, timeout);
#endif
if (nready == -1 && errno != EINTR) {
perr("select");
panic((char *)0);
}
if (nready == 0 || MustEraseTag) {
erasetag();
timeout = 0;
if (!nready)
continue;
}
count = read(0, &c, 1);
if (count == 1)
break;
if (count == 0)
panic("EOF encountered");
if (count == -1 && errno != EINTR) {
perr("read");
panic((char *)0);
}
}
c &= 0177;
return(c);
}
showtag(tag)
char *tag;
{
register int i;
MustEraseTag = strlen(tag) + 1;
char_scr(' ');
str_scr(tag);
for (i=0; i < MustEraseTag; i++)
char_scr('\b');
return;
}
erasetag()
{
register int i;
for (i=0; i < MustEraseTag; i++)
char_scr(' ');
for (i=0; i < MustEraseTag; i++)
char_scr('\b');
MustEraseTag = 0;
return;
}
/*
* Figure out which argument vector to use for argument completion.
*/
struct list *
picklist(cmd)
char *cmd;
{
cmd = tail(cmd);
if (SCMPN("user", cmd))
return(&Users);
else if (SCMPN("sig", cmd))
return(&Sigs);
else if (SCMPN("class", cmd))
return(&Classes);
else if (SCMPN("group", cmd))
return(&Groups);
else if (SCMPN("range", cmd))
return(&Ranges);
else if (SCMPN("vig", cmd))
return(&Vigs);
else if (SCMPN("command", cmd))
return(&AllCommands);
#ifdef SENDMAIL
else if (SCMPN("alias", cmd))
return(&Aliases);
#endif
#ifdef HELPDIR
else if (SCMPN("is", cmd))
return(&Terms);
#endif
else
return(&Null_List);
}
#ifdef sun
#define sighandler (void (*)())
#else
#define sighandler (int (*)())
#endif
#ifdef sun
void
#endif
tstp_cleanup()
{
char_scr('\r');
nocbreak();
(void) signal(SIGTSTP, sighandler SIG_DFL);
(void) kill(getpid(), SIGTSTP);
return;
}
#ifdef sun
void
#endif
input_continue()
{
cbreak();
(void) signal(SIGTSTP, tstp_cleanup);
longjmp(in_continue, SIGCONT);
}
/*
* Get a line of input using command and smart argument completion.
*/
GetCommandLine(prompt, nargs, argc, argv)
char *prompt;
int nargs, *argc;
addr *argv;
{
addr *tmpargv;
char buf[LONG_BUF], *p;
int c, iscomplete, indx, windex, hindex, i, end_of_line, h_len, d;
int quote_open;
struct list *c_list;
struct hist *hi, hh;
cbreak();
MustEraseTag = 0;
exprv[0] = buf;
*argc = 0;
*line = '\0';
quote_open = end_of_line = windex = indx = 0;
hindex = History.l_count;
c_list = &Commands;
(void) signal(SIGTSTP, tstp_cleanup);
(void) signal(SIGCONT, input_continue);
(void) setjmp(in_continue);
str_scr(prompt);
while (!end_of_line && indx < BUFSIZ) {
c = GET();
if (setjmp(in_continue) == SIGCONT) {
redraw(prompt, line);
continue;
}
switch (c) {
case ':':
break; /* ignore colons for pwd, group, etc. files */
/*
* ^P goes one step back in the history list.
*/
case '\020':
if (hindex == 0) {
showtag("[History begins here...]");
break;
}
if (hindex == History.l_count) {
(void) strcpy(currh.h_line, line);
currh.h_prompt = prompt;
currh.h_argc = *argc;
currh.h_index = indx;
currh.h_windex = windex;
currh.h_qopen = quote_open;
currh.h_list = c_list;
}
hi = (struct hist *) History.l_list[--hindex];
h_len = strlen(line) + strlen(prompt);
for (i=strlen(hi->h_line); i < h_len; i++)
str_scr(BACKSPACE);
(void) strcpy(line, hi->h_line);
prompt = hi->h_prompt;
*argc = hi->h_argc;
indx = hi->h_index;
windex = hi->h_windex;
quote_open = hi->h_qopen;
c_list = hi->h_list;
redraw(prompt, line);
break;
/*
* ^N goes one step forward in the history list.
*/
case '\016':
if (!History.l_count || hindex == History.l_count) {
showtag("[End of history]");
break;
}
if (hindex == History.l_count-1) {
h_len = strlen(line) + strlen(prompt);
i=strlen(currh.h_line)+strlen(currh.h_prompt);
for (; i < h_len; i++)
str_scr(BACKSPACE);
(void) strcpy(line, currh.h_line);
prompt = currh.h_prompt;
*argc = currh.h_argc;
indx = currh.h_index;
windex = currh.h_windex;
quote_open = currh.h_qopen;
c_list = currh.h_list;
redraw(prompt, line);
hindex++;
break;
}
hi = (struct hist *) History.l_list[++hindex];
h_len = strlen(line) + strlen(prompt);
i = strlen(hi->h_line) + strlen(hi->h_prompt);
for (; i < h_len; i++)
str_scr(BACKSPACE);
(void) strcpy(line, hi->h_line);
prompt = hi->h_prompt;
*argc = hi->h_argc;
indx = hi->h_index;
windex = hi->h_windex;
quote_open = hi->h_qopen;
c_list = hi->h_list;
redraw(prompt, line);
break;
/*
* For space bar, do completion only for the
* command (first) word. Allow normal spaces
* only at the end of a word.
*/
case ' ':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (windex != 0 && windex != indx) {
char_scr(c);
line[indx] = c;
line[indx + 1] = '\0';
indx++;
if (!quote_open) {
windex = indx;
(*argc)++;
}
break;
}
else if (windex == indx) /* balk at adjacent spaces */
break;
else
/* FALL THROUGH */
;
/*
* Word is completion done here. Completion is activated
* by the TAB or the ESC key. Activation by CR or LF only
* if completing first word.
*/
case '\r':
case '\n':
if (*argc != 0) {
end_of_line++;
break;
}
if (nargs && *argc == nargs) {
end_of_line++;
break;
}
/* FALL THROUGH */
case '\t':
case '\033':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
p = complete(&line[windex], c_list, &iscomplete);
str_scr(p);
if (iscomplete) {
if (windex == 0)
c_list = picklist(line);
windex = strlen(line);
(*argc)++;
}
else if (*p == '\0' && indx != windex)
showtag("[Ambiguous]");
indx = strlen(line);
if ((c == '\r' || c == '\n') && iscomplete)
end_of_line++;
break;
/*
* Ctrl-T transposes the two characters before the cursor
*/
case '\024':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
/* not enough characters */
if (indx < 2)
break;
/* can't let space to be first character on a line */
if (indx == 2 && line[1] == ' ')
break;
swap(line[indx-1], line[indx-2]);
/*
* If one of the transposed characters was a space,
* we have either broken a word in two or merged
* the current word with the preceding one.
*/
if (line[indx-2] == ' ')
if (!quote_open && line[indx-1] == '"') {
(*argc)++, windex--;
c_list = picklist(line);
}
else if (line[indx-1] == ' ')
if (!quote_open && line[indx-2] == '"') {
(*argc)--, windex++;
c_list = (*argc == 0) ?
&Commands : picklist(line);
}
str_scr("\b \b\b \b");
str_scr(&line[indx-2]);
break;
/*
* Show user possiblities if he wonders why
* a word isn't completing.
*/
case '?':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
str_scr("\r\n");
(void) strcpy(buf, "^");
(void) strcat(buf, &line[windex]);
(void) strcat(buf, ".*");
nocbreak();
/*
* We don't want completion info going to stdout
* so we temporarily connect stdout to /dev/tty
*/
d = dup(1);
(void) dup2(DevTty, 1);
(void) showlist(c_list, (addr *)exprv);
/*
* Now re-connect stdout to whatever is was before
*/
(void) dup2(d, 1);
(void) close(d);
cbreak();
redraw(prompt, line);
break;
/*
* Ctrl-R simply reprints the command line.
*/
case '\022':
redraw(prompt, line);
break;
/*
* Ctrl-W is accepted as the word-kill character.
*/
case '\027':
if (indx == windex && windex != 0) {
for (windex -= 2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
if (windex == 0)
c_list = &Commands;
for (; indx > windex; indx--)
str_scr(BACKSPACE);
line[indx] = '\0';
break;
/*
* Backspace (^H) and del are accepted as erase
* characters, with the screen eraseure being
* accomplished via backspace-space-backpsace
* sequences
*/
case '\177':
case '\b':
if (--indx < windex) {
for (windex-=2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
if (windex == 0)
c_list = &Commands;
if (indx >= 0) {
if (line[indx] == '"')
quote_open = !quote_open;
line[indx] = '\0';
str_scr(BACKSPACE);
}
else
indx = 0;
break;
/*
* Ctrl-X and ctrl-U are accepted as line kill
* characters.
*/
case '\030':
case '\025':
if (indx == 0)
break;
for (; indx>0; indx--)
str_scr(BACKSPACE);
quote_open = windex = indx = 0;
*line = '\0';
c_list = &Commands;
*argc = 0;
break;
default:
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (c == '"')
quote_open = !quote_open;
/*
* Ignore unrecognized control characters
*/
if (isprint(c)) {
char_scr(c);
line[indx] = c;
line[indx + 1] = '\0';
indx++;
}
break;
}
}
(void) signal(SIGCONT, sighandler SIG_DFL);
(void) signal(SIGTSTP, sighandler SIG_DFL);
if (quote_open) {
char_scr('"');
line[indx++] = '"';
line[indx] = '\0';
quote_open = 0;
}
critical();
savestr(&hh.h_prompt, prompt);
savestr(&hh.h_line, line);
hh.h_argc = *argc;
hh.h_index = indx;
hh.h_windex = windex;
hh.h_qopen = quote_open;
hh.h_list = c_list;
trimhist();
genlistadd(&History, (addr)&hh, sizeof (struct hist));
non_critical();
if (indx > windex)
(*argc)++;
(void) cut(line);
tmpargv = mkargv(line, *argc);
for (i=0; tmpargv[i]; i++)
savestr((char **)&argv[i], (char *)tmpargv[i]);
argv[i] = NIL;
str_scr("\r\n");
nocbreak();
return;
}
GetLine(prompt, nargs, argc, argv, c_list)
char *prompt;
int nargs, *argc;
addr *argv;
struct list *c_list;
{
addr *tmpargv;
char buf[LONG_BUF], *p;
int c, iscomplete, indx, windex, i, d, quote_open;
cbreak();
exprv[0] = buf;
MustEraseTag = 0;
*argc = quote_open = 0;
*line = '\0';
windex = indx = 0;
(void) signal(SIGTSTP, tstp_cleanup);
(void) signal(SIGCONT, input_continue);
(void) setjmp(in_continue);
str_scr(prompt);
while ((c = GET()) != '\n' && indx < BUFSIZ) {
if (setjmp(in_continue) == SIGCONT) {
redraw(prompt, line);
continue;
}
if (c == '\r')
break;
switch ( c ) {
case ':':
break; /* ignore colons for pwd, group, etc. files */
case ' ':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (windex != indx) {
char_scr(c);
line[indx] = c;
line[indx+1] = '\0';
indx++;
if (!quote_open) {
windex = indx;
(*argc)++;
}
}
break;
case '\t':
case '\033':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
p = complete(&line[windex], c_list, &iscomplete);
str_scr(p);
if (iscomplete) {
windex = strlen(line);
(*argc)++;
}
else if (*p == '\b')
showtag("[No match]");
else if (*p == '\0' && indx != windex)
showtag("[Ambiguous]");
indx = strlen(line);
break;
/*
* Ctrl-T transposes the two characters before the cursor
*/
case '\024':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (indx < 2)
break;
if (indx == 2 && line[1] == ' ')
break;
swap(line[indx-1], line[indx-2]);
/*
* If one of the transposed characters was a space,
* we have either broken a word in two or merged
* the current word with the preceding one.
*/
if (line[indx-2] == ' ')
if (!quote_open && line[indx-1] == '"')
(*argc)++, windex--;
else if (line[indx-1] == ' ')
if (!quote_open && line[indx-2] == '"')
(*argc)--, windex++;
str_scr("\b \b\b \b");
str_scr(&line[indx-2]);
break;
/*
* Show user possiblities if he wonders why
* a word isn't completing.
*/
case '?':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
str_scr("\r\n");
(void) strcpy(buf, "^");
(void) strcat(buf, &line[windex]);
(void) strcat(buf, ".*");
nocbreak();
/*
* We don't want completion info going to stdout
* so we temporarily connect stdout to /dev/tty
*/
d = dup(1);
(void) dup2(DevTty, 1);
(void) showlist(c_list, (addr *)exprv);
/*
* Now re-connect stdout to whatever is was before
*/
(void) dup2(d, 1);
(void) close(d);
cbreak();
redraw(prompt, line);
break;
/*
* Ctrl-R simply reprints the command line.
*/
case '\022':
redraw(prompt, line);
break;
/*
* Ctrl-W is accepted as the word-kill character.
*/
case '\027':
if (indx == windex && windex != 0) {
for (windex -= 2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
for (; indx > windex; indx--)
str_scr(BACKSPACE);
line[indx] = '\0';
break;
/*
* Backspace (^H) and del are accepted as erase
* characters, with the screen erasure being
* accomplished via backspace-space-backpsace
* sequences
*/
case '\177':
case '\b':
if ( --indx < windex ) {
for (windex-=2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
if (indx >= 0) {
if (line[indx] == '"')
quote_open = !quote_open;
line[indx] = '\0';
str_scr(BACKSPACE);
}
else
indx = 0;
break;
/*
* Ctrl-X and ctrl-U are accepted as line kill
* characters.
*/
case '\030':
case '\025':
if (indx == 0)
break;
for (; indx>0; indx--)
str_scr(BACKSPACE);
quote_open = windex = indx = 0;
*line = '\0';
*argc = 0;
break;
default:
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (c == '"')
quote_open = !quote_open;
/*
* Ignore unrecognized control characters
*/
if (isprint(c)) {
char_scr(c);
line[indx] = c;
line[indx+1] = '\0';
indx++;
}
break;
}
}
(void) signal(SIGCONT, sighandler SIG_DFL);
(void) signal(SIGTSTP, sighandler SIG_DFL);
if (quote_open) {
char_scr('"');
line[indx++] = '"';
line[indx] = '\0';
}
if (indx > windex)
(*argc)++;
(void) cut(line);
tmpargv = mkargv(line, *argc);
for (i=0; tmpargv[i]; i++)
savestr((char **)&argv[i], (char *)tmpargv[i]);
argv[i] = NIL;
str_scr("\r\n");
nocbreak();
return;
}
trimhist()
{
struct hist *h;
if (History.l_count <= MAXHIST) return;
h = (struct hist *) listpop(&History);
FREEMEM(h->h_line);
FREEMEM((char *)h);
return;
}
GetFilenames(prompt, nargs, argc, argv)
char *prompt;
int nargs, *argc;
addr *argv;
{
addr *tmpargv;
char buf[LONG_BUF], *p;
int c, iscomplete, indx, windex, i, d, quote_open;
static struct list c_list;
exprv[0] = buf;
MustEraseTag = 0;
*argc = 0;
*line = '\0';
quote_open = windex = indx = 0;
zerolist(&c_list);
tmplistadd(&c_list);
cbreak();
(void) signal(SIGTSTP, tstp_cleanup);
(void) signal(SIGCONT, input_continue);
(void) setjmp(in_continue);
str_scr(prompt);
while ((c = GET()) != '\n' && indx < BUFSIZ) {
if (setjmp(in_continue) == SIGCONT) {
redraw(prompt, line);
continue;
}
if (c == '\r')
break;
switch ( c ) {
case ' ':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (windex != indx) {
char_scr(c);
line[indx] = c;
line[indx+1] = '\0';
indx++;
if (!quote_open) {
windex = indx;
(*argc)++;
}
}
break;
case '\t':
case '\033':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
p = pathcomplete(&line[windex], &c_list, &iscomplete);
str_scr(p);
if (iscomplete) {
windex = strlen(line);
(*argc)++;
}
else if (*p == '\0' && indx != windex)
showtag("[Ambiguous]");
indx = strlen(line);
break;
/*
* Ctrl-T transposes the two characters before the cursor
*/
case '\024':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (indx < 2)
break;
if (indx == 2 && line[1] == ' ')
break;
swap(line[indx-1], line[indx-2]);
/*
* If one of the transposed characters was a space,
* we have either broken a word in two or merged
* the current word with the preceding one.
*/
if (line[indx-2] == ' ')
if (!quote_open && line[indx-1] == '"')
(*argc)++, windex--;
else if (line[indx-1] == ' ')
if (!quote_open && line[indx-2] == '"')
(*argc)--, windex++;
str_scr("\b \b\b \b");
str_scr(&line[indx-2]);
break;
/*
* Show user possiblities if he wonders why
* a word isn't completing.
*/
case '?':
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
str_scr("\r\n");
freelist(&c_list);
dirscan(dirof(&line[windex]), &c_list);
(void) strcpy(buf, "^");
(void) strcat(buf, fileof(&line[windex]));
(void) strcat(buf, ".*");
nocbreak();
/*
* We don't want completion info going to stdout
* so we temporarily connect stdout to /dev/tty
*/
d = dup(1);
(void) dup2(DevTty, 1);
(void) showlist(&c_list, (addr *)exprv);
/*
* Now re-connect stdout to whatever is was before
*/
(void) dup2(d, 1);
(void) close(d);
cbreak();
redraw(prompt, line);
break;
/*
* Ctrl-R simply reprints the command line.
*/
case '\022':
redraw(prompt, line);
break;
/*
* Ctrl-W is accepted as the word-kill character.
*/
case '\027':
if (indx == windex && windex != 0) {
for (windex -= 2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
for (; indx > windex; indx--)
str_scr(BACKSPACE);
line[indx] = '\0';
break;
/*
* Backspace (^H) and del are accepted as erase
* characters, with the screen erasure being
* accomplished via backspace-space-backpsace
* sequences
*/
case '\177':
case '\b':
if ( --indx < windex ) {
for (windex-=2; windex >= 0; windex--) {
if (line[windex] == '"') {
quote_open = !quote_open;
continue;
}
if (!quote_open&& line[windex] == ' ')
break;
}
windex++;
if (windex < 0)
windex = 0;
decr(*argc);
}
if (indx >= 0) {
if (line[indx] == '"')
quote_open = !quote_open;
line[indx] = '\0';
str_scr(BACKSPACE);
}
else
indx = 0;
break;
/*
* Ctrl-X and ctrl-U are accepted as line kill
* characters.
*/
case '\030':
case '\025':
if (indx == 0)
break;
for (; indx>0; indx--)
str_scr(BACKSPACE);
quote_open = windex = indx = 0;
*line = '\0';
*argc = 0;
break;
default:
if (nargs && *argc == nargs) {
showtag("[Press RETURN]");
break;
}
if (c == '"')
quote_open = !quote_open;
/*
* Ignore unrecognized control characters
*/
if (isprint(c)) {
char_scr(c);
line[indx] = c;
line[indx+1] = '\0';
indx++;
}
break;
}
}
(void) signal(SIGCONT, sighandler SIG_DFL);
(void) signal(SIGTSTP, sighandler SIG_DFL);
if (quote_open) {
char_scr('"');
line[indx++] = '"';
line[indx] = '\0';
}
if (indx > windex)
(*argc)++;
(void) cut(line);
tmpargv = mkargv(line, *argc);
for (i=0; tmpargv[i]; i++)
savestr((char **)&argv[i], (char *)tmpargv[i]);
argv[i] = NIL;
str_scr("\r\n");
nocbreak();
return;
}
char *
pathcomplete(path, c_list, iscomplete)
char *path;
struct list *c_list;
int *iscomplete;
{
static char delta[LONG_BUF];
char buf[LONG_BUF], *dir, *file;
int n_matched, d_len, i;
delta[0] = '\0';
(void) strcpy(buf, path);
/*
* If we can't chdir to the directory prefix of the path, then
* we hack away parts of the path until we can or it's all
* gone. Note that this depends on the fact within UNIX "\0"
* is synonymous with "."
*/
dir = dirof(buf);
if (chdir(dir) == -1) {
diraxe(buf);
while (*buf && chdir(buf) == -1)
diraxe(buf);
(void) getwd(buf);
/*
* If this isn't the root directory, add a slash
*/
if (!eq(buf, "/"))
(void) strcat(buf, "/");
/* note changes and store necessary cursor motions */
n_matched = nmatch(path, buf);
d_len = strlen(path);
for (i=d_len; i > n_matched; i--)
(void) strcat(delta, BACKSPACE);
(void) strcat(delta, buf+n_matched);
(void) strcpy(path, buf);
(void) chdir(Working_Directory);
*iscomplete = 0;
return delta;
}
/*
* Chdir'ed successfully so now we make the completion list.
*/
freelist(c_list);
dirscan(".", c_list);
/* do completion of the file suffix of the path */
file = fileof(path);
(void) complete(file, c_list, iscomplete);
/*
* Shed . , .. and symlinks
*/
(void) getwd(buf);
/*
* If this isn't the root directory, add a slash
*/
if (!eq(buf, "/"))
(void) strcat(buf, "/");
/*
* Add the result of file name completion
*/
(void) strcat(buf, file);
/*
* Now note the difference between the what was passed in and
* what we have now and put the necessary cursor movements into
* delta[]
*/
n_matched = nmatch(path, buf);
d_len = strlen(path);
for (i=d_len; i > n_matched; i--)
(void) strcat(delta, BACKSPACE);
(void) strcat(delta, buf+n_matched);
/* get rid of side effects */
(void) chdir(Working_Directory);
/* save results */
(void) strcpy(path, buf);
/*
* If the filename completed was in fact a directory then we append
* a slash to the file name and note that the completion didn't
* result in a regular file name (*iscomplete = 0).
*/
if (*iscomplete) {
d_len = strlen(path) - 1;
path[d_len] = '/';
if (isdir(path)) {
delta[strlen(delta)-1] = '/';
*iscomplete = 0;
return delta;
}
path[d_len] = ' ';
}
return delta;
}
char *
dirof(path)
char *path;
{
register char *cp;
static char buf[LONG_BUF];
(void) strcpy(buf, path);
cp = rindex(buf, '/');
if (cp)
*++cp = '\0';
else
*buf = '\0';
return buf;
}
char *
fileof(path)
char *path;
{
register char *cp;
static char buf[LONG_BUF];
(void) strcpy(buf, path);
cp = rindex(buf, '/');
return cp ? cp+1 : buf;
}
/*
* Hack off the last directory in a path. The path should end with '/'
* for this to work properly.
*/
diraxe(path)
char *path;
{
register char *cp;
cp = rindex(path, '/');
if (cp)
*cp = '\0';
else {
*path = '\0';
return;
}
cp = rindex(path, '/');
if (cp)
*++cp = '\0';
else
*path = '\0';
return;
}