plan9port/src/cmd/9term/win.c
Russ Cox ba31ab3044 9term, acme: autoscroll
Ignore scroll/noscroll window setting.
Instead, scroll when the write begins in
or immediately after the displayed window content.

In the new scrolling discipline, executing
"Noscroll" is replaced by typing Page Up or
using the mouse to scroll higher in the buffer,
and executing "Scroll" is replaced by typing End
or using the mouse to scroll to the bottom of
the buffer.

R=r, r2
http://codereview.appspot.com/4433060
2011-04-27 13:18:07 -04:00

851 lines
14 KiB
C

#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include <9pclient.h>
#include "term.h"
#define EVENTSIZE 256
#define STACK 32768
typedef struct Event Event;
typedef struct Q Q;
struct Event
{
int c1;
int c2;
int q0;
int q1;
int flag;
int nb;
int nr;
char b[EVENTSIZE*UTFmax+1];
Rune r[EVENTSIZE+1];
};
Event blank = {
'M',
'X',
0, 0, 0, 1, 1,
{ ' ', 0 },
{ ' ', 0 }
};
struct Q
{
QLock lk;
int p;
int k;
};
Q q;
CFid *eventfd;
CFid *addrfd;
CFid *datafd;
CFid *ctlfd;
/* int bodyfd; */
char *typing;
int ntypeb;
int ntyper;
int ntypebreak;
int debug;
int rcfd;
int cook = 1;
int password;
int israw(int);
char *name;
char **prog;
Channel *cwait;
int pid = -1;
int label(char*, int);
void error(char*, ...);
void stdinproc(void*);
void stdoutproc(void*);
void type(Event*, int, CFid*, CFid*);
void sende(Event*, int, CFid*, CFid*, CFid*, int);
char *onestring(int, char**);
int delete(Event*);
void deltype(uint, uint);
void sendbs(int, int);
void runproc(void*);
int
fsfidprint(CFid *fid, char *fmt, ...)
{
char buf[256];
va_list arg;
int n;
va_start(arg, fmt);
n = vsnprint(buf, sizeof buf, fmt, arg);
va_end(arg);
return fswrite(fid, buf, n);
}
void
usage(void)
{
fprint(2, "usage: win cmd args...\n");
threadexitsall("usage");
}
void
waitthread(void *v)
{
recvp(cwait);
threadexitsall(nil);
}
void
hangupnote(void *a, char *msg)
{
if(strcmp(msg, "hangup") == 0 && pid != 0){
postnote(PNGROUP, pid, "hangup");
noted(NDFLT);
}
if(strstr(msg, "child")){
char buf[128];
int n;
n = awaitnohang(buf, sizeof buf-1);
if(n > 0){
buf[n] = 0;
if(atoi(buf) == pid)
threadexitsall(0);
}
noted(NCONT);
}
noted(NDFLT);
}
void
threadmain(int argc, char **argv)
{
int fd, id;
char buf[256];
char buf1[128];
CFsys *fs;
char *dump;
dump = onestring(argc, argv);
ARGBEGIN{
case 'd':
debug = 1;
break;
case 'n':
name = EARGF(usage());
break;
default:
usage();
}ARGEND
prog = argv;
if(name == nil){
if(argc > 0)
name = argv[0];
else{
name = sysname();
if(name == nil)
name = "gnot";
}
}
/*
* notedisable("sys: write on closed pipe");
* not okay to disable the note, because that
* gets inherited by the subshell, so that something
* as simple as "yes | sed 10q" never exits.
* call notifyoff instead. (is notedisable ever safe?)
*/
notifyoff("sys: write on closed pipe");
noteenable("sys: child");
notify(hangupnote);
if((fs = nsmount("acme", "")) == 0)
sysfatal("nsmount acme: %r");
ctlfd = fsopen(fs, "new/ctl", ORDWR|OCEXEC);
if(ctlfd == 0 || fsread(ctlfd, buf, 12) != 12)
sysfatal("ctl: %r");
id = atoi(buf);
snprint(buf, sizeof buf, "%d", id);
putenv("winid", buf);
sprint(buf, "%d/tag", id);
fd = fsopenfd(fs, buf, OWRITE|OCEXEC);
write(fd, " Send", 1+4);
close(fd);
sprint(buf, "%d/event", id);
eventfd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/addr", id);
addrfd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/data", id);
datafd = fsopen(fs, buf, ORDWR|OCEXEC);
sprint(buf, "%d/body", id);
/* bodyfd = fsopenfd(fs, buf, ORDWR|OCEXEC); */
if(eventfd==nil || addrfd==nil || datafd==nil)
sysfatal("data files: %r");
/*
if(eventfd<0 || addrfd<0 || datafd<0 || bodyfd<0)
sysfatal("data files: %r");
*/
fsunmount(fs);
cwait = threadwaitchan();
threadcreate(waitthread, nil, STACK);
pid = rcstart(argc, argv, &rcfd, nil);
if(pid == -1)
sysfatal("exec failed");
getwd(buf1, sizeof buf1);
sprint(buf, "name %s/-%s\n0\n", buf1, name);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "dumpdir %s/\n", buf1);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "dump %s\n", dump);
fswrite(ctlfd, buf, strlen(buf));
sprint(buf, "scroll");
fswrite(ctlfd, buf, strlen(buf));
updatewinsize(25, 80, 0, 0);
proccreate(stdoutproc, nil, STACK);
stdinproc(nil);
}
void
error(char *s, ...)
{
va_list arg;
if(s){
va_start(arg, s);
s = vsmprint(s, arg);
va_end(arg);
fprint(2, "win: %s: %r\n", s);
}
if(pid != -1)
postnote(PNGROUP, pid, "hangup");
threadexitsall(s);
}
char*
onestring(int argc, char **argv)
{
char *p;
int i, n;
static char buf[1024];
if(argc == 0)
return "";
p = buf;
for(i=0; i<argc; i++){
n = strlen(argv[i]);
if(p+n+1 >= buf+sizeof buf)
break;
memmove(p, argv[i], n);
p += n;
*p++ = ' ';
}
p[-1] = 0;
return buf;
}
int
getec(CFid *efd)
{
static char buf[8192];
static char *bufp;
static int nbuf;
if(nbuf == 0){
nbuf = fsread(efd, buf, sizeof buf);
if(nbuf <= 0)
error(nil);
bufp = buf;
}
--nbuf;
return *bufp++;
}
int
geten(CFid *efd)
{
int n, c;
n = 0;
while('0'<=(c=getec(efd)) && c<='9')
n = n*10+(c-'0');
if(c != ' ')
error("event number syntax");
return n;
}
int
geter(CFid *efd, char *buf, int *nb)
{
Rune r;
int n;
r = getec(efd);
buf[0] = r;
n = 1;
if(r < Runeself)
goto Return;
while(!fullrune(buf, n))
buf[n++] = getec(efd);
chartorune(&r, buf);
Return:
*nb = n;
return r;
}
void
gete(CFid *efd, Event *e)
{
int i, nb;
e->c1 = getec(efd);
e->c2 = getec(efd);
e->q0 = geten(efd);
e->q1 = geten(efd);
e->flag = geten(efd);
e->nr = geten(efd);
if(e->nr > EVENTSIZE)
error("event string too long");
e->nb = 0;
for(i=0; i<e->nr; i++){
e->r[i] = geter(efd, e->b+e->nb, &nb);
e->nb += nb;
}
e->r[e->nr] = 0;
e->b[e->nb] = 0;
if(getec(efd) != '\n')
error("event syntax 2");
}
int
nrunes(char *s, int nb)
{
int i, n;
Rune r;
n = 0;
for(i=0; i<nb; n++)
i += chartorune(&r, s+i);
return n;
}
void
stdinproc(void *v)
{
CFid *cfd = ctlfd;
CFid *efd = eventfd;
CFid *dfd = datafd;
CFid *afd = addrfd;
int fd0 = rcfd;
Event e, e2, e3, e4;
int n;
USED(v);
for(;;){
if(debug)
fprint(2, "typing[%d,%d)\n", q.p, q.p+ntyper);
gete(efd, &e);
if(debug)
fprint(2, "msg %c%c q[%d,%d)... ", e.c1, e.c2, e.q0, e.q1);
qlock(&q.lk);
switch(e.c1){
default:
Unknown:
print("unknown message %c%c\n", e.c1, e.c2);
break;
case 'E': /* write to body or tag; can't affect us */
switch(e.c2){
case 'I':
case 'D': /* body */
if(debug)
fprint(2, "shift typing %d... ", e.q1-e.q0);
q.p += e.q1-e.q0;
break;
case 'i':
case 'd': /* tag */
break;
default:
goto Unknown;
}
break;
case 'F': /* generated by our actions; ignore */
break;
case 'K':
case 'M':
switch(e.c2){
case 'I':
if(e.nr == 1 && e.r[0] == 0x7F) {
char buf[1];
fsprint(addrfd, "#%ud,#%ud", e.q0, e.q1);
fswrite(datafd, "", 0);
buf[0] = 0x7F;
write(fd0, buf, 1);
break;
}
if(e.q0 < q.p){
if(debug)
fprint(2, "shift typing %d... ", e.q1-e.q0);
q.p += e.q1-e.q0;
}
else if(e.q0 <= q.p+ntyper){
if(debug)
fprint(2, "type... ");
type(&e, fd0, afd, dfd);
}
break;
case 'D':
n = delete(&e);
q.p -= n;
if(israw(fd0) && e.q1 >= q.p+n)
sendbs(fd0, n);
break;
case 'x':
case 'X':
if(e.flag & 2)
gete(efd, &e2);
if(e.flag & 8){
gete(efd, &e3);
gete(efd, &e4);
}
if(e.flag&1 || (e.c2=='x' && e.nr==0 && e2.nr==0)){
/* send it straight back */
fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
break;
}
if(e.q0==e.q1 && (e.flag&2)){
e2.flag = e.flag;
e = e2;
}
char buf[100];
snprint(buf, sizeof buf, "%.*S", e.nr, e.r);
if(cistrcmp(buf, "cook") == 0) {
cook = 1;
break;
}
if(cistrcmp(buf, "nocook") == 0) {
cook = 0;
break;
}
if(e.flag & 8){
if(e.q1 != e.q0){
sende(&e, fd0, cfd, afd, dfd, 0);
sende(&blank, fd0, cfd, afd, dfd, 0);
}
sende(&e3, fd0, cfd, afd, dfd, 1);
}else if(e.q1 != e.q0)
sende(&e, fd0, cfd, afd, dfd, 1);
break;
case 'l':
case 'L':
/* just send it back */
if(e.flag & 2)
gete(efd, &e2);
fsfidprint(efd, "%c%c%d %d\n", e.c1, e.c2, e.q0, e.q1);
break;
case 'd':
case 'i':
break;
default:
goto Unknown;
}
}
qunlock(&q.lk);
}
}
void
stdoutproc(void *v)
{
int fd1 = rcfd;
CFid *afd = addrfd;
CFid *dfd = datafd;
int n, m, w, npart;
char *buf, *s, *t;
Rune r;
char x[16], hold[UTFmax];
USED(v);
buf = malloc(8192+UTFmax+1);
npart = 0;
for(;;){
/* Let typing have a go -- maybe there's a rubout waiting. */
yield();
n = read(fd1, buf+npart, 8192);
if(n <= 0)
error(nil);
n = echocancel(buf+npart, n);
if(n == 0)
continue;
n = dropcrnl(buf+npart, n);
if(n == 0)
continue;
/* squash NULs */
s = memchr(buf+npart, 0, n);
if(s){
for(t=s; s<buf+npart+n; s++)
if(*t = *s) /* assign = */
t++;
n = t-(buf+npart);
}
n += npart;
/* hold on to final partial rune */
npart = 0;
while(n>0 && (buf[n-1]&0xC0)){
--n;
npart++;
if((buf[n]&0xC0)!=0x80){
if(fullrune(buf+n, npart)){
w = chartorune(&r, buf+n);
n += w;
npart -= w;
}
break;
}
}
if(n > 0){
memmove(hold, buf+n, npart);
buf[n] = 0;
n = label(buf, n);
buf[n] = 0;
// clumsy but effective: notice password
// prompts so we can disable echo.
password = 0;
if(cistrstr(buf, "password")) {
int i;
i = n;
while(i > 0 && buf[i-1] == ' ')
i--;
password = i > 0 && buf[i-1] == ':';
}
qlock(&q.lk);
m = sprint(x, "#%d", q.p);
if(fswrite(afd, x, m) != m){
fprint(2, "stdout writing address %s: %r; resetting\n", x);
if(fswrite(afd, "$", 1) < 0)
fprint(2, "reset: %r\n");
fsseek(afd, 0, 0);
m = fsread(afd, x, sizeof x-1);
if(m >= 0){
x[m] = 0;
q.p = atoi(x);
}
}
if(fswrite(dfd, buf, n) != n)
error("stdout writing body");
/* Make sure acme scrolls to the end of the above write. */
if(fswrite(dfd, nil, 0) != 0)
error("stdout flushing body");
q.p += nrunes(buf, n);
qunlock(&q.lk);
memmove(buf, hold, npart);
}
}
}
char wdir[512];
int
label(char *sr, int n)
{
char *sl, *el, *er, *r, *p;
er = sr+n;
for(r=er-1; r>=sr; r--)
if(*r == '\007')
break;
if(r < sr)
return n;
el = r+1;
if(el-sr > sizeof wdir - strlen(name) - 20)
sr = el - sizeof wdir - strlen(name) - 20;
for(sl=el-3; sl>=sr; sl--)
if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
break;
if(sl < sr)
return n;
*r = 0;
/*
* add /-sysname if not present
*/
snprint(wdir, sizeof wdir, "name %s", sl+3);
p = strrchr(wdir, '/');
if(p==nil || *(p+1) != '-'){
p = wdir+strlen(wdir);
if(*(p-1) != '/')
*p++ = '/';
*p++ = '-';
strcpy(p, name);
}
strcat(wdir, "\n0\n");
fswrite(ctlfd, wdir, strlen(wdir));
memmove(sl, el, er-el);
n -= (el-sl);
return n;
}
int
delete(Event *e)
{
uint q0, q1;
int deltap;
q0 = e->q0;
q1 = e->q1;
if(q1 <= q.p)
return e->q1-e->q0;
if(q0 >= q.p+ntyper)
return 0;
deltap = 0;
if(q0 < q.p){
deltap = q.p-q0;
q0 = 0;
}else
q0 -= q.p;
if(q1 > q.p+ntyper)
q1 = ntyper;
else
q1 -= q.p;
deltype(q0, q1);
return deltap;
}
void
addtype(int c, uint p0, char *b, int nb, int nr)
{
int i, w;
Rune r;
uint p;
char *b0;
for(i=0; i<nb; i+=w){
w = chartorune(&r, b+i);
if((r==0x7F||r==3) && c=='K'){
write(rcfd, "\x7F", 1);
/* toss all typing */
q.p += ntyper+nr;
ntypebreak = 0;
ntypeb = 0;
ntyper = 0;
/* buglet: more than one delete ignored */
return;
}
if(r=='\n' || r==0x04)
ntypebreak++;
}
typing = realloc(typing, ntypeb+nb);
if(typing == nil)
error("realloc");
if(p0 == ntyper)
memmove(typing+ntypeb, b, nb);
else{
b0 = typing;
for(p=0; p<p0 && b0<typing+ntypeb; p++){
w = chartorune(&r, b0+i);
b0 += w;
}
if(p != p0)
error("typing: findrune");
memmove(b0+nb, b0, (typing+ntypeb)-b0);
memmove(b0, b, nb);
}
ntypeb += nb;
ntyper += nr;
}
int
israw(int fd0)
{
return (!cook || password) && !isecho(fd0);
}
void
sendtype(int fd0)
{
int i, n, nr, raw;
raw = israw(fd0);
while(ntypebreak || (raw && ntypeb > 0)){
for(i=0; i<ntypeb; i++)
if(typing[i]=='\n' || typing[i]==0x04 || (i==ntypeb-1 && raw)){
if((typing[i] == '\n' || typing[i] == 0x04) && ntypebreak > 0)
ntypebreak--;
n = i+1;
i++;
if(!raw)
echoed(typing, n);
if(write(fd0, typing, n) != n)
error("sending to program");
nr = nrunes(typing, i);
q.p += nr;
ntyper -= nr;
ntypeb -= i;
memmove(typing, typing+i, ntypeb);
goto cont2;
}
print("no breakchar\n");
ntypebreak = 0;
cont2:;
}
}
void
sendbs(int fd0, int n)
{
char buf[128];
int m;
memset(buf, 0x08, sizeof buf);
while(n > 0) {
m = sizeof buf;
if(m > n)
m = n;
n -= m;
write(fd0, buf, m);
}
}
void
deltype(uint p0, uint p1)
{
int w;
uint p, b0, b1;
Rune r;
/* advance to p0 */
b0 = 0;
for(p=0; p<p0 && b0<ntypeb; p++){
w = chartorune(&r, typing+b0);
b0 += w;
}
if(p != p0)
error("deltype 1");
/* advance to p1 */
b1 = b0;
for(; p<p1 && b1<ntypeb; p++){
w = chartorune(&r, typing+b1);
b1 += w;
if(r=='\n' || r==0x04)
ntypebreak--;
}
if(p != p1)
error("deltype 2");
memmove(typing+b0, typing+b1, ntypeb-b1);
ntypeb -= b1-b0;
ntyper -= p1-p0;
}
void
type(Event *e, int fd0, CFid *afd, CFid *dfd)
{
int m, n, nr;
char buf[128];
if(e->nr > 0)
addtype(e->c1, e->q0-q.p, e->b, e->nb, e->nr);
else{
m = e->q0;
while(m < e->q1){
n = sprint(buf, "#%d", m);
fswrite(afd, buf, n);
n = fsread(dfd, buf, sizeof buf);
nr = nrunes(buf, n);
while(m+nr > e->q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
addtype(e->c1, m-q.p, buf, n, nr);
m += nr;
}
}
if(israw(fd0)) {
n = sprint(buf, "#%d,#%d", e->q0, e->q1);
fswrite(afd, buf, n);
fswrite(dfd, "", 0);
q.p -= e->q1 - e->q0;
}
sendtype(fd0);
if(e->nb > 0 && e->b[e->nb-1] == '\n')
cook = 1;
}
void
sende(Event *e, int fd0, CFid *cfd, CFid *afd, CFid *dfd, int donl)
{
int l, m, n, nr, lastc, end;
char abuf[16], buf[128];
end = q.p+ntyper;
l = sprint(abuf, "#%d", end);
fswrite(afd, abuf, l);
if(e->nr > 0){
fswrite(dfd, e->b, e->nb);
addtype(e->c1, ntyper, e->b, e->nb, e->nr);
lastc = e->r[e->nr-1];
}else{
m = e->q0;
lastc = 0;
while(m < e->q1){
n = sprint(buf, "#%d", m);
fswrite(afd, buf, n);
n = fsread(dfd, buf, sizeof buf);
nr = nrunes(buf, n);
while(m+nr > e->q1){
do; while(n>0 && (buf[--n]&0xC0)==0x80);
--nr;
}
if(n == 0)
break;
l = sprint(abuf, "#%d", end);
fswrite(afd, abuf, l);
fswrite(dfd, buf, n);
addtype(e->c1, ntyper, buf, n, nr);
lastc = buf[n-1];
m += nr;
end += nr;
}
}
if(donl && lastc!='\n'){
fswrite(dfd, "\n", 1);
addtype(e->c1, ntyper, "\n", 1, 1);
}
fswrite(cfd, "dot=addr", 8);
sendtype(fd0);
}