zsh/Src/prompt.c

767 lines
16 KiB
C
Raw Normal View History

1999-04-15 20:05:38 +02:00
/*
* prompt.c - construct zsh prompts
*
* This file is part of zsh, the Z shell.
*
* Copyright (c) 1992-1997 Paul Falstad
* All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and to distribute modified versions of this software for any
* purpose, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* In no event shall Paul Falstad or the Zsh Development Group be liable
* to any party for direct, indirect, special, incidental, or consequential
* damages arising out of the use of this software and its documentation,
* even if Paul Falstad and the Zsh Development Group have been advised of
* the possibility of such damage.
*
* Paul Falstad and the Zsh Development Group specifically disclaim any
* warranties, including, but not limited to, the implied warranties of
* merchantability and fitness for a particular purpose. The software
* provided hereunder is on an "as is" basis, and Paul Falstad and the
* Zsh Development Group have no obligation to provide maintenance,
* support, updates, enhancements, or modifications.
*
*/
#include "zsh.mdh"
#include "prompt.pro"
/* text attribute mask */
/**/
unsigned txtattrmask;
/* text change - attribute change made by prompts */
/**/
unsigned txtchange;
/* the command stack for use with %_ in prompts */
/**/
unsigned char *cmdstack;
/**/
int cmdsp;
/* parser states, for %_ */
static char *cmdnames[] = {
"for", "while", "repeat", "select",
"until", "if", "then", "else",
"elif", "math", "cond", "cmdor",
"cmdand", "pipe", "errpipe", "foreach",
"case", "function", "subsh", "cursh",
"array", "quote", "dquote", "bquote",
"cmdsubst", "mathsubst", "elif-then", "heredoc",
"heredocd", "brace", "braceparam",
};
/* The buffer into which an expanded and metafied prompt is being written, *
* and its size. */
static char *buf;
static int bufspc;
/* bp is the pointer to the current position in the buffer, where the next *
* character will be added. */
static char *bp;
/* bp1 is an auxilliary pointer into the buffer, which when non-NULL is *
* moved whenever the buffer is reallocated. It is used when data is *
* being temporarily held in the buffer. */
static char *bp1;
/* The format string, for %-expansion. */
static char *fm;
/* Current truncation string (metafied), the length at which truncation *
* occurs, and the direction in which it occurs. */
static char *truncstr;
static int trunclen, truncatleft;
/* Current level of nesting of %{ / %} sequences. */
static int dontcount;
/* Strings to use for %r and %R (for the spelling prompt). */
static char *rstring, *Rstring;
/* If non-zero, Inpar, Outpar and Nularg can be added to the buffer. */
static int nonsp;
/* Perform prompt expansion on a string, putting the result in a *
* permanently-allocated string. If ns is non-zero, this string *
* may have embedded Inpar and Outpar, which indicate a toggling *
* between spacing and non-spacing parts of the prompt, and *
* Nularg, which (in a non-spacing sequence) indicates a *
* `glitch' space. */
/**/
char *
promptexpand(char *s, int ns, char *rs, char *Rs)
{
if(!s)
return ztrdup("");
if ((termflags & TERM_UNKNOWN) && (unset(INTERACTIVE)))
init_term();
if (isset(PROMPTSUBST)) {
int olderr = errflag;
HEAPALLOC {
s = dupstring(s);
if (!parsestr(s))
singsub(&s);
} LASTALLOC;
/* Ignore errors in prompt substitution */
errflag = olderr;
}
rstring = rs;
Rstring = Rs;
nonsp = ns;
fm = s;
bp = buf = zalloc(bufspc = 256);
bp1 = NULL;
trunclen = 0;
putpromptchar(1, '\0');
addbufspc(1);
if(dontcount)
*bp++ = Outpar;
*bp = 0;
return buf;
}
/* Perform %- and !-expansion as required on a section of the prompt. The *
* section is ended by an instance of endchar. If doprint is 0, the valid *
* % sequences are merely skipped over, and nothing is stored. */
/**/
static int
putpromptchar(int doprint, int endchar)
{
char *ss, *tmbuf = NULL;
int t0, arg, test, sep;
struct tm *tm;
time_t timet;
Nameddir nd;
for (; *fm && *fm != endchar; fm++) {
arg = 0;
if (*fm == '%' && isset(PROMPTPERCENT)) {
if (idigit(*++fm)) {
arg = zstrtol(fm, &fm, 10);
}
if (*fm == '(') {
int tc;
if (idigit(*++fm)) {
arg = zstrtol(fm, &fm, 10);
}
test = 0;
ss = pwd;
switch (tc = *fm) {
case 'c':
case '.':
case '~':
if ((nd = finddir(ss))) {
arg--;
ss += strlen(nd->dir);
}
case '/':
case 'C':
for (; *ss; ss++)
if (*ss == '/')
arg--;
if (arg <= 0)
test = 1;
break;
case 't':
case 'T':
case 'd':
case 'D':
case 'w':
timet = time(NULL);
tm = localtime(&timet);
switch (tc) {
case 't':
test = (arg == tm->tm_min);
break;
case 'T':
test = (arg == tm->tm_hour);
break;
case 'd':
test = (arg == tm->tm_mday);
break;
case 'D':
test = (arg == tm->tm_mon);
break;
case 'w':
test = (arg == tm->tm_wday);
break;
}
break;
case '?':
if (lastval == arg)
test = 1;
break;
case '#':
if (geteuid() == arg)
test = 1;
break;
case 'g':
if (getegid() == arg)
test = 1;
break;
case 'L':
if (shlvl >= arg)
test = 1;
break;
case 'S':
if (time(NULL) - shtimer.tv_sec >= arg)
test = 1;
break;
case 'v':
if (arrlen(psvar) >= arg)
test = 1;
break;
case '_':
test = (cmdsp >= arg);
break;
case '!':
test = privasserted();
break;
default:
test = -1;
break;
}
if (!*fm || !(sep = *++fm))
return 0;
fm++;
if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
!putpromptchar(test == 0 && doprint, ')')) {
return 0;
}
continue;
}
if (!doprint)
switch(*fm) {
case '[':
while(idigit(*++fm));
while(*++fm != ']');
continue;
case '<':
while(*++fm != '<');
continue;
case '>':
while(*++fm != '>');
continue;
case 'D':
if(fm[1]=='{')
while(*++fm != '}');
continue;
default:
continue;
}
switch (*fm) {
case '~':
if ((nd = finddir(pwd))) {
char *t = tricat("~", nd->nam, pwd + strlen(nd->dir));
stradd(t);
zsfree(t);
break;
}
case 'd':
case '/':
stradd(pwd);
break;
case 'c':
case '.':
{
char *t;
if ((nd = finddir(pwd)))
t = tricat("~", nd->nam, pwd + strlen(nd->dir));
else
t = ztrdup(pwd);
if (!arg)
arg++;
for (ss = t + strlen(t); ss > t; ss--)
if (*ss == '/' && !--arg) {
ss++;
break;
}
if(*ss == '/' && ss[1] && ss != t)
ss++;
stradd(ss);
zsfree(t);
break;
}
case 'C':
if (!arg)
arg++;
for (ss = pwd + strlen(pwd); ss > pwd; ss--)
if (*ss == '/' && !--arg) {
ss++;
break;
}
if (*ss == '/' && ss[1] && (ss != pwd))
ss++;
stradd(ss);
break;
case 'h':
case '!':
addbufspc(DIGBUFSIZE);
sprintf(bp, "%d", curhist);
bp += strlen(bp);
break;
case 'M':
stradd(hostnam);
break;
case 'm':
if (!arg)
arg++;
for (ss = hostnam; *ss; ss++)
if (*ss == '.' && !--arg)
break;
t0 = *ss;
*ss = '\0';
stradd(hostnam);
*ss = t0;
break;
case 'S':
txtchangeset(TXTSTANDOUT, TXTNOSTANDOUT);
txtset(TXTSTANDOUT);
tsetcap(TCSTANDOUTBEG, 1);
break;
case 's':
txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
txtset(TXTDIRTY);
txtunset(TXTSTANDOUT);
tsetcap(TCSTANDOUTEND, 1);
break;
case 'B':
txtchangeset(TXTBOLDFACE, TXTNOBOLDFACE);
txtset(TXTDIRTY);
txtset(TXTBOLDFACE);
tsetcap(TCBOLDFACEBEG, 1);
break;
case 'b':
txtchangeset(TXTNOBOLDFACE, TXTBOLDFACE);
txtchangeset(TXTNOSTANDOUT, TXTSTANDOUT);
txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
txtset(TXTDIRTY);
txtunset(TXTBOLDFACE);
tsetcap(TCALLATTRSOFF, 1);
break;
case 'U':
txtchangeset(TXTUNDERLINE, TXTNOUNDERLINE);
txtset(TXTUNDERLINE);
tsetcap(TCUNDERLINEBEG, 1);
break;
case 'u':
txtchangeset(TXTNOUNDERLINE, TXTUNDERLINE);
txtset(TXTDIRTY);
txtunset(TXTUNDERLINE);
tsetcap(TCUNDERLINEEND, 1);
break;
case '[':
if (idigit(*++fm))
trunclen = zstrtol(fm, &fm, 10);
else
trunclen = arg;
if (trunclen) {
truncatleft = *fm && *fm != ']' && *fm++ == '<';
bp1 = bp;
while (*fm && *fm != ']') {
if (*fm == '\\' && fm[1])
++fm;
addbufspc(1);
*bp++ = *fm++;
}
addbufspc(2);
if (bp1 == bp)
*bp++ = '<';
*bp = '\0';
zsfree(truncstr);
truncstr = ztrdup(bp = bp1);
bp1 = NULL;
} else {
while (*fm && *fm != ']') {
if (*fm == '\\' && fm[1])
fm++;
fm++;
}
}
if(!*fm)
return 0;
break;
case '<':
case '>':
if((trunclen = arg)) {
char ch = *fm++;
truncatleft = ch == '<';
bp1 = bp;
while (*fm && *fm != ch) {
if (*fm == '\\' && fm[1])
++fm;
addbufspc(1);
*bp++ = *fm++;
}
addbufspc(1);
*bp = '\0';
zsfree(truncstr);
truncstr = ztrdup(bp = bp1);
bp1 = NULL;
} else {
char ch = *fm++;
while(*fm && *fm != ch) {
if (*fm == '\\' && fm[1])
fm++;
fm++;
}
}
if(!*fm)
return 0;
break;
case '{': /*}*/
if (!dontcount++ && nonsp) {
addbufspc(1);
*bp++ = Inpar;
}
break;
case /*{*/ '}':
if (dontcount && !--dontcount && nonsp) {
addbufspc(1);
*bp++ = Outpar;
}
break;
case 't':
case '@':
case 'T':
case '*':
case 'w':
case 'W':
case 'D':
{
char *tmfmt, *dd;
switch (*fm) {
case 'T':
tmfmt = "%K:%M";
break;
case '*':
tmfmt = "%K:%M:%S";
break;
case 'w':
tmfmt = "%a %f";
break;
case 'W':
tmfmt = "%m/%d/%y";
break;
case 'D':
if (fm[1] == '{') /*}*/ {
for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++)
if(*ss == '\\' && ss[1])
ss++;
dd = tmfmt = tmbuf = zalloc(ss - fm);
for (ss = fm + 2; *ss && *ss != /*{*/ '}'; ss++) {
if(*ss == '\\' && ss[1])
ss++;
*dd++ = *ss;
}
*dd = 0;
fm = ss - !*ss;
} else
tmfmt = "%y-%m-%d";
break;
default:
tmfmt = "%l:%M%p";
break;
}
timet = time(NULL);
tm = localtime(&timet);
for(t0=80; ; t0*=2) {
addbufspc(t0);
if(ztrftime(bp, t0, tmfmt, tm) != t0)
break;
}
bp += strlen(bp);
free(tmbuf);
tmbuf = NULL;
break;
}
case 'n':
stradd(get_username());
break;
case 'l':
if (*ttystrname) {
ss = (strncmp(ttystrname, "/dev/tty", 8) ?
ttystrname + 5 : ttystrname + 8);
stradd(ss);
} else
stradd("()");
break;
case 'L':
addbufspc(DIGBUFSIZE);
sprintf(bp, "%ld", (long)shlvl);
bp += strlen(bp);
break;
case '?':
addbufspc(DIGBUFSIZE);
sprintf(bp, "%ld", (long)lastval);
bp += strlen(bp);
break;
case '%':
case ')':
addbufspc(1);
*bp++ = *fm;
break;
case '#':
addbufspc(1);
*bp++ = privasserted() ? '#' : '%';
break;
case 'v':
if (!arg)
arg = 1;
if (arrlen(psvar) >= arg)
stradd(psvar[arg - 1]);
break;
case 'E':
tsetcap(TCCLEAREOL, 1);
break;
case '_':
if (cmdsp) {
if (arg > cmdsp || arg <= 0)
arg = cmdsp;
for (t0 = cmdsp - arg; arg--; t0++) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bp++=' ';
}
}
}
break;
case 'r':
if(rstring)
stradd(rstring);
break;
case 'R':
if(Rstring)
stradd(Rstring);
break;
case '\0':
return 0;
case Meta:
fm++;
break;
}
} else if(*fm == '!' && isset(PROMPTBANG)) {
if(doprint)
if(fm[1] == '!') {
fm++;
addbufspc(1);
pputc('!');
} else {
addbufspc(DIGBUFSIZE);
sprintf(bp, "%d", curhist);
bp += strlen(bp);
}
} else {
char c = *fm == Meta ? *++fm ^ 32 : *fm;
if (doprint) {
addbufspc(1);
pputc(c);
}
}
}
return *fm;
}
/* pputc adds a character to the buffer, metafying. There must *
* already be space. */
/**/
static void
pputc(char c)
{
if(imeta(STOUC(c))) {
*bp++ = Meta;
c ^= 32;
}
*bp++ = c;
}
/* Make sure there is room for `need' more characters in the buffer. */
/**/
static void
addbufspc(int need)
{
need *= 2; /* for metafication */
if((bp - buf) + need > bufspc) {
int bo = bp - buf;
int bo1 = bp1 ? bp1 - buf : -1;
if(need & 255)
need = (need | 255) + 1;
buf = realloc(buf, bufspc += need);
bp = buf + bo;
if(bo1 != -1)
bp1 = buf + bo1;
}
}
/* stradd() adds a metafied string to the prompt, *
* in a visible representation, doing truncation. */
/**/
void
stradd(char *d)
{
/* dlen is the full length of the string we want to add */
int dlen = niceztrlen(d);
char *ps, *pd, *pc, *t;
int tlen, maxlen;
addbufspc(dlen);
/* This loop puts the nice representation of the string into the prompt *
* buffer. It might be modified later. Note that bp isn't changed. */
for(ps=d, pd=bp; *ps; ps++)
for(pc=nicechar(*ps == Meta ? STOUC(*++ps)^32 : STOUC(*ps)); *pc; pc++)
*pd++ = *pc;
if(!trunclen || dlen <= trunclen) {
/* No truncation is needed, so update bp and return, *
* leaving the full string in the prompt. */
bp += dlen;
return;
}
/* We need to truncate. t points to the truncation string -- which is *
* inserted literally, without nice representation. tlen is its *
* length, and maxlen is the amout of the main string that we want to *
* keep. Note that if the truncation string is longer than the *
* truncation length (tlen > trunclen), the truncation string is used *
* in full. */
addbufspc(tlen = ztrlen(t = truncstr));
maxlen = tlen < trunclen ? trunclen - tlen : 0;
if(truncatleft) {
memmove(bp + strlen(t), bp + dlen - maxlen, maxlen);
while(*t)
*bp++ = *t++;
bp += maxlen;
} else {
bp += maxlen;
while(*t)
*bp++ = *t++;
}
}
/* tsetcap(), among other things, can write a termcap string into the buffer. */
/**/
void
tsetcap(int cap, int flag)
{
if (!(termflags & TERM_SHORT) && tcstr[cap]) {
switch(flag) {
case -1:
tputs(tcstr[cap], 1, putraw);
break;
case 0:
tputs(tcstr[cap], 1, putshout);
break;
case 1:
if (!dontcount && nonsp) {
addbufspc(1);
*bp++ = Inpar;
}
tputs(tcstr[cap], 1, putstr);
if (!dontcount && nonsp) {
int glitch = 0;
if (cap == TCSTANDOUTBEG || cap == TCSTANDOUTEND)
glitch = tgetnum("sg");
else if (cap == TCUNDERLINEBEG || cap == TCUNDERLINEEND)
glitch = tgetnum("ug");
if(glitch < 0)
glitch = 0;
addbufspc(glitch + 1);
while(glitch--)
*bp++ = Nularg;
*bp++ = Outpar;
}
break;
}
if (txtisset(TXTDIRTY)) {
txtunset(TXTDIRTY);
if (txtisset(TXTBOLDFACE) && cap != TCBOLDFACEBEG)
tsetcap(TCBOLDFACEBEG, flag);
if (txtisset(TXTSTANDOUT))
tsetcap(TCSTANDOUTBEG, flag);
if (txtisset(TXTUNDERLINE))
tsetcap(TCUNDERLINEBEG, flag);
}
}
}
/**/
int
putstr(int d)
{
addbufspc(1);
pputc(d);
return 0;
}
/* Count height etc. of a prompt string returned by promptexpand(). *
* This depends on the current terminal width, and tabs and *
* newlines require nontrivial processing. */
/**/
void
countprompt(char *str, int *wp, int *hp)
{
int w = 0, h = 1;
int s = 1;
for(; *str; str++) {
if(*str == Meta)
str++;
if(*str == Inpar)
s = 0;
else if(*str == Outpar)
s = 1;
else if(*str == Nularg)
w++;
else if(s) {
if(*str == '\t')
w = (w | 7) + 1;
else if(*str == '\n')
w = columns;
else
w++;
}
if(w >= columns) {
w = 0;
h++;
}
}
if(wp)
*wp = w;
if(hp)
*hp = h;
}