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

1655 lines
32 KiB
C

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <9pclient.h>
#include <plumb.h>
#include <complete.h>
#include "dat.h"
#include "fns.h"
#define MOVEIT if(0)
enum
{
HiWater = 640000, /* max size of history */
LoWater = 400000, /* min size of history after max'ed */
MinWater = 20000 /* room to leave available when reallocating */
};
static int topped;
static int id;
static Image *cols[NCOL];
static Image *grey;
static Image *darkgrey;
static Cursor *lastcursor;
static Image *titlecol;
static Image *lighttitlecol;
static Image *holdcol;
static Image *lightholdcol;
static Image *paleholdcol;
Window*
wmk(Image *i, Mousectl *mc, Channel *ck, Channel *cctl)
{
Window *w;
Rectangle r;
if(cols[0] == nil){
/* greys are multiples of 0x11111100+0xFF, 14* being palest */
grey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
darkgrey = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x666666FF);
cols[BACK] = display->white;
cols[HIGH] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
cols[BORD] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x999999FF);
cols[TEXT] = display->black;
cols[HTEXT] = display->black;
titlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreygreen);
lighttitlecol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreygreen);
holdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DMedblue);
lightholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DGreyblue);
paleholdcol = allocimage(display, Rect(0,0,1,1), CMAP8, 1, DPalegreyblue);
}
w = emalloc(sizeof(Window));
w->screenr = i->r;
r = insetrect(i->r, Selborder+1);
w->i = i;
w->mc = *mc;
w->ck = ck;
w->cctl = cctl;
w->cursorp = nil;
w->conswrite = chancreate(sizeof(Conswritemesg), 0);
w->consread = chancreate(sizeof(Consreadmesg), 0);
w->mouseread = chancreate(sizeof(Mousereadmesg), 0);
w->wctlread = chancreate(sizeof(Consreadmesg), 0);
w->scrollr = r;
w->scrollr.max.x = r.min.x+Scrollwid;
w->lastsr = ZR;
r.min.x += Scrollwid+Scrollgap;
frinit(&w->f, r, font, i, cols);
w->f.maxtab = maxtab*stringwidth(font, "0");
w->topped = ++topped;
w->id = ++id;
w->notefd = -1;
w->dir = estrdup(startdir);
w->label = estrdup("<unnamed>");
r = insetrect(w->i->r, Selborder);
draw(w->i, r, cols[BACK], nil, w->f.entire.min);
wborder(w, Selborder);
wscrdraw(w);
incref(&w->ref); /* ref will be removed after mounting; avoids delete before ready to be deleted */
return w;
}
void
wsetname(Window *w)
{
int i, n;
char err[ERRMAX];
n = sprint(w->name, "window.%d.%d", w->id, w->namecount++);
for(i='A'; i<='Z'; i++){
if(nameimage(w->i, w->name, 1) > 0)
return;
errstr(err, sizeof err);
if(strcmp(err, "image name in use") != 0)
break;
w->name[n] = i;
w->name[n+1] = 0;
}
w->name[0] = 0;
fprint(2, "rio: setname failed: %s\n", err);
}
void
wresize(Window *w, Image *i, int move)
{
Rectangle r, or;
or = w->i->r;
if(move || (Dx(or)==Dx(i->r) && Dy(or)==Dy(i->r)))
draw(i, i->r, w->i, nil, w->i->r.min);
if(w->i != i){
fprint(2, "res %p %p\n", w->i, i);
freeimage(w->i);
w->i = i;
}
/* wsetname(w); */
/*XXX w->mc.image = i; */
r = insetrect(i->r, Selborder+1);
w->scrollr = r;
w->scrollr.max.x = r.min.x+Scrollwid;
w->lastsr = ZR;
r.min.x += Scrollwid+Scrollgap;
if(move)
frsetrects(&w->f, r, w->i);
else{
frclear(&w->f, FALSE);
frinit(&w->f, r, w->f.font, w->i, cols);
wsetcols(w);
w->f.maxtab = maxtab*stringwidth(w->f.font, "0");
r = insetrect(w->i->r, Selborder);
draw(w->i, r, cols[BACK], nil, w->f.entire.min);
wfill(w);
wsetselect(w, w->q0, w->q1);
wscrdraw(w);
}
wborder(w, Selborder);
w->topped = ++topped;
w->resized = TRUE;
w->mouse.counter++;
}
void
wrefresh(Window *w, Rectangle r)
{
/* USED(r); */
/* BUG: rectangle is ignored */
if(w == input)
wborder(w, Selborder);
else
wborder(w, Unselborder);
if(w->mouseopen)
return;
draw(w->i, insetrect(w->i->r, Borderwidth), w->f.cols[BACK], nil, w->i->r.min);
w->f.ticked = 0;
if(w->f.p0 > 0)
frdrawsel(&w->f, frptofchar(&w->f, 0), 0, w->f.p0, 0);
if(w->f.p1 < w->f.nchars)
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, w->f.nchars, 0);
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 1);
w->lastsr = ZR;
wscrdraw(w);
}
int
wclose(Window *w)
{
int i;
i = decref(&w->ref);
if(i > 0)
return 0;
if(i < 0)
error("negative ref count");
if(!w->deleted)
wclosewin(w);
wsendctlmesg(w, Exited, ZR, nil);
return 1;
}
void
winctl(void *arg)
{
Rune *rp, *bp, *up, *kbdr;
uint qh;
int nr, nb, c, wid, i, npart, initial, lastb, scrolling;
char *s, *t, part[UTFmax];
Window *w;
Mousestate *mp, m;
enum { WKey, WMouse, WMouseread, WCtl, WCwrite, WCread, WWread, NWALT };
Alt alts[NWALT+1];
Mousereadmesg mrm;
Conswritemesg cwm;
Consreadmesg crm;
Consreadmesg cwrm;
Stringpair pair;
Wctlmesg wcm;
char buf[4*12+1];
w = arg;
snprint(buf, sizeof buf, "winctl-id%d", w->id);
threadsetname(buf);
mrm.cm = chancreate(sizeof(Mouse), 0);
cwm.cw = chancreate(sizeof(Stringpair), 0);
crm.c1 = chancreate(sizeof(Stringpair), 0);
crm.c2 = chancreate(sizeof(Stringpair), 0);
cwrm.c1 = chancreate(sizeof(Stringpair), 0);
cwrm.c2 = chancreate(sizeof(Stringpair), 0);
alts[WKey].c = w->ck;
alts[WKey].v = &kbdr;
alts[WKey].op = CHANRCV;
alts[WMouse].c = w->mc.c;
alts[WMouse].v = &w->mc.m;
alts[WMouse].op = CHANRCV;
alts[WMouseread].c = w->mouseread;
alts[WMouseread].v = &mrm;
alts[WMouseread].op = CHANSND;
alts[WCtl].c = w->cctl;
alts[WCtl].v = &wcm;
alts[WCtl].op = CHANRCV;
alts[WCwrite].c = w->conswrite;
alts[WCwrite].v = &cwm;
alts[WCwrite].op = CHANSND;
alts[WCread].c = w->consread;
alts[WCread].v = &crm;
alts[WCread].op = CHANSND;
alts[WWread].c = w->wctlread;
alts[WWread].v = &cwrm;
alts[WWread].op = CHANSND;
alts[NWALT].op = CHANEND;
npart = 0;
lastb = -1;
for(;;){
if(w->mouseopen && w->mouse.counter != w->mouse.lastcounter)
alts[WMouseread].op = CHANSND;
else
alts[WMouseread].op = CHANNOP;
alts[WCwrite].op = CHANSND;
if(w->deleted || !w->wctlready)
alts[WWread].op = CHANNOP;
else
alts[WWread].op = CHANSND;
/* this code depends on NL and EOT fitting in a single byte */
/* kind of expensive for each loop; worth precomputing? */
if(w->holding)
alts[WCread].op = CHANNOP;
else if(npart || (w->rawing && w->nraw>0))
alts[WCread].op = CHANSND;
else{
alts[WCread].op = CHANNOP;
for(i=w->qh; i<w->nr; i++){
c = w->r[i];
if(c=='\n' || c=='\004'){
alts[WCread].op = CHANSND;
break;
}
}
}
switch(alt(alts)){
case WKey:
for(i=0; kbdr[i]!=L'\0'; i++)
wkeyctl(w, kbdr[i]);
/* wkeyctl(w, r); */
/* while(nbrecv(w->ck, &r)) */
/* wkeyctl(w, r); */
break;
case WMouse:
if(w->mouseopen) {
w->mouse.counter++;
/* queue click events */
if(!w->mouse.qfull && lastb != w->mc.m.buttons) { /* add to ring */
mp = &w->mouse.queue[w->mouse.wi];
if(++w->mouse.wi == nelem(w->mouse.queue))
w->mouse.wi = 0;
if(w->mouse.wi == w->mouse.ri)
w->mouse.qfull = TRUE;
mp->m = w->mc.m;
mp->counter = w->mouse.counter;
lastb = w->mc.m.buttons;
}
} else
wmousectl(w);
break;
case WMouseread:
/* send a queued event or, if the queue is empty, the current state */
/* if the queue has filled, we discard all the events it contained. */
/* the intent is to discard frantic clicking by the user during long latencies. */
w->mouse.qfull = FALSE;
if(w->mouse.wi != w->mouse.ri) {
m = w->mouse.queue[w->mouse.ri];
if(++w->mouse.ri == nelem(w->mouse.queue))
w->mouse.ri = 0;
} else {
m.m = w->mc.m;
m.counter = w->mouse.counter;
}
w->mouse.lastcounter = m.counter;
send(mrm.cm, &m.m);
continue;
case WCtl:
if(wctlmesg(w, wcm.type, wcm.r, wcm.image) == Exited){
chanfree(crm.c1);
chanfree(crm.c2);
chanfree(mrm.cm);
chanfree(cwm.cw);
chanfree(cwrm.c1);
chanfree(cwrm.c2);
threadexits(nil);
}
continue;
case WCwrite:
recv(cwm.cw, &pair);
rp = pair.s;
nr = pair.ns;
bp = rp;
up = rp;
initial = 0;
for(i=0; i<nr; i++){
switch(*bp){
case 0:
break;
case '\b':
if(up == rp)
initial++;
else
--up;
break;
case '\r':
while(i<nr-1 && *(bp+1) == '\r'){
bp++;
i++;
}
if(i<nr-1 && *(bp+1) != '\n'){
while(up > rp && *(up-1) != '\n')
up--;
if(up == rp)
initial = wbswidth(w, '\r');
}else if(i == nr-1)
*up = '\n';
break;
default:
*up++ = *bp;
break;
}
bp++;
}
if(initial){
if(initial > w->qh)
initial = w->qh;
qh = w->qh - initial;
wdelete(w, qh, qh+initial);
w->qh = qh;
}
nr = up - rp;
scrolling = w->org <= w->qh && w->qh <= w->org + w->f.nchars;
w->qh = winsert(w, rp, nr, w->qh)+nr;
if(scrolling)
wshow(w, w->qh);
wsetselect(w, w->q0, w->q1);
wscrdraw(w);
free(rp);
break;
case WCread:
recv(crm.c1, &pair);
t = pair.s;
nb = pair.ns;
i = npart;
npart = 0;
if(i)
memmove(t, part, i);
while(i<nb && (w->qh<w->nr || w->nraw>0)){
if(w->qh == w->nr){
wid = runetochar(t+i, &w->raw[0]);
w->nraw--;
runemove(w->raw, w->raw+1, w->nraw);
}else
wid = runetochar(t+i, &w->r[w->qh++]);
c = t[i]; /* knows break characters fit in a byte */
i += wid;
if(!w->rawing && (c == '\n' || c=='\004')){
/* if(c == '\004') */
/* i--; */
break;
}
}
/* if(i==nb && w->qh<w->nr && w->r[w->qh]=='\004') */
/* w->qh++; */
if(i > nb){
npart = i-nb;
memmove(part, t+nb, npart);
i = nb;
}
pair.s = t;
pair.ns = i;
send(crm.c2, &pair);
continue;
case WWread:
w->wctlready = 0;
recv(cwrm.c1, &pair);
if(w->deleted || w->i==nil)
pair.ns = sprint(pair.s, "");
else{
s = "visible";
for(i=0; i<nhidden; i++)
if(hidden[i] == w){
s = "hidden";
break;
}
t = "notcurrent";
if(w == input)
t = "current";
pair.ns = snprint(pair.s, pair.ns, "%11d %11d %11d %11d %s %s ",
w->i->r.min.x, w->i->r.min.y, w->i->r.max.x, w->i->r.max.y, t, s);
}
send(cwrm.c2, &pair);
continue;
}
if(!w->deleted)
flushimage(display, 1);
}
}
void
waddraw(Window *w, Rune *r, int nr)
{
w->raw = runerealloc(w->raw, w->nraw+nr);
runemove(w->raw+w->nraw, r, nr);
w->nraw += nr;
}
/*
* Need to do this in a separate proc because if process we're interrupting
* is dying and trying to print tombstone, kernel is blocked holding p->debug lock.
*/
void
interruptproc(void *v)
{
int *notefd;
notefd = v;
write(*notefd, "interrupt", 9);
free(notefd);
}
int
windfilewidth(Window *w, uint q0, int oneelement)
{
uint q;
Rune r;
q = q0;
while(q > 0){
r = w->r[q-1];
if(r<=' ')
break;
if(oneelement && r=='/')
break;
--q;
}
return q0-q;
}
void
showcandidates(Window *w, Completion *c)
{
int i;
Fmt f;
Rune *rp;
uint nr, qline, q0;
char *s;
runefmtstrinit(&f);
if (c->nmatch == 0)
s = "[no matches in ";
else
s = "[";
if(c->nfile > 32)
fmtprint(&f, "%s%d files]\n", s, c->nfile);
else{
fmtprint(&f, "%s", s);
for(i=0; i<c->nfile; i++){
if(i > 0)
fmtprint(&f, " ");
fmtprint(&f, "%s", c->filename[i]);
}
fmtprint(&f, "]\n");
}
/* place text at beginning of line before host point */
qline = w->qh;
while(qline>0 && w->r[qline-1] != '\n')
qline--;
rp = runefmtstrflush(&f);
nr = runestrlen(rp);
q0 = w->q0;
q0 += winsert(w, rp, runestrlen(rp), qline) - qline;
free(rp);
wsetselect(w, q0+nr, q0+nr);
}
Rune*
namecomplete(Window *w)
{
int nstr, npath;
Rune *rp, *path, *str;
Completion *c;
char *s, *dir, *root;
/* control-f: filename completion; works back to white space or / */
if(w->q0<w->nr && w->r[w->q0]>' ') /* must be at end of word */
return nil;
nstr = windfilewidth(w, w->q0, TRUE);
str = runemalloc(nstr);
runemove(str, w->r+(w->q0-nstr), nstr);
npath = windfilewidth(w, w->q0-nstr, FALSE);
path = runemalloc(npath);
runemove(path, w->r+(w->q0-nstr-npath), npath);
rp = nil;
/* is path rooted? if not, we need to make it relative to window path */
if(npath>0 && path[0]=='/'){
dir = malloc(UTFmax*npath+1);
sprint(dir, "%.*S", npath, path);
}else{
if(strcmp(w->dir, "") == 0)
root = ".";
else
root = w->dir;
dir = malloc(strlen(root)+1+UTFmax*npath+1);
sprint(dir, "%s/%.*S", root, npath, path);
}
dir = cleanname(dir);
s = smprint("%.*S", nstr, str);
c = complete(dir, s);
free(s);
if(c == nil)
goto Return;
if(!c->advance)
showcandidates(w, c);
if(c->advance)
rp = runesmprint("%s", c->string);
Return:
freecompletion(c);
free(dir);
free(path);
free(str);
return rp;
}
void
wkeyctl(Window *w, Rune r)
{
uint q0 ,q1;
int n, nb, nr;
Rune *rp;
if(r == 0)
return;
if(w->deleted)
return;
w->rawing = rawon();
/* navigation keys work only when mouse is not open */
if(!w->mouseopen)
switch(r){
case Kdown:
n = w->f.maxlines/3;
goto case_Down;
case Kscrollonedown:
n = mousescrollsize(w->f.maxlines);
if(n <= 0)
n = 1;
goto case_Down;
case Kpgdown:
n = 2*w->f.maxlines/3;
case_Down:
q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+n*w->f.font->height));
wsetorigin(w, q0, TRUE);
return;
case Kup:
n = w->f.maxlines/3;
goto case_Up;
case Kscrolloneup:
n = mousescrollsize(w->f.maxlines);
if(n <= 0)
n = 1;
goto case_Up;
case Kpgup:
n = 2*w->f.maxlines/3;
case_Up:
q0 = wbacknl(w, w->org, n);
wsetorigin(w, q0, TRUE);
return;
case Kleft:
if(w->q0 > 0){
q0 = w->q0-1;
wsetselect(w, q0, q0);
wshow(w, q0);
}
return;
case Kright:
if(w->q1 < w->nr){
q1 = w->q1+1;
wsetselect(w, q1, q1);
wshow(w, q1);
}
return;
case Khome:
wshow(w, 0);
return;
case Kend:
wshow(w, w->nr);
return;
case 0x01: /* ^A: beginning of line */
if(w->q0==0 || w->q0==w->qh || w->r[w->q0-1]=='\n')
return;
nb = wbswidth(w, 0x15 /* ^U */);
wsetselect(w, w->q0-nb, w->q0-nb);
wshow(w, w->q0);
return;
case 0x05: /* ^E: end of line */
q0 = w->q0;
while(q0 < w->nr && w->r[q0]!='\n')
q0++;
wsetselect(w, q0, q0);
wshow(w, w->q0);
return;
}
/*
* This if used to be below the if(w->rawing ...),
* but let's try putting it here. This will allow ESC-processing
* to toggle hold mode even in remote SSH connections.
* The drawback is that vi-style processing gets harder.
* If you find yourself in some weird readline mode, good
* luck getting out without ESC. Let's see who complains.
*/
if(r==0x1B || (w->holding && r==0x7F)){ /* toggle hold */
if(w->holding)
--w->holding;
else
w->holding++;
wrepaint(w);
if(r == 0x1B)
return;
}
if(!w->holding && w->rawing && (w->q0==w->nr || w->mouseopen)){
waddraw(w, &r, 1);
return;
}
if(r == Kcmd+'x'){
wsnarf(w);
wcut(w);
wscrdraw(w);
return;
}
if(r == Kcmd+'c'){
wsnarf(w);
return;
}
if(r == Kcmd+'v'){
riogetsnarf();
wpaste(w);
wscrdraw(w);
return;
}
if(r != 0x7F){
wsnarf(w);
wcut(w);
}
switch(r){
case 0x7F: /* send interrupt */
w->qh = w->nr;
wshow(w, w->qh);
winterrupt(w);
return;
case 0x06: /* ^F: file name completion */
case Kins: /* Insert: file name completion */
rp = namecomplete(w);
if(rp == nil)
return;
nr = runestrlen(rp);
q0 = w->q0;
q0 = winsert(w, rp, nr, q0);
wshow(w, q0+nr);
free(rp);
return;
case 0x08: /* ^H: erase character */
case 0x15: /* ^U: erase line */
case 0x17: /* ^W: erase word */
if(w->q0==0 || w->q0==w->qh)
return;
nb = wbswidth(w, r);
q1 = w->q0;
q0 = q1-nb;
if(q0 < w->org){
q0 = w->org;
nb = q1-q0;
}
if(nb > 0){
wdelete(w, q0, q0+nb);
wsetselect(w, q0, q0);
}
return;
}
/* otherwise ordinary character; just insert */
q0 = w->q0;
q0 = winsert(w, &r, 1, q0);
wshow(w, q0+1);
}
void
wsetcols(Window *w)
{
if(w->holding)
if(w == input)
w->f.cols[TEXT] = w->f.cols[HTEXT] = holdcol;
else
w->f.cols[TEXT] = w->f.cols[HTEXT] = lightholdcol;
else
if(w == input)
w->f.cols[TEXT] = w->f.cols[HTEXT] = display->black;
else
w->f.cols[TEXT] = w->f.cols[HTEXT] = darkgrey;
}
void
wrepaint(Window *w)
{
wsetcols(w);
if(!w->mouseopen){
frredraw(&w->f);
}
if(w == input){
wborder(w, Selborder);
wsetcursor(w, 0);
}else
wborder(w, Unselborder);
}
int
wbswidth(Window *w, Rune c)
{
uint q, eq, stop;
Rune r;
int skipping;
/* there is known to be at least one character to erase */
if(c == 0x08) /* ^H: erase character */
return 1;
q = w->q0;
stop = 0;
if(q > w->qh)
stop = w->qh;
skipping = TRUE;
while(q > stop){
r = w->r[q-1];
if(r == '\n'){ /* eat at most one more character */
if(q == w->q0 && c != '\r') /* eat the newline */
--q;
break;
}
if(c == 0x17){
eq = isalnum(r);
if(eq && skipping) /* found one; stop skipping */
skipping = FALSE;
else if(!eq && !skipping)
break;
}
--q;
}
return w->q0-q;
}
void
wsnarf(Window *w)
{
if(w->q1 == w->q0)
return;
nsnarf = w->q1-w->q0;
snarf = runerealloc(snarf, nsnarf);
snarfversion++; /* maybe modified by parent */
runemove(snarf, w->r+w->q0, nsnarf);
rioputsnarf();
}
void
wcut(Window *w)
{
if(w->q1 == w->q0)
return;
wdelete(w, w->q0, w->q1);
wsetselect(w, w->q0, w->q0);
}
void
wpaste(Window *w)
{
uint q0;
if(nsnarf == 0)
return;
wcut(w);
q0 = w->q0;
if(w->rawing && !w->holding && q0==w->nr){
waddraw(w, snarf, nsnarf);
wsetselect(w, q0, q0);
}else{
q0 = winsert(w, snarf, nsnarf, w->q0);
wsetselect(w, q0, q0+nsnarf);
}
}
void
wplumb(Window *w)
{
Plumbmsg *m;
static CFid *fd;
char buf[32];
uint p0, p1;
Cursor *c;
if(fd == nil)
fd = plumbopenfid("send", OWRITE);
if(fd == nil)
return;
m = emalloc(sizeof(Plumbmsg));
m->src = estrdup("rio");
m->dst = nil;
m->wdir = estrdup(w->dir);
m->type = estrdup("text");
p0 = w->q0;
p1 = w->q1;
if(w->q1 > w->q0)
m->attr = nil;
else{
while(p0>0 && w->r[p0-1]!=' ' && w->r[p0-1]!='\t' && w->r[p0-1]!='\n')
p0--;
while(p1<w->nr && w->r[p1]!=' ' && w->r[p1]!='\t' && w->r[p1]!='\n')
p1++;
sprint(buf, "click=%d", w->q0-p0);
m->attr = plumbunpackattr(buf);
}
if(p1-p0 > messagesize-1024){
plumbfree(m);
return; /* too large for 9P */
}
m->data = runetobyte(w->r+p0, p1-p0, &m->ndata);
if(plumbsendtofid(fd, m) < 0){
c = lastcursor;
riosetcursor(&query, 1);
sleep(300);
riosetcursor(c, 1);
}
plumbfree(m);
}
int
winborder(Window *w, Point xy)
{
return ptinrect(xy, w->screenr) && !ptinrect(xy, insetrect(w->screenr, Selborder));
}
void
wmousectl(Window *w)
{
int but;
if(w->mc.m.buttons == 1)
but = 1;
else if(w->mc.m.buttons == 2)
but = 2;
else if(w->mc.m.buttons == 4)
but = 3;
else{
if(w->mc.m.buttons == 8)
wkeyctl(w, Kscrolloneup);
if(w->mc.m.buttons == 16)
wkeyctl(w, Kscrollonedown);
return;
}
incref(&w->ref); /* hold up window while we track */
if(w->deleted)
goto Return;
if(ptinrect(w->mc.m.xy, w->scrollr)){
if(but)
wscroll(w, but);
goto Return;
}
if(but == 1)
wselect(w);
/* else all is handled by main process */
Return:
wclose(w);
}
void
wdelete(Window *w, uint q0, uint q1)
{
uint n, p0, p1;
n = q1-q0;
if(n == 0)
return;
runemove(w->r+q0, w->r+q1, w->nr-q1);
w->nr -= n;
if(q0 < w->q0)
w->q0 -= min(n, w->q0-q0);
if(q0 < w->q1)
w->q1 -= min(n, w->q1-q0);
if(q1 < w->qh)
w->qh -= n;
else if(q0 < w->qh)
w->qh = q0;
if(q1 <= w->org)
w->org -= n;
else if(q0 < w->org+w->f.nchars){
p1 = q1 - w->org;
if(p1 > w->f.nchars)
p1 = w->f.nchars;
if(q0 < w->org){
w->org = q0;
p0 = 0;
}else
p0 = q0 - w->org;
frdelete(&w->f, p0, p1);
wfill(w);
}
}
static Window *clickwin;
static uint clickmsec;
static Window *selectwin;
static uint selectq;
/*
* called from frame library
*/
void
framescroll(Frame *f, int dl)
{
if(f != &selectwin->f)
error("frameselect not right frame");
wframescroll(selectwin, dl);
}
void
wframescroll(Window *w, int dl)
{
uint q0;
if(dl == 0){
wscrsleep(w, 100);
return;
}
if(dl < 0){
q0 = wbacknl(w, w->org, -dl);
if(selectq > w->org+w->f.p0)
wsetselect(w, w->org+w->f.p0, selectq);
else
wsetselect(w, selectq, w->org+w->f.p0);
}else{
if(w->org+w->f.nchars == w->nr)
return;
q0 = w->org+frcharofpt(&w->f, Pt(w->f.r.min.x, w->f.r.min.y+dl*w->f.font->height));
if(selectq >= w->org+w->f.p1)
wsetselect(w, w->org+w->f.p1, selectq);
else
wsetselect(w, selectq, w->org+w->f.p1);
}
wsetorigin(w, q0, TRUE);
}
void
wselect(Window *w)
{
uint q0, q1;
int b, x, y, first;
first = 1;
selectwin = w;
/*
* Double-click immediately if it might make sense.
*/
b = w->mc.m.buttons;
q0 = w->q0;
q1 = w->q1;
selectq = w->org+frcharofpt(&w->f, w->mc.m.xy);
if(clickwin==w && w->mc.m.msec-clickmsec<500)
if(q0==q1 && selectq==w->q0){
wdoubleclick(w, &q0, &q1);
wsetselect(w, q0, q1);
flushimage(display, 1);
x = w->mc.m.xy.x;
y = w->mc.m.xy.y;
/* stay here until something interesting happens */
do
readmouse(&w->mc);
while(w->mc.m.buttons==b && abs(w->mc.m.xy.x-x)<3 && abs(w->mc.m.xy.y-y)<3);
w->mc.m.xy.x = x; /* in case we're calling frselect */
w->mc.m.xy.y = y;
q0 = w->q0; /* may have changed */
q1 = w->q1;
selectq = q0;
}
if(w->mc.m.buttons == b){
w->f.scroll = framescroll;
frselect(&w->f, &w->mc);
/* horrible botch: while asleep, may have lost selection altogether */
if(selectq > w->nr)
selectq = w->org + w->f.p0;
w->f.scroll = nil;
if(selectq < w->org)
q0 = selectq;
else
q0 = w->org + w->f.p0;
if(selectq > w->org+w->f.nchars)
q1 = selectq;
else
q1 = w->org+w->f.p1;
}
if(q0 == q1){
if(q0==w->q0 && clickwin==w && w->mc.m.msec-clickmsec<500){
wdoubleclick(w, &q0, &q1);
clickwin = nil;
}else{
clickwin = w;
clickmsec = w->mc.m.msec;
}
}else
clickwin = nil;
wsetselect(w, q0, q1);
flushimage(display, 1);
while(w->mc.m.buttons){
w->mc.m.msec = 0;
b = w->mc.m.buttons;
if(b & 6){
if(b & 2){
wsnarf(w);
wcut(w);
}else{
if(first){
first = 0;
riogetsnarf();
}
wpaste(w);
}
}
wscrdraw(w);
flushimage(display, 1);
while(w->mc.m.buttons == b)
readmouse(&w->mc);
clickwin = nil;
}
}
void
wsendctlmesg(Window *w, int type, Rectangle r, Image *image)
{
Wctlmesg wcm;
wcm.type = type;
wcm.r = r;
wcm.image = image;
send(w->cctl, &wcm);
}
int
wctlmesg(Window *w, int m, Rectangle r, Image *i)
{
char buf[64];
switch(m){
default:
error("unknown control message");
break;
case Wakeup:
break;
case Moved:
case Reshaped:
if(w->deleted){
freeimage(i);
break;
}
w->screenr = r;
strcpy(buf, w->name);
wresize(w, i, m==Moved);
w->wctlready = 1;
if(Dx(r) > 0){
if(w != input)
wcurrent(w);
}else if(w == input)
wcurrent(nil);
flushimage(display, 1);
break;
case Refresh:
if(w->deleted || Dx(w->screenr)<=0 || !rectclip(&r, w->i->r))
break;
if(!w->mouseopen)
wrefresh(w, r);
flushimage(display, 1);
break;
case Movemouse:
if(sweeping || !ptinrect(r.min, w->i->r))
break;
wmovemouse(w, r.min);
case Rawon:
break;
case Rawoff:
if(w->deleted)
break;
while(w->nraw > 0){
wkeyctl(w, w->raw[0]);
--w->nraw;
runemove(w->raw, w->raw+1, w->nraw);
}
break;
case Holdon:
case Holdoff:
if(w->deleted)
break;
wrepaint(w);
flushimage(display, 1);
break;
case Deleted:
if(w->deleted)
break;
write(w->notefd, "hangup", 6);
wclosewin(w);
break;
case Exited:
frclear(&w->f, TRUE);
close(w->notefd);
chanfree(w->mc.c);
chanfree(w->ck);
chanfree(w->cctl);
chanfree(w->conswrite);
chanfree(w->consread);
chanfree(w->mouseread);
chanfree(w->wctlread);
free(w->raw);
free(w->r);
free(w->dir);
free(w->label);
free(w);
break;
}
return m;
}
/*
* Convert back to physical coordinates
*/
void
wmovemouse(Window *w, Point p)
{
p.x += w->screenr.min.x-w->i->r.min.x;
p.y += w->screenr.min.y-w->i->r.min.y;
moveto(mousectl, p);
}
void
wcurrent(Window *w)
{
Window *oi;
if(wkeyboard!=nil && w==wkeyboard)
return;
oi = input;
input = w;
if(oi!=w && oi!=nil)
wrepaint(oi);
if(w !=nil){
wrepaint(w);
wsetcursor(w, 0);
}
if(w != oi){
if(oi){
oi->wctlready = 1;
wsendctlmesg(oi, Wakeup, ZR, nil);
}
if(w){
w->wctlready = 1;
wsendctlmesg(w, Wakeup, ZR, nil);
}
}
}
void
wsetcursor(Window *w, int force)
{
Cursor *p;
if(w==nil || /*w!=input || */ w->i==nil || Dx(w->screenr)<=0)
p = nil;
else if(wpointto(mouse->xy) == w){
p = w->cursorp;
if(p==nil && w->holding)
p = &whitearrow;
}else
p = nil;
if(!menuing)
riosetcursor(p, force && !menuing);
}
void
riosetcursor(Cursor *p, int force)
{
if(!force && p==lastcursor)
return;
setcursor(mousectl, p);
lastcursor = p;
}
Window*
wtop(Point pt)
{
Window *w;
w = wpointto(pt);
if(w){
if(w->topped == topped)
return nil;
topwindow(w->i);
wcurrent(w);
flushimage(display, 1);
w->topped = ++topped;
}
return w;
}
void
wtopme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted && w->topped!=topped){
topwindow(w->i);
flushimage(display, 1);
w->topped = ++ topped;
}
}
void
wbottomme(Window *w)
{
if(w!=nil && w->i!=nil && !w->deleted){
bottomwindow(w->i);
flushimage(display, 1);
w->topped = 0;
}
}
Window*
wlookid(int id)
{
int i;
for(i=0; i<nwindow; i++)
if(window[i]->id == id)
return window[i];
return nil;
}
void
wclosewin(Window *w)
{
Rectangle r;
int i;
w->deleted = TRUE;
if(w == input){
input = nil;
wsetcursor(w, 0);
}
if(w == wkeyboard)
wkeyboard = nil;
for(i=0; i<nhidden; i++)
if(hidden[i] == w){
--nhidden;
memmove(hidden+i, hidden+i+1, (nhidden-i)*sizeof(hidden[0]));
break;
}
for(i=0; i<nwindow; i++)
if(window[i] == w){
--nwindow;
memmove(window+i, window+i+1, (nwindow-i)*sizeof(Window*));
w->deleted = TRUE;
r = w->i->r;
/* move it off-screen to hide it, in case client is slow in letting it go */
MOVEIT originwindow(w->i, r.min, view->r.max);
freeimage(w->i);
w->i = nil;
return;
}
error("unknown window in closewin");
}
void
wsetpid(Window *w, int pid, int dolabel)
{
char buf[128];
w->pid = pid;
if(dolabel){
sprint(buf, "rc %d", pid);
free(w->label);
w->label = estrdup(buf);
drawsetlabel(w->label);
}
}
static Rune left1[] = {
'{', '[', '(', '<', 0xAB,
0x207d, 0x2329, 0x27e6, 0x27e8, 0x27ea,
0xfe59, 0xfe5b, 0xfe5d, 0xff08, 0xff3b, 0xff5b,
0
};
static Rune right1[] = {
'}', ']', ')', '>', 0xBB,
0x207e, 0x232a, 0x27e7, 0x27e9, 0x27eb,
0xfe5a, 0xfe5c, 0xfe5e, 0xff09, 0xff3d, 0xff5d,
0
};
static Rune left2[] = { '\n', 0 };
static Rune left3[] = { '\'', '"', '`', 0 };
Rune *left[] = {
left1,
left2,
left3,
nil
};
Rune *right[] = {
right1,
left2,
left3,
nil
};
void
wdoubleclick(Window *w, uint *q0, uint *q1)
{
int c, i;
Rune *r, *l, *p;
uint q;
for(i=0; left[i]!=nil; i++){
q = *q0;
l = left[i];
r = right[i];
/* try matching character to left, looking right */
if(q == 0)
c = '\n';
else
c = w->r[q-1];
p = strrune(l, c);
if(p != nil){
if(wclickmatch(w, c, r[p-l], 1, &q))
*q1 = q-(c!='\n');
return;
}
/* try matching character to right, looking left */
if(q == w->nr)
c = '\n';
else
c = w->r[q];
p = strrune(r, c);
if(p != nil){
if(wclickmatch(w, c, l[p-r], -1, &q)){
*q1 = *q0+(*q0<w->nr && c=='\n');
*q0 = q;
if(c!='\n' || q!=0 || w->r[0]=='\n')
(*q0)++;
}
return;
}
}
/* try filling out word to right */
while(*q1<w->nr && isalnum(w->r[*q1]))
(*q1)++;
/* try filling out word to left */
while(*q0>0 && isalnum(w->r[*q0-1]))
(*q0)--;
}
int
wclickmatch(Window *w, int cl, int cr, int dir, uint *q)
{
Rune c;
int nest;
nest = 1;
for(;;){
if(dir > 0){
if(*q == w->nr)
break;
c = w->r[*q];
(*q)++;
}else{
if(*q == 0)
break;
(*q)--;
c = w->r[*q];
}
if(c == cr){
if(--nest==0)
return 1;
}else if(c == cl)
nest++;
}
return cl=='\n' && nest==1;
}
uint
wbacknl(Window *w, uint p, uint n)
{
int i, j;
/* look for start of this line if n==0 */
if(n==0 && p>0 && w->r[p-1]!='\n')
n = 1;
i = n;
while(i-->0 && p>0){
--p; /* it's at a newline now; back over it */
if(p == 0)
break;
/* at 128 chars, call it a line anyway */
for(j=128; --j>0 && p>0; p--)
if(w->r[p-1]=='\n')
break;
}
return p;
}
void
wshow(Window *w, uint q0)
{
int qe;
int nl;
uint q;
qe = w->org+w->f.nchars;
if(w->org<=q0 && (q0<qe || (q0==qe && qe==w->nr)))
wscrdraw(w);
else{
nl = 4*w->f.maxlines/5;
q = wbacknl(w, q0, nl);
/* avoid going backwards if trying to go forwards - long lines! */
if(!(q0>w->org && q<w->org))
wsetorigin(w, q, TRUE);
while(q0 > w->org+w->f.nchars)
wsetorigin(w, w->org+1, FALSE);
}
}
void
wsetorigin(Window *w, uint org, int exact)
{
int i, a, fixup;
Rune *r;
uint n;
if(org>0 && !exact){
/* org is an estimate of the char posn; find a newline */
/* don't try harder than 256 chars */
for(i=0; i<256 && org<w->nr; i++){
if(w->r[org] == '\n'){
org++;
break;
}
org++;
}
}
a = org-w->org;
fixup = 0;
if(a>=0 && a<w->f.nchars){
frdelete(&w->f, 0, a);
fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
}else if(a<0 && -a<w->f.nchars){
n = w->org - org;
r = runemalloc(n);
runemove(r, w->r+org, n);
frinsert(&w->f, r, r+n, 0);
free(r);
}else
frdelete(&w->f, 0, w->f.nchars);
w->org = org;
wfill(w);
wscrdraw(w);
wsetselect(w, w->q0, w->q1);
if(fixup && w->f.p1 > w->f.p0)
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1-1), w->f.p1-1, w->f.p1, 1);
}
void
wsetselect(Window *w, uint q0, uint q1)
{
int p0, p1;
/* w->f.p0 and w->f.p1 are always right; w->q0 and w->q1 may be off */
w->q0 = q0;
w->q1 = q1;
/* compute desired p0,p1 from q0,q1 */
p0 = q0-w->org;
p1 = q1-w->org;
if(p0 < 0)
p0 = 0;
if(p1 < 0)
p1 = 0;
if(p0 > w->f.nchars)
p0 = w->f.nchars;
if(p1 > w->f.nchars)
p1 = w->f.nchars;
if(p0==w->f.p0 && p1==w->f.p1)
return;
/* screen disagrees with desired selection */
if(w->f.p1<=p0 || p1<=w->f.p0 || p0==p1 || w->f.p1==w->f.p0){
/* no overlap or too easy to bother trying */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, w->f.p1, 0);
frdrawsel(&w->f, frptofchar(&w->f, p0), p0, p1, 1);
goto Return;
}
/* overlap; avoid unnecessary painting */
if(p0 < w->f.p0){
/* extend selection backwards */
frdrawsel(&w->f, frptofchar(&w->f, p0), p0, w->f.p0, 1);
}else if(p0 > w->f.p0){
/* trim first part of selection */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p0), w->f.p0, p0, 0);
}
if(p1 > w->f.p1){
/* extend selection forwards */
frdrawsel(&w->f, frptofchar(&w->f, w->f.p1), w->f.p1, p1, 1);
}else if(p1 < w->f.p1){
/* trim last part of selection */
frdrawsel(&w->f, frptofchar(&w->f, p1), p1, w->f.p1, 0);
}
Return:
w->f.p0 = p0;
w->f.p1 = p1;
}
uint
winsert(Window *w, Rune *r, int n, uint q0)
{
uint m;
if(n == 0)
return q0;
if(w->nr+n>HiWater && q0>=w->org && q0>=w->qh){
m = min(HiWater-LoWater, min(w->org, w->qh));
w->org -= m;
w->qh -= m;
if(w->q0 > m)
w->q0 -= m;
else
w->q0 = 0;
if(w->q1 > m)
w->q1 -= m;
else
w->q1 = 0;
w->nr -= m;
runemove(w->r, w->r+m, w->nr);
q0 -= m;
}
if(w->nr+n > w->maxr){
/*
* Minimize realloc breakage:
* Allocate at least MinWater
* Double allocation size each time
* But don't go much above HiWater
*/
m = max(min(2*(w->nr+n), HiWater), w->nr+n)+MinWater;
if(m > HiWater)
m = max(HiWater+MinWater, w->nr+n);
if(m > w->maxr){
w->r = runerealloc(w->r, m);
w->maxr = m;
}
}
runemove(w->r+q0+n, w->r+q0, w->nr-q0);
runemove(w->r+q0, r, n);
w->nr += n;
/* if output touches, advance selection, not qh; works best for keyboard and output */
if(q0 <= w->q1)
w->q1 += n;
if(q0 <= w->q0)
w->q0 += n;
if(q0 < w->qh)
w->qh += n;
if(q0 < w->org)
w->org += n;
else if(q0 <= w->org+w->f.nchars)
frinsert(&w->f, r, r+n, q0-w->org);
return q0;
}
void
wfill(Window *w)
{
Rune *rp;
int i, n, m, nl;
if(w->f.lastlinefull)
return;
rp = malloc(messagesize);
do{
n = w->nr-(w->org+w->f.nchars);
if(n == 0)
break;
if(n > 2000) /* educated guess at reasonable amount */
n = 2000;
runemove(rp, w->r+(w->org+w->f.nchars), n);
/*
* it's expensive to frinsert more than we need, so
* count newlines.
*/
nl = w->f.maxlines-w->f.nlines;
m = 0;
for(i=0; i<n; ){
if(rp[i++] == '\n'){
m++;
if(m >= nl)
break;
}
}
frinsert(&w->f, rp, rp+i, w->f.nchars);
}while(w->f.lastlinefull == FALSE);
free(rp);
}
char*
wcontents(Window *w, int *ip)
{
return runetobyte(w->r, w->nr, ip);
}