/* hsh -- ZCPR3 history shell */ /* copyright 1985-6 Michael M Rubenstein */ /* version 1.5 9 Apr 86 */ /* Modifications */ /* 1.1 (MMR) Fixed handling of firsthist on wrap around. Previous */ /* did not reset to 0, which could result in the program hanging */ /* if a search was done for a leading string which was not in the */ /* history. */ /* 1.2 (MMR) Don't put signon message if ZCPR3 quiet flag set. */ /* Don't execute comments (lines beginning with ";") */ /* 1.3 (MMR) Correct prompt for user number 10. */ /* 1.4 (MMR) Correct bug in handling user number of HSH.VAR. */ /* Add patches to put HSH.VAR in fixed location and for insert */ /* mode. */ /* 1.5 (MMR) Don't allow command line larger than ZCPR3 command */ /* buffer. Add installation of prompt string. */ #include #include #define SHNAME "HSH.COM" /* default name */ #define VARFILE "HSH.VAR" /* history file */ #define DEFNHIST 10 /* default number of history lines */ /* some interesting characters */ #define CTL 0x1f #define NUL 0 #define CTLA (CTL & 'A') #define CTLC (CTL & 'C') #define CTLD (CTL & 'D') #define CTLE (CTL & 'E') #define CTLF (CTL & 'F') #define CTLG (CTL & 'G') #define BEL CTLG #define BS (CTL & 'H') #define CTLJ (CTL & 'J') #define LF CTLJ #define CR (CTL & 'M') #define CTLL (CTL & 'L') #define CTLP (CTL & 'P') #define CTLQ (CTL & 'Q') #define CTLR (CTL & 'R') #define CTLS (CTL & 'S') #define CTLT (CTL & 'T') #define CTLU (CTL & 'U') #define CTLV (CTL & 'V') #define CTLW (CTL & 'W') #define CTLX (CTL & 'X') #define CTLY (CTL & 'Y') #define CTLZ (CTL & 'Z') #define ESC (CTL & '[') #define DEL 0x7f extern unsigned z3env; /* pointer to ZCPR3 environment */ extern FCB dfcb_; /* default CP/M FCB */ int curdir = FALSE; /* if TRUE, search current dir */ int rdrive = 0, /* root drive/user */ ruser = 0; int vmode = FALSE; /* if TRUE, insert is mode */ int insert = FALSE; /* if TRUE, inserting */ int nhist = DEFNHIST; /* number of history lines */ char uprompt[49] = "$D$U$:$N>"; int firsthist = 0; /* index of first history line */ int lasthist = 0; /* index of one past last line */ int drive, user; /* current drive/user */ int linepos; /* position on CRT line */ int maxpos; /* max pos on CRT line used */ int maxcols; /* columns on CRT display */ char *cur; /* pointer to current char in */ /* line being edited */ char *line; /* pointer to start of line */ /* being edited */ char *maxcur; /* end of line */ char *gethline(); /* get a line from the history */ char *puthline(); /* put a line to the history */ FCB *gefcb(); /* get ZCPR3 external FCB */ char *getcrt(); /* get ZCPR3 terminal data */ main() { static FCB *efcb; /* FCB for shell name */ static char shname[13]; /* shell name */ static char *s; int du; static int c; static int i; static int n = -1; /* value of command line arg */ char aline[256]; line = aline; /* cheap way to allocate mem */ maxcur = line + gcl1() - 12; /* end of line */ /* allow room for extended command */ /* processor */ /* if there is a command line argument, evaluate and save it */ if (isdigit(dfcb_.name1.fname[0])) { n = atoi(dfcb_.name1.fname); if (n < 0) error("hsh: number of history lines too large."); } else if (dfcb_.name1.fname[0] != ' ') error("syntax: hsh []"); if (z3env == 0) error("hsh: ZCPR3 required."); /* abort if no external command line */ if (!getcl1()) error("hsh: ZCPR3 external command line required."); /* find where we are and where root dir is for later use */ drive = curdsk(); user = getusr(); if (!rdrive) { du = groot(); rdrive = (du >> 8) + 1; ruser = du & 0x0f; } /* if not invoked as a shell, install as shell */ if (!qsh()) { /* if no external FCB, use default name */ if ((efcb = gefcb()) == NULL) { setfcb(SHNAME, &dfcb_); efcb = &dfcb_; } /* find where program is and set up name with drive/user */ if ((du = pfnd(efcb, curdir)) == -1) error("hsh: Cannot find shell program."); shname[0] = (du >> 8) + 'A'; du &= 0xff; shname[1] = du / 10 + '0'; shname[2] = du % 10 + '0'; shname[3] = ':'; for (i = 0; i < 8; ++i) { if ((c = (efcb->name1.fname[i] & 0x7f)) == ' ') break; shname[4 + i] = c; } shname[4 + i] = '\0'; if (shpsh(shname)) error("hsh: Cannot install shell."); if (!getq()) ps("HSH Version 1.5 (9 Apr 86) installed\n"); /* if a command line arg was entered, set up history file with number */ /* of lines to keep */ if (n >= 0) { nhist = n; /* go to the right place */ select(rdrive - 1); setusr(ruser); setfcb(VARFILE, &dfcb_); delete(&dfcb_); if (make(&dfcb_) == 0xff) sherror("hsh: Cannot create HSH.VAR."); puthist(); } /* exit after installing shell, so will work from submit file */ return; } /* set ZCPR3 command status message so next program won't think it's */ /* a shell (needed since we don't warm boot on exit) */ pcst(0); clcl(); /* clear command line */ maxcols = *getcrt() & 0xff; gethist(); /* get history */ getcmd(); /* handle a command */ puthist(); /* save history */ } /* get the history */ gethist() { /* first record in history file keeps parameters */ struct { int f_nhist, f_fhist, f_lhist; char f_fill[126]; } fnhist; /* go to the right place */ select(rdrive - 1); setusr(ruser); setfcb(VARFILE, &dfcb_); /* if there's a history file, read it in and set firsthist and lasthist */ if (open(&dfcb_) != 0xff) { setdma(&fnhist); if (!rdseq(&dfcb_)) { nhist = fnhist.f_nhist; firsthist = fnhist.f_fhist; lasthist = fnhist.f_lhist; } } else /* if no history file, create it */ if (make(&dfcb_) == 0xff) sherror("hsh: Cannot create HSH.VAR."); } /* put the history paramters to file */ puthist() { /* first record in history file keeps parameters */ struct { int f_nhist, f_fhist, f_lhist; char f_fill[126]; } fnhist; fnhist.f_nhist = nhist; fnhist.f_fhist = firsthist; fnhist.f_lhist = lasthist; setdma(&fnhist); dfcb_.rrec = 0; if (wrran(&dfcb_)) sherror("hsh: Error writing HSH.VAR."); if (close(&dfcb_) == 0xff) sherror("hsh: Error closing HSH.VAR"); /* return to where we were */ select(drive); setusr(user); } /* get a history line. returns pointer to line for convenience. */ char *gethline(line, n) char *line; int n; { dfcb_.rrec = 2 * n + 1; setdma(line); if (rdran(&dfcb_)) sherror("hsh: Cannot read HSH.VAR."); ++dfcb_.rrec; setdma(line + 128); if (rdran(&dfcb_)) sherror("hsh: Cannot read HSH.VAR."); return line; } /* put a history line. returns pointer to line for convenience. */ char *puthline(line, n) char *line; int n; { dfcb_.rrec = 2 * n + 1; setdma(line); if (wrran(&dfcb_)) sherror("hsh: Cannot write HSH.VAR."); ++dfcb_.rrec; setdma(line + 128); if (wrran(&dfcb_)) sherror("hsh: Cannot write HSH.VAR."); return line; } /* get a command and pass to CCP */ getcmd() { static char *cmd; for (;;) { pzex(1); /* mark prompt for ZEX */ getline(); /* get a line */ pzex(0); for (cmd = line; isspace(*cmd); ++cmd) /* skip spaces at start */ ; if (*cmd == '\0') continue; puthline(line, lasthist); if (++lasthist > nhist) /* advance history */ lasthist = 0; if (lasthist == firsthist) ++firsthist; if (firsthist > nhist) firsthist = 0; if (*cmd == ';') continue; if (pcl(cmd)) /* pass to CCP */ return; } } /* output prompt */ putprompt() { static char *dirname; static int c; static char *s; static int lower; dirname = dutd((drive << 8) + user); for (s = uprompt; *s; ++s) if (*s == '$') { lower = islower(c = *++s); switch (toupper(c)) { case 'D': putdrv(lower); break; case 'U': putusr(); break; case 'V': if (user != 0) putusr(); break; case 'M': if (dirname == NULL) { putdrv(lower); putusr(); break; } case 'N': putdir(dirname, lower); break; case ':': if (dirname != NULL) pc(':'); break; case '\0': --s; default: pc(*s); } } else pc(*s); } /* put drive */ putdrv(lower) int lower; { pc(drive + (lower ? 'a' : 'A')); } /* put user */ putusr() { if (user > 9) pc(user / 10 + '0'); pc(user % 10 + '0'); } /* put directory name */ putdir(dirname, lower) char *dirname; int lower; { static int c; static int i; if (dirname != NULL) for (i = 0; i < 8 && (c = dirname[i]) != ' '; ++i) pc(lower ? tolower(c) : c); } /* get a line with editing */ getline(prompt) char *prompt; { static char *p, *q; static int n; static int i; static int c; static int hindex; /* index into history for recall */ char wkline[256]; /* work line for history search */ maxpos = 0; setmem(line, 256, 0); /* clear the line */ hindex = lasthist; /* point to next line for history */ ps("\n"); putprompt(); cur = line; for (;;) switch (c = bconin() & 0x7f) { case CR: /* done with line */ bcnout('\r'); i = (maxpos + maxcols - 1) / maxcols - (linepos + maxcols - 1) / maxcols; while (i-- > 0) bcnout('\n'); return line; case CTLS: /* back one character */ if (cur != line) backup(); break; case CTLA: /* back one word */ if (cur == line) break; backup(); while (cur != line && !isalnum(*cur)) backup(); while (cur != line && isalnum(*(cur - 1))) backup(); break; case CTLQ: /* back on command */ if (cur != line) backup(); while (cur != line && *(cur - 1) != ';') backup(); break; case CTLD: /* forward one character */ if (*cur != '\0') forward(); break; case CTLF: /* forward one word */ while (isalnum(*cur)) forward(); while (*cur != '\0' && !isalnum(*cur)) forward(); break; case CTLR: /* forward command */ while (*cur != '\0' && *cur != ';') forward(); if (*cur == ';') forward(); break; case BS: case DEL: /* backspace and delete */ if (cur == line) break; backup(); /* NOTE fall through */ case CTLG: /* delete current char */ if (*cur != '\0') delchr(); break; case CTLW: /* delete word left */ if (cur == line) break; backup(); while (cur != line && !isalnum(*cur)) backup(); while (cur != line && isalnum(*(cur - 1))) backup(); /* NOTE fall through */ case CTLT: /* delete word right */ while (isalnum(*cur)) delchr(); while (*cur != '\0' && !isalnum(*cur)) delchr(); break; case CTLZ: /* delete command */ if (*cur == ';') forward(); while (cur != line && *(cur - 1) != ';') backup(); while (*cur != '\0' && *cur != ';') delchr(); if (*cur == ';') delchr(); if (*cur == '\0' && *(cur - 1) == ';') { backup(); delchr(); } break; case CTLD: /* forward one character */ if (*cur != '\0') forward(); break; case CTLV: /* insert char */ if (vmode) { insert = !insert; break; } inschr(); showln(); break; case CTLX: /* recall next line */ if (hindex != lasthist) { ++hindex; if (hindex > nhist) hindex = 0; } if (hindex != lasthist) { delln(); gethline(line, hindex); showln(); break; } /* NOTE fall through */ case CTLU: /* delete line */ hindex = lasthist; delln(); break; case CTLY: /* delete to end of line */ i = linepos; p = cur; while (*cur != '\0') forward(); n = linepos - i; while (cur > p) *(--cur) = '\0'; for (i = 0; i < n; ++i) pc('\b'); for (i = 0; i < n; ++i) pc(' '); for (i = 0; i < n; ++i) pc('\b'); break; case CTLE: /* recall previous line */ if (hindex == firsthist) { bell(); break; } delln(); if (--hindex < 0) hindex = nhist; gethline(line, hindex); showln(); break; case CTLL: /* search for previous */ n = FALSE; for (i = hindex; i != firsthist;) { if (--i < 0) i = nhist; if (n = prefix(line, cur, gethline(wkline, i))) break; } if (n) { p = cur; delln(); gethline(line, hindex = i); showln(); while (cur < p) forward(); } else bell(); break; case CTLJ: /* help */ p = cur; help(); maxpos = 0; putprompt(); cur = line; while (cur < p) forward(); showln(); break; case NUL: /* ignore nulls */ break; case CTLP: /* take next char as literal */ if ((c = bconin() & 0x7f) == NUL) break; /* NUL can't be handled */ /* NOTE fall through */ default: if (cur == line) switch (c) /* handle special commands */ { case ESC: delln(); ps("\n"); shpop(); puthist(); exit(0); case CTLC: delln(); ps("^C"); puthist(); boot_(); } if (cur == maxcur) bell(); else { if (insert) inschr(); *cur = c; forward(); showln(); } } } /* delete character */ delchr() { static char *p; for (p = cur; (*p = *(p + 1)) != '\0'; ++p) ; p = cur; while (*cur != '\0') forward(); ps(" \b\b"); while (cur > p) backup(); } /* insert a character in line */ inschr() { static char *p; p = cur + strlen(cur); if (p == maxcur) *(p - 1) = '\0'; while (p > cur) { *p = *(p - 1); --p; } *cur = ' '; } /* delete entire line */ delln() { while (*cur != '\0') forward(); while (cur > line) { if (*--cur < ' ' || *cur == DEL) ps("\b \b"); ps("\b \b"); *cur = '\0'; } maxpos = linepos; } /* show line from current position */ showln() { static char *p; if (*cur != '\0') { p = cur; while (*cur != '\0') forward(); ps(" \b"); while (cur != p) backup(); } } /* back up one character */ backup() { --cur; if (*cur < ' ' || *cur == DEL) pc('\b'); pc('\b'); } /* forward one character */ forward() { pctl(*(cur++)); } /* is s - t a prefix of u. ignore case */ prefix(s, t, u) register char *s, *t, *u; { while (s < t) if (toupper(*(s++)) != toupper(*(u++))) return FALSE; return TRUE; } /* put string to console */ ps(s) register char *s; { while (*s != '\0') { if (*s == '\n') pc('\r'); pc(*(s++)); } } /* put char to console */ pc(c) int c; { bcnout(c); switch (c) { case BS: --linepos; break; case CR: linepos = 0; break; default: if (c >= ' ' && c < DEL) if (++linepos > maxpos) maxpos = linepos; } } /* put character to console, converting controls to printables */ pctl(c) int c; { if (c == DEL) { ps("^?"); return; } if (c < ' ') { pc('^'); pc (c += '@'); return; } pc(c); } /* audible alarm */ bell() { bcnout(BEL); } /* critical error. remove shell and exit with message */ sherror(s) char *s; { shpop(); error(s); } /* display help */ help() { static char *msg[] = { "\n\n\n\n +-------+-------+-------+--------+--------+\n", " | Back | Fwd | Del L | Del R | Del All|\n", " +-----+-------+-------+-------+--------+--------+\n", " |Char | ^S | ^D | BS | ^G | |\n", " +-----+-------+-------+-------+--------+--------+\n", " |Word | ^A | ^F | ^W | ^T | |\n", " +-----+-------+-------+-------+--------+--------+\n", " |Cmd | ^Q | ^R | | | ^Z |\n", " +-----+-------+-------+-------+--------+--------+\n", " |Line | | | | ^Y | ^U |\n", " +-----+-------+-------+-------+--------+--------+\n\n", " ^E - Recall prev cmd line\n", " ^L - Search for match left of the cursor\n", " ^P - Quote next char\n", " ^V - Insert\n", " ^X - Recall next cmd line\n", NULL }; static char **p; for (p = msg; *p != NULL; ++p) ps(*p); } /* ZCPR3 transfer routines */ /* These routines simply convert calling sequences and return values */ /* between C/80 and Z3LIB. */ /* clear command line */ clcl() { #asm call clrcl## mov h,a mov l,a #endasm } /* is this a shell? */ qsh() { #asm call qshell## lxi h,0 rnz inx h #endasm } /* point to external FCB */ FCB *gefcb() { #asm call getefcb## rnz lxi h,0 #endasm } /* install shell */ shpsh() { #asm pop d pop h push h push d call shpush## mvi h,0 mov l,a #endasm } /* get root drive/user */ groot() { #asm call root## mov h,b mov l,c #endasm } /* put command line */ pcl() { #asm pop d pop h push h push d call putcl## mov h,a mov l,a #endasm } /* get length of command line */ gcl1() { #asm call getcl1## mov l,a mov h,0 #endasm } /* convert drive/user to directory name */ dutd() { #asm pop h pop b push b push h call dutdir## rnz lxi h,0 #endasm } /* find file along path */ pfnd() { #asm pop h pop b pop d push d push b push h mov a,c call pfind## mov h,b mov l,c rnz lxi h,0 #endasm } /* set command status message */ pcst() { #asm pop h pop b push b push h mov a,c call putcst## #endasm } /* set ZEX control message */ pzex() { #asm pop h pop b push b push h mov a,c call putzex## #endasm } /* get quiet flag */ getq() { #asm call getquiet## mov h,a mov l,a #endasm }