zsh/Src/prompt.c

1307 lines
29 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 */
/**/
mod_export unsigned txtchange;
1999-04-15 20:05:38 +02:00
/* the command stack for use with %_ in prompts */
/**/
unsigned char *cmdstack;
/**/
int cmdsp;
/* parser states, for %_ */
static char *cmdnames[CS_COUNT] = {
1999-04-15 20:05:38 +02:00
"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", "always",
1999-04-15 20:05:38 +02:00
};
/* 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;
/* Position of the start of the current line in the buffer */
static char *bufline;
/* bp1 is an auxiliary pointer into the buffer, which when non-NULL is *
1999-04-15 20:05:38 +02:00
* 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;
/* Non-zero if truncating the current segment of the buffer. */
1999-04-15 20:05:38 +02:00
static int truncwidth;
1999-04-15 20:05:38 +02:00
/* Current level of nesting of %{ / %} sequences. */
static int dontcount;
/* Level of %{ / %} surrounding a truncation segment. */
static int trunccount;
1999-04-15 20:05:38 +02:00
/* Strings to use for %r and %R (for the spelling prompt). */
static char *rstring, *Rstring;
/*
* Expand path p; maximum is npath segments where 0 means the whole path.
* If tilde is 1, try and find a named directory to use.
*/
1999-04-15 20:05:38 +02:00
static void
promptpath(char *p, int npath, int tilde)
{
char *modp = p;
Nameddir nd;
if (tilde && ((nd = finddir(p))))
modp = tricat("~", nd->nam, p + strlen(nd->dir));
if (npath) {
char *sptr;
if (npath > 0) {
for (sptr = modp + strlen(modp); sptr > modp; sptr--) {
if (*sptr == '/' && !--npath) {
sptr++;
break;
}
}
if (*sptr == '/' && sptr[1] && sptr != modp)
sptr++;
stradd(sptr);
} else {
char cbu;
for (sptr = modp+1; *sptr; sptr++)
if (*sptr == '/' && !++npath)
break;
cbu = *sptr;
*sptr = 0;
stradd(modp);
*sptr = cbu;
}
} else
stradd(modp);
if (p != modp)
zsfree(modp);
}
1999-04-15 20:05:38 +02:00
/* 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. */
/**/
mod_export char *
1999-04-15 20:05:38 +02:00
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;
int oldval = lastval;
1999-04-15 20:05:38 +02:00
s = dupstring(s);
if (!parsestr(s))
singsub(&s);
/* Ignore errors and status change in prompt substitution */
1999-04-15 20:05:38 +02:00
errflag = olderr;
lastval = oldval;
1999-04-15 20:05:38 +02:00
}
rstring = rs;
Rstring = Rs;
fm = s;
2003-10-29 20:17:30 +01:00
bp = bufline = buf = zshcalloc(bufspc = 256);
1999-04-15 20:05:38 +02:00
bp1 = NULL;
truncwidth = 0;
1999-04-15 20:05:38 +02:00
putpromptchar(1, '\0');
addbufspc(1);
if(dontcount)
*bp++ = Outpar;
*bp = 0;
if (!ns) {
/* If zero, Inpar, Outpar and Nularg should be removed. */
for (bp = buf; *bp; ) {
if (*bp == Meta)
bp += 2;
else if (*bp == Inpar || *bp == Outpar || *bp == Nularg)
chuck(bp);
else
bp++;
}
}
1999-04-15 20:05:38 +02:00
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, *hostnam;
int t0, arg, test, sep, j, numjobs;
1999-04-15 20:05:38 +02:00
struct tm *tm;
time_t timet;
Nameddir nd;
for (; *fm && *fm != endchar; fm++) {
arg = 0;
if (*fm == '%' && isset(PROMPTPERCENT)) {
int minus = 0;
fm++;
if (*fm == '-') {
minus = 1;
fm++;
1999-04-15 20:05:38 +02:00
}
if (idigit(*fm)) {
arg = zstrtol(fm, &fm, 10);
if (minus)
arg *= -1;
} else if (minus)
arg = -1;
1999-04-15 20:05:38 +02:00
if (*fm == '(') {
int tc, otruncwidth;
1999-04-15 20:05:38 +02:00
if (idigit(*++fm)) {
arg = zstrtol(fm, &fm, 10);
} else if (arg < 0) {
/* negative numbers don't make sense here */
arg *= -1;
1999-04-15 20:05:38 +02:00
}
test = 0;
ss = pwd;
switch (tc = *fm) {
case 'c':
case '.':
case '~':
if ((nd = finddir(ss))) {
arg--;
ss += strlen(nd->dir);
} /*FALLTHROUGH*/
1999-04-15 20:05:38 +02:00
case '/':
case 'C':
/* `/' gives 0, `/any' gives 1, etc. */
if (*ss++ == '/' && *ss)
arg--;
1999-04-15 20:05:38 +02:00
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() == (uid_t)arg)
1999-04-15 20:05:38 +02:00
test = 1;
break;
case 'g':
if (getegid() == (gid_t)arg)
1999-04-15 20:05:38 +02:00
test = 1;
break;
case 'j':
for (numjobs = 0, j = 1; j <= maxjob; j++)
if (jobtab[j].stat && jobtab[j].procs &&
!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
if (numjobs >= arg)
test = 1;
break;
case 'l':
*bp = '\0';
countprompt(bufline, &t0, 0, 0);
if (t0 >= arg)
test = 1;
break;
1999-04-15 20:05:38 +02:00
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++;
/* Don't do the current truncation until we get back */
otruncwidth = truncwidth;
truncwidth = 0;
1999-04-15 20:05:38 +02:00
if (!putpromptchar(test == 1 && doprint, sep) || !*++fm ||
!putpromptchar(test == 0 && doprint, ')')) {
truncwidth = otruncwidth;
1999-04-15 20:05:38 +02:00
return 0;
}
truncwidth = otruncwidth;
1999-04-15 20:05:38 +02:00
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 '~':
promptpath(pwd, arg, 1);
break;
1999-04-15 20:05:38 +02:00
case 'd':
case '/':
promptpath(pwd, arg, 0);
1999-04-15 20:05:38 +02:00
break;
case 'c':
case '.':
promptpath(pwd, arg ? arg : 1, 1);
break;
1999-04-15 20:05:38 +02:00
case 'C':
promptpath(pwd, arg ? arg : 1, 0);
break;
case 'N':
promptpath(scriptname ? scriptname : argzero, arg, 0);
1999-04-15 20:05:38 +02:00
break;
case 'h':
case '!':
addbufspc(DIGBUFSIZE);
convbase(bp, curhist, 10);
1999-04-15 20:05:38 +02:00
bp += strlen(bp);
break;
case 'j':
for (numjobs = 0, j = 1; j <= maxjob; j++)
if (jobtab[j].stat && jobtab[j].procs &&
!(jobtab[j].stat & STAT_NOPRINT)) numjobs++;
addbufspc(DIGBUFSIZE);
sprintf(bp, "%d", numjobs);
bp += strlen(bp);
break;
1999-04-15 20:05:38 +02:00
case 'M':
queue_signals();
if ((hostnam = getsparam("HOST")))
stradd(hostnam);
unqueue_signals();
1999-04-15 20:05:38 +02:00
break;
case 'm':
if (!arg)
arg++;
queue_signals();
if (!(hostnam = getsparam("HOST")))
break;
if (arg < 0) {
for (ss = hostnam + strlen(hostnam); ss > hostnam; ss--)
if (ss[-1] == '.' && !++arg)
break;
stradd(ss);
} else {
for (ss = hostnam; *ss; ss++)
if (*ss == '.' && !--arg)
break;
stradd(*ss ? dupstrpfx(hostnam, ss - hostnam) : hostnam);
}
unqueue_signals();
1999-04-15 20:05:38 +02:00
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))
arg = zstrtol(fm, &fm, 10);
if (!prompttrunc(arg, ']', doprint, endchar))
return *fm;
1999-04-15 20:05:38 +02:00
break;
case '<':
case '>':
if (!prompttrunc(arg, *fm, doprint, endchar))
return *fm;
1999-04-15 20:05:38 +02:00
break;
case '{': /*}*/
if (!dontcount++) {
1999-04-15 20:05:38 +02:00
addbufspc(1);
*bp++ = Inpar;
}
break;
case /*{*/ '}':
if (trunccount && trunccount >= dontcount)
return *fm;
if (dontcount && !--dontcount) {
1999-04-15 20:05:38 +02:00
addbufspc(1);
*bp++ = Outpar;
}
break;
case 't':
case '@':
case 'T':
case '*':
case 'w':
case 'W':
case 'D':
{
char *tmfmt, *dd, *tmbuf = NULL;
1999-04-15 20:05:38 +02:00
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] == '{' /*}*/) {
1999-04-15 20:05:38 +02:00
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++) {
1999-04-15 20:05:38 +02:00
if(*ss == '\\' && ss[1])
ss++;
*dd++ = *ss;
}
*dd = 0;
fm = ss - !*ss;
if (!*tmfmt) {
free(tmbuf);
continue;
}
1999-04-15 20:05:38 +02:00
} else
tmfmt = "%y-%m-%d";
break;
default:
tmfmt = "%l:%M%p";
break;
}
timet = time(NULL);
tm = localtime(&timet);
/*
* Hack because strftime won't say how
* much space it actually needs. Try to add it
* a few times until it works. Some formats don't
* actually have a length, so we could go on for
* ever.
*/
for(j = 0, t0 = strlen(tmfmt)*8; j < 3; j++, t0*=2) {
1999-04-15 20:05:38 +02:00
addbufspc(t0);
if (ztrftime(bp, t0, tmfmt, tm) >= 0)
1999-04-15 20:05:38 +02:00
break;
}
2006-01-13 10:43:46 +01:00
/* There is enough room for this because addbufspc(t0)
* allocates room for t0 * 2 bytes. */
metafy(bp, -1, META_NOALLOC);
bp += strlen(bp);
zsfree(tmbuf);
1999-04-15 20:05:38 +02:00
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 'y':
1999-04-15 20:05:38 +02:00
if (*ttystrname) {
ss = (strncmp(ttystrname, "/dev/", 5) ?
ttystrname : ttystrname + 5);
1999-04-15 20:05:38 +02:00
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;
else if (arg < 0)
arg += arrlen(psvar) + 1;
if (arg > 0 && arrlen(psvar) >= arg)
1999-04-15 20:05:38 +02:00
stradd(psvar[arg - 1]);
break;
case 'E':
tsetcap(TCCLEAREOL, 1);
break;
case '^':
if (cmdsp) {
if (arg >= 0) {
if (arg > cmdsp || arg == 0)
arg = cmdsp;
for (t0 = cmdsp - 1; arg--; t0--) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bp++=' ';
}
}
} else {
arg = -arg;
if (arg > cmdsp)
arg = cmdsp;
for (t0 = arg - 1; arg--; t0--) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bp++=' ';
}
}
}
}
break;
1999-04-15 20:05:38 +02:00
case '_':
if (cmdsp) {
if (arg >= 0) {
if (arg > cmdsp || arg == 0)
arg = cmdsp;
for (t0 = cmdsp - arg; arg--; t0++) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bp++=' ';
}
}
} else {
arg = -arg;
if (arg > cmdsp)
arg = cmdsp;
for (t0 = 0; arg--; t0++) {
stradd(cmdnames[cmdstack[t0]]);
if (arg) {
addbufspc(1);
*bp++=' ';
}
1999-04-15 20:05:38 +02:00
}
}
}
break;
case 'r':
if(rstring)
stradd(rstring);
break;
case 'R':
if(Rstring)
stradd(Rstring);
break;
case 'i':
addbufspc(DIGBUFSIZE);
sprintf(bp, "%ld", (long)lineno);
bp += strlen(bp);
break;
1999-04-15 20:05:38 +02:00
case '\0':
return 0;
case Meta:
fm++;
break;
}
} else if(*fm == '!' && isset(PROMPTBANG)) {
if(doprint) {
1999-04-15 20:05:38 +02:00
if(fm[1] == '!') {
fm++;
addbufspc(1);
pputc('!');
} else {
addbufspc(DIGBUFSIZE);
convbase(bp, curhist, 10);
1999-04-15 20:05:38 +02:00
bp += strlen(bp);
}
}
1999-04-15 20:05:38 +02:00
} 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(c)) {
1999-04-15 20:05:38 +02:00
*bp++ = Meta;
c ^= 32;
}
*bp++ = c;
if (c == '\n' && !dontcount)
bufline = bp;
1999-04-15 20:05:38 +02:00
}
/* 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. */
1999-04-15 20:05:38 +02:00
/**/
void
stradd(char *d)
{
#ifdef MULTIBYTE_SUPPORT
char *ums, *ups;
int upslen, eol = 0;
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
ums = ztrdup(d);
ups = unmetafy(ums, &upslen);
/*
* We now have a raw string of possibly multibyte characters.
* Read each character one by one.
*/
while (upslen > 0) {
wchar_t cc;
char *pc;
size_t cnt = eol ? MB_INVALID : mbrtowc(&cc, ups, upslen, &mbs);
switch (cnt) {
case MB_INCOMPLETE:
eol = 1;
/* FALL THROUGH */
case MB_INVALID:
/* Bad character. Take the next byte on its own. */
pc = nicechar(*ups);
cnt = 1;
memset(&mbs, 0, sizeof mbs);
break;
case 0:
cnt = 1;
/* FALL THROUGH */
default:
/* Take full wide character in one go */
pc = wcs_nicechar(cc, NULL, NULL);
break;
}
/* Keep output as metafied string. */
addbufspc(strlen(pc));
upslen -= cnt;
ups += cnt;
/* Put printed representation into the buffer */
while (*pc)
*bp++ = *pc++;
}
free(ums);
#else
char *ps, *pc;
addbufspc(niceztrlen(d));
/* This loop puts the nice representation of the string into the
* prompt buffer. */
for (ps = d; *ps; ps++) {
for (pc = nicechar(*ps == Meta ? *++ps^32 : *ps); *pc; pc++)
*bp++ = *pc;
}
#endif
1999-04-15 20:05:38 +02:00
}
/* tsetcap(), among other things, can write a termcap string into the buffer. */
/**/
mod_export void
1999-04-15 20:05:38 +02:00
tsetcap(int cap, int flag)
{
if (tccan(cap) && !isset(SINGLELINEZLE) &&
!(termflags & (TERM_NOUP|TERM_BAD|TERM_UNKNOWN))) {
1999-04-15 20:05:38 +02:00
switch(flag) {
case -1:
tputs(tcstr[cap], 1, putraw);
break;
case 0:
tputs(tcstr[cap], 1, putshout);
break;
case 1:
if (!dontcount) {
1999-04-15 20:05:38 +02:00
addbufspc(1);
*bp++ = Inpar;
}
tputs(tcstr[cap], 1, putstr);
if (!dontcount) {
1999-04-15 20:05:38 +02:00
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.
* Passing `overf' as -1 means to ignore columns (absolute width).
*
* If multibyte is enabled, take account of multibyte characters
* by locating them and finding out their screen width.
*/
1999-04-15 20:05:38 +02:00
/**/
mod_export void
countprompt(char *str, int *wp, int *hp, int overf)
1999-04-15 20:05:38 +02:00
{
int w = 0, h = 1;
int s = 1;
#ifdef MULTIBYTE_SUPPORT
int wcw, multi = 0;
char inchar;
mbstate_t mbs;
wchar_t wc;
memset(&mbs, 0, sizeof(mbs));
#endif
for (; *str; str++) {
if (w >= columns && overf >= 0) {
w = 0;
h++;
}
/*
* Input string should be metafied, so tokens in it should
* be real tokens, even if there are multibyte characters.
*/
if (*str == Inpar)
1999-04-15 20:05:38 +02:00
s = 0;
else if (*str == Outpar)
1999-04-15 20:05:38 +02:00
s = 1;
else if (*str == Nularg)
w++;
else if (s) {
if (*str == Meta) {
#ifdef MULTIBYTE_SUPPORT
inchar = *++str ^ 32;
#else
str++;
#endif
} else {
#ifdef MULTIBYTE_SUPPORT
/*
* Don't look for tab or newline in the middle
* of a multibyte character. Otherwise, we are
* relying on the character set being an extension
* of ASCII so it's safe to test a single byte.
*/
if (!multi) {
#endif
if (*str == '\t') {
w = (w | 7) + 1;
continue;
} else if (*str == '\n') {
w = 0;
h++;
continue;
}
#ifdef MULTIBYTE_SUPPORT
}
inchar = *str;
#endif
}
#ifdef MULTIBYTE_SUPPORT
switch (mbrtowc(&wc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Character is incomplete -- keep looking. */
multi = 1;
break;
case MB_INVALID:
memset(&mbs, 0, sizeof mbs);
/* FALL THROUGH */
case 0:
/* Invalid character or null: assume no output. */
multi = 0;
break;
default:
/* If the character isn't printable, wcwidth() returns -1. */
wcw = wcwidth(wc);
if (wcw > 0)
w += wcw;
multi = 0;
break;
}
#else
1999-04-15 20:05:38 +02:00
w++;
#endif
1999-04-15 20:05:38 +02:00
}
}
/*
* multi may still be set if we were in the middle of the character.
* This isn't easy to handle generally; just assume there's no
* output.
*/
if(w >= columns && overf >= 0) {
if (!overf || w > columns) {
1999-04-15 20:05:38 +02:00
w = 0;
h++;
}
}
if(wp)
*wp = w;
if(hp)
*hp = h;
}
/**/
static int
prompttrunc(int arg, int truncchar, int doprint, int endchar)
{
if (arg > 0) {
char ch = *fm, *ptr, *truncstr;
int truncatleft = ch == '<';
int w = bp - buf;
/*
* If there is already a truncation active, return so that
* can be finished, backing up so that the new truncation
* can be started afterwards.
*/
if (truncwidth) {
while (*--fm != '%')
;
fm--;
return 0;
}
truncwidth = arg;
if (*fm != ']')
fm++;
while (*fm && *fm != truncchar) {
if (*fm == '\\' && fm[1])
++fm;
addbufspc(1);
*bp++ = *fm++;
}
if (!*fm)
return 0;
if (bp - buf == w && truncchar == ']') {
addbufspc(1);
*bp++ = '<';
}
ptr = buf + w; /* addbufspc() may have realloc()'d buf */
/*
* Now:
* buf is the start of the output prompt buffer
* ptr is the start of the truncation string
* bp is the end of the truncation string
*/
truncstr = ztrduppfx(ptr, bp - ptr);
bp = ptr;
w = bp - buf;
fm++;
trunccount = dontcount;
putpromptchar(doprint, endchar);
trunccount = 0;
ptr = buf + w; /* putpromptchar() may have realloc()'d */
*bp = '\0';
/*
* Now:
* ptr is the start of the truncation string and also
* where we need to start putting any truncated output
* bp is the end of the string we have just added, which
* may need truncating.
*/
/*
* w below is screen width if multibyte support is enabled
* (note that above it was a raw string pointer difference).
* It's the full width of the string we may need to truncate.
*
* truncwidth has come from the user, so we interpret this
* as a screen width, too.
*/
countprompt(ptr, &w, 0, -1);
if (w > truncwidth) {
/*
* We need to truncate. t points to the truncation string
* -- which is inserted literally, without nice
* representation. twidth is its printing width, and maxwidth
* is the amount of the main string that we want to keep.
* Note that if the truncation string is longer than the
* truncation length (twidth > truncwidth), the truncation
* string is used in full.
*/
char *t = truncstr;
int fullen = bp - ptr;
int twidth, maxwidth;
int ntrunc = strlen(t);
#ifdef MULTIBYTE_SUPPORT
/* Use screen width of string */
twidth = mb_width(t);
2005-10-22 00:40:30 +02:00
#else
twidth = ztrlen(t);
#endif
if (twidth < truncwidth) {
maxwidth = truncwidth - twidth;
/*
* It's not safe to assume there are no invisible substrings
* just because the width is less than the full string
* length since there may be multibyte characters.
*/
addbufspc(ntrunc+1);
/* may have realloc'd */
ptr = bp - fullen;
if (truncatleft) {
/*
* To truncate at the left, selectively copy
* maxwidth bytes from the main prompt, preceeded
* by the truncation string in full.
*
* We're overwriting the string containing the
* text to be truncated, so copy it. We've
* just ensured there's sufficient space at the
* end of the prompt string.
*
* Pointer into text to be truncated.
*/
char *fulltextptr, *fulltext;
int remw;
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
2005-10-23 21:28:20 +02:00
#endif
fulltextptr = fulltext = ptr + ntrunc;
memmove(fulltext, ptr, fullen);
fulltext[fullen] = '\0';
/* Copy the truncstr into place. */
while (*t)
*ptr++ = *t++;
/*
* Find the point in the text at which we should
* start copying, i.e. when the remaining width
* is less than or equal to the maximum width.
*/
remw = w;
while (remw > maxwidth && *fulltextptr) {
if (*fulltextptr == Inpar) {
/*
* Text marked as invisible: copy
* regardless, since we don't know what
* this does but it shouldn't affect
* the width.
*/
for (;;) {
*ptr++ = *fulltextptr;
if (*fulltextptr == Outpar ||
*fulltextptr == '\0')
break;
fulltextptr++;
}
} else {
#ifdef MULTIBYTE_SUPPORT
/*
* Normal text: build up a multibyte character.
*/
char inchar;
wchar_t cc;
/*
* careful: string is still metafied (we
* need that because we don't know a
* priori when to stop and the resulting
* string must be metafied).
*/
if (*fulltextptr == Meta)
inchar = *++fulltextptr ^ 32;
else
inchar = *fulltextptr;
fulltextptr++;
switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Incomplete multibyte character. */
break;
case MB_INVALID:
/* Reset invalid state. */
memset(&mbs, 0, sizeof mbs);
/* FALL THROUGH */
case 0:
/* Assume a single-byte character. */
remw--;
break;
default:
remw -= wcwidth(cc);
break;
}
2005-10-22 00:40:30 +02:00
#else
/* Single byte character */
if (*fulltextptr == Meta)
fulltextptr++;
fulltextptr++;
remw--;
#endif
}
}
/*
* Now simply copy the rest of the text. Still
* metafied, so this is easy.
*/
while (*fulltextptr)
*ptr++ = *fulltextptr++;
/* Mark the end of copying */
bp = ptr;
} else {
/*
* Truncating at the right is easier: just leave
* enough characters until we have reached the
* maximum width.
*/
char *skiptext = ptr;
#ifdef MULTIBYTE_SUPPORT
mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
2005-10-23 21:28:20 +02:00
#endif
while (maxwidth > 0 && *skiptext) {
if (*skiptext == Inpar) {
for (; *skiptext != Outpar && *skiptext;
skiptext++);
} else {
#ifdef MULTIBYTE_SUPPORT
char inchar;
wchar_t cc;
if (*skiptext == Meta)
inchar = *++skiptext ^ 32;
else
inchar = *skiptext;
skiptext++;
switch (mbrtowc(&cc, &inchar, 1, &mbs)) {
case MB_INCOMPLETE:
/* Incomplete character. */
break;
case MB_INVALID:
/* Reset invalid state. */
memset(&mbs, 0, sizeof mbs);
/* FALL THROUGH */
case 0:
/* Assume a single-byte character. */
maxwidth--;
break;
default:
maxwidth -= wcwidth(cc);
break;
}
2005-10-22 00:40:30 +02:00
#else
if (*skiptext == Meta)
skiptext++;
skiptext++;
maxwidth--;
#endif
}
}
/*
* We don't need the visible text from now on,
* but we'd better copy any invisible bits.
* History dictates that these go after the
* truncation string. This is sensible since
* they may, for example, turn off an effect which
* should apply to all text at this point.
*
* Copy the truncstr.
*/
ptr = skiptext;
while (*t)
*ptr++ = *t++;
bp = ptr;
if (*skiptext) {
/* Move remaining text so we don't overwrite it */
memmove(bp, skiptext, strlen(skiptext)+1);
skiptext = bp;
/*
* Copy anything we want, updating bp
*/
while (*skiptext) {
if (*skiptext == Inpar) {
for (;;) {
*bp++ = *skiptext;
if (*skiptext == Outpar ||
*skiptext == '\0')
break;
skiptext++;
}
}
else
skiptext++;
}
}
}
} else {
/* Just copy truncstr; no other text appears. */
while (*t)
*ptr++ = *t++;
bp = ptr;
}
*bp = '\0';
}
zsfree(truncstr);
truncwidth = 0;
/*
* We may have returned early from the previous putpromptchar *
* because we found another truncation following this one. *
* In that case we need to do the rest now. *
*/
if (!*fm)
return 0;
if (*fm != endchar) {
fm++;
/*
* With truncwidth set to zero, we always reach endchar *
* (or the terminating NULL) this time round. *
*/
if (!putpromptchar(doprint, endchar))
return 0;
}
/* Now we have to trick it into matching endchar again */
fm--;
} else {
if (*fm != ']')
fm++;
while(*fm && *fm != truncchar) {
if (*fm == '\\' && fm[1])
fm++;
fm++;
}
if (truncwidth || !*fm)
return 0;
}
return 1;
}