plan9port/src/cmd/9term/9term.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

639 lines
9.9 KiB
C

#include <u.h>
#include <signal.h>
#include <libc.h>
#include <ctype.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <cursor.h>
#include <keyboard.h>
#include <frame.h>
#include <plumb.h>
#include <complete.h>
#define Extern
#include "dat.h"
#include "fns.h"
#include "term.h"
int use9wm;
int mainpid;
int mousepid;
int plumbfd;
int rcpid;
int rcfd;
int sfd;
Window *w;
char *fontname;
void derror(Display*, char*);
void mousethread(void*);
void keyboardthread(void*);
void winclosethread(void*);
void deletethread(void*);
void rcoutputproc(void*);
void rcinputproc(void*);
void hangupnote(void*, char*);
void resizethread(void*);
void servedevtext(void);
int errorshouldabort = 0;
int cooked;
void
usage(void)
{
fprint(2, "usage: 9term [-s] [-f font] [-W winsize] [cmd ...]\n");
threadexitsall("usage");
}
void
threadmain(int argc, char *argv[])
{
char *p;
rfork(RFNOTEG);
font = nil;
_wantfocuschanges = 1;
mainpid = getpid();
messagesize = 8192;
ARGBEGIN{
default:
usage();
case 'l':
loginshell = TRUE;
break;
case 'f':
fontname = EARGF(usage());
break;
case 's':
/* no-op */
break;
case 'c':
cooked = TRUE;
break;
case 'w': /* started from rio or 9wm */
use9wm = TRUE;
break;
case 'W':
winsize = EARGF(usage());
break;
}ARGEND
if(fontname)
putenv("font", fontname);
p = getenv("tabstop");
if(p == 0)
p = getenv("TABSTOP");
if(p && maxtab <= 0)
maxtab = strtoul(p, 0, 0);
if(maxtab <= 0)
maxtab = 4;
free(p);
startdir = ".";
if(initdraw(derror, fontname, "9term") < 0)
sysfatal("initdraw: %r");
notify(hangupnote);
noteenable("sys: child");
mousectl = initmouse(nil, screen);
if(mousectl == nil)
error("cannot find mouse");
keyboardctl = initkeyboard(nil);
if(keyboardctl == nil)
error("cannot find keyboard");
mouse = &mousectl->m;
winclosechan = chancreate(sizeof(Window*), 0);
deletechan = chancreate(sizeof(char*), 0);
timerinit();
servedevtext();
rcpid = rcstart(argc, argv, &rcfd, &sfd);
w = new(screen, FALSE, rcpid, ".", nil, nil);
threadcreate(keyboardthread, nil, STACK);
threadcreate(mousethread, nil, STACK);
threadcreate(resizethread, nil, STACK);
proccreate(rcoutputproc, nil, STACK);
proccreate(rcinputproc, nil, STACK);
}
void
derror(Display *d, char *errorstr)
{
USED(d);
error(errorstr);
}
void
hangupnote(void *a, char *msg)
{
if(getpid() != mainpid)
noted(NDFLT);
if(strcmp(msg, "hangup") == 0){
postnote(PNPROC, rcpid, "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) == rcpid)
threadexitsall(0);
}
noted(NCONT);
}
noted(NDFLT);
}
void
keyboardthread(void *v)
{
Rune buf[2][20], *rp;
int i, n;
USED(v);
threadsetname("keyboardthread");
n = 0;
for(;;){
rp = buf[n];
n = 1-n;
recv(keyboardctl->c, rp);
for(i=1; i<nelem(buf[0])-1; i++)
if(nbrecv(keyboardctl->c, rp+i) <= 0)
break;
rp[i] = L'\0';
sendp(w->ck, rp);
}
}
void
resizethread(void *v)
{
Point p;
USED(v);
for(;;){
p = stringsize(display->defaultfont, "0");
if(p.x && p.y)
updatewinsize(Dy(screen->r)/p.y, (Dx(screen->r)-Scrollwid-2)/p.x,
Dx(screen->r), Dy(screen->r));
wresize(w, screen, 0);
flushimage(display, 1);
if(recv(mousectl->resizec, nil) != 1)
break;
if(getwindow(display, Refnone) < 0)
sysfatal("can't reattach to window");
}
}
void
mousethread(void *v)
{
int sending;
Mouse tmp;
USED(v);
sending = FALSE;
threadsetname("mousethread");
while(readmouse(mousectl) >= 0){
if(sending){
Send:
/* send to window */
if(mouse->buttons == 0)
sending = FALSE;
else
wsetcursor(w, 0);
tmp = mousectl->m;
send(w->mc.c, &tmp);
continue;
}
if((mouse->buttons&(1|8|16)) || ptinrect(mouse->xy, w->scrollr)){
sending = TRUE;
goto Send;
}else if(mouse->buttons&2)
button2menu(w);
else
bouncemouse(mouse);
}
}
void
wborder(Window *w, int type)
{
}
Window*
wpointto(Point pt)
{
return w;
}
Window*
new(Image *i, int hideit, int pid, char *dir, char *cmd, char **argv)
{
Window *w;
Mousectl *mc;
Channel *cm, *ck, *cctl;
if(i == nil)
return nil;
cm = chancreate(sizeof(Mouse), 0);
ck = chancreate(sizeof(Rune*), 0);
cctl = chancreate(sizeof(Wctlmesg), 4);
if(cm==nil || ck==nil || cctl==nil)
error("new: channel alloc failed");
mc = emalloc(sizeof(Mousectl));
*mc = *mousectl;
/* mc->image = i; */
mc->c = cm;
w = wmk(i, mc, ck, cctl);
free(mc); /* wmk copies *mc */
window = erealloc(window, ++nwindow*sizeof(Window*));
window[nwindow-1] = w;
if(hideit){
hidden[nhidden++] = w;
w->screenr = ZR;
}
threadcreate(winctl, w, STACK);
if(!hideit)
wcurrent(w);
flushimage(display, 1);
wsetpid(w, pid, 1);
wsetname(w);
if(dir)
w->dir = estrdup(dir);
return w;
}
/*
* Button 2 menu. Extra entry for always cook
*/
enum
{
Cut,
Paste,
Snarf,
Plumb,
Send,
Cook
};
char *menu2str[] = {
"cut",
"paste",
"snarf",
"plumb",
"send",
"cook",
nil
};
Menu menu2 =
{
menu2str
};
Rune newline[] = { '\n' };
void
button2menu(Window *w)
{
if(w->deleted)
return;
incref(&w->ref);
if(cooked)
menu2str[Cook] = "nocook";
else
menu2str[Cook] = "cook";
switch(menuhit(2, mousectl, &menu2, wscreen)){
case Cut:
wsnarf(w);
wcut(w);
wscrdraw(w);
break;
case Snarf:
wsnarf(w);
break;
case Paste:
riogetsnarf();
wpaste(w);
wscrdraw(w);
break;
case Plumb:
wplumb(w);
break;
case Send:
riogetsnarf();
wsnarf(w);
if(nsnarf == 0)
break;
if(w->rawing){
waddraw(w, snarf, nsnarf);
if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
waddraw(w, newline, 1);
}else{
winsert(w, snarf, nsnarf, w->nr);
if(snarf[nsnarf-1]!='\n' && snarf[nsnarf-1]!='\004')
winsert(w, newline, 1, w->nr);
}
wsetselect(w, w->nr, w->nr);
wshow(w, w->nr);
break;
case Cook:
cooked ^= 1;
break;
}
wclose(w);
wsendctlmesg(w, Wakeup, ZR, nil);
flushimage(display, 1);
}
int
rawon(void)
{
return !cooked && !isecho(sfd);
}
/*
* I/O with child rc.
*/
int label(Rune*, int);
void
rcoutputproc(void *arg)
{
int i, cnt, n, nb, nr;
static char data[9000];
Conswritemesg cwm;
Rune *r;
Stringpair pair;
i = 0;
cnt = 0;
for(;;){
/* XXX Let typing have a go -- maybe there's a rubout waiting. */
i = 1-i;
n = read(rcfd, data+cnt, sizeof data-cnt);
if(n <= 0){
if(n < 0)
fprint(2, "9term: rc read error: %r\n");
threadexitsall("eof on rc output");
}
n = echocancel(data+cnt, n);
if(n == 0)
continue;
cnt += n;
r = runemalloc(cnt);
cvttorunes(data, cnt-UTFmax, r, &nb, &nr, nil);
/* approach end of buffer */
while(fullrune(data+nb, cnt-nb)){
nb += chartorune(&r[nr], data+nb);
if(r[nr])
nr++;
}
if(nb < cnt)
memmove(data, data+nb, cnt-nb);
cnt -= nb;
nr = label(r, nr);
if(nr == 0)
continue;
recv(w->conswrite, &cwm);
pair.s = r;
pair.ns = nr;
send(cwm.cw, &pair);
}
}
void
winterrupt(Window *w)
{
char rubout[1];
USED(w);
rubout[0] = getintr(sfd);
write(rcfd, rubout, 1);
}
/*
* Process in-band messages about window title changes.
* The messages are of the form:
*
* \033];xxx\007
*
* where xxx is the new directory. This format was chosen
* because it changes the label on xterm windows.
*/
int
label(Rune *sr, int n)
{
Rune *sl, *el, *er, *r;
char *p, *dir;
er = sr+n;
for(r=er-1; r>=sr; r--)
if(*r == '\007')
break;
if(r < sr)
return n;
el = r+1;
for(sl=el-3; sl>=sr; sl--)
if(sl[0]=='\033' && sl[1]==']' && sl[2]==';')
break;
if(sl < sr)
return n;
dir = smprint("%.*S", (el-1)-(sl+3), sl+3);
if(dir){
drawsetlabel(dir);
free(w->dir);
w->dir = dir;
}
/* remove trailing /-sysname if present */
p = strrchr(dir, '/');
if(p && *(p+1) == '-'){
if(p == dir)
p++;
*p = 0;
}
runemove(sl, el, er-el);
n -= (el-sl);
return n;
}
void
rcinputproc(void *arg)
{
static char data[9000];
Consreadmesg crm;
Channel *c1, *c2;
Stringpair pair;
for(;;){
recv(w->consread, &crm);
c1 = crm.c1;
c2 = crm.c2;
pair.s = data;
pair.ns = sizeof data;
send(c1, &pair);
recv(c2, &pair);
if(isecho(sfd))
echoed(pair.s, pair.ns);
if(write(rcfd, pair.s, pair.ns) < 0)
threadexitsall(nil);
}
}
/*
* Snarf buffer - rio uses runes internally
*/
void
rioputsnarf(void)
{
char *s;
s = smprint("%.*S", nsnarf, snarf);
if(s){
putsnarf(s);
free(s);
}
}
void
riogetsnarf(void)
{
char *s;
int n, nb, nulls;
s = getsnarf();
if(s == nil)
return;
n = strlen(s)+1;
free(snarf);
snarf = runemalloc(n);
cvttorunes(s, n, snarf, &nb, &nsnarf, &nulls);
free(s);
}
/*
* Clumsy hack to make " and "" work.
* Then again, what's not a clumsy hack here in Unix land?
*/
char adir[100];
char thesocket[100];
int afd;
void listenproc(void*);
void textproc(void*);
void
removethesocket(void)
{
if(thesocket[0])
if(remove(thesocket) < 0)
fprint(2, "remove %s: %r\n", thesocket);
}
void
servedevtext(void)
{
char buf[100];
snprint(buf, sizeof buf, "unix!/tmp/9term-text.%d", getpid());
if((afd = announce(buf, adir)) < 0){
putenv("text9term", "");
return;
}
putenv("text9term", buf);
proccreate(listenproc, nil, STACK);
strcpy(thesocket, buf+5);
atexit(removethesocket);
}
void
listenproc(void *arg)
{
int fd;
char dir[100];
threadsetname("listen %s", thesocket);
USED(arg);
for(;;){
fd = listen(adir, dir);
if(fd < 0){
close(afd);
return;
}
proccreate(textproc, (void*)(uintptr)fd, STACK);
}
}
void
textproc(void *arg)
{
int fd, i, x, n, end;
Rune r;
char buf[4096], *p, *ep;
threadsetname("textproc");
fd = (uintptr)arg;
p = buf;
ep = buf+sizeof buf;
if(w == nil){
close(fd);
return;
}
end = w->org+w->nr; /* avoid possible output loop */
for(i=w->org;; i++){
if(i >= end || ep-p < UTFmax){
for(x=0; x<p-buf; x+=n)
if((n = write(fd, buf+x, (p-x)-buf)) <= 0)
goto break2;
if(i >= end)
break;
p = buf;
}
if(i < w->org)
i = w->org;
r = w->r[i-w->org];
if(r < Runeself)
*p++ = r;
else
p += runetochar(p, &r);
}
break2:
close(fd);
}