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
1655 lines
32 KiB
C
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);
|
|
}
|