int
textbswidth(Text *t, Rune c)
{
uint q, eq;
Rune r;
int skipping;
/* there is known to be at least one character to erase */
if(c == Kbs) /* ^H: erase character */
return 1;
q = t->q0;
skipping = TRUE;
while(q > 0){
r = t->rs.r[q-1];
if(r == '\n'){ /* eat at most one more character */
if(q == t->q0) /* eat the newline */
--q;
break;
}
if(c == Ketb){
eq = isalnum(r);
if(eq && skipping) /* found one; stop skipping */
skipping = FALSE;
else if(!eq && !skipping)
break;
}
--q;
}
return t->q0-q;
}
void
texttype(Text *t, Rune r)
{
uint q0, q1;
int nb, n;
int nr;
Rune *rp;
nr = 1;
rp = &r;
switch(r){
case Kleft:
if(t->q0 > 0)
textshow(t, t->q0-1, t->q0-1, TRUE);
return;
case Kright:
if(t->q1 < t->rs.nr)
textshow(t, t->q1+1, t->q1+1, TRUE);
return;
case Kdown:
n = t->maxlines/3;
goto case_Down;
case Kscrollonedown:
n = mousescrollsize(t->maxlines);
if(n <= 0)
n = 1;
goto case_Down;
case Kpgdown:
n = 2*t->maxlines/3;
case_Down:
q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
textsetorigin(t, q0, TRUE);
return;
case Kup:
n = t->maxlines/3;
goto case_Up;
case Kscrolloneup:
n = mousescrollsize(t->maxlines);
goto case_Up;
case Kpgup:
n = 2*t->maxlines/3;
case_Up:
q0 = textbacknl(t, t->org, n);
textsetorigin(t, q0, TRUE);
return;
case Khome:
textshow(t, 0, 0, FALSE);
return;
case Kend:
textshow(t, t->rs.nr, t->rs.nr, FALSE);
return;
case Ksoh: /* ^A: beginning of line */
/* go to where ^U would erase, if not already at BOL */
nb = 0;
if(t->q0>0 && t->rs.r[t->q0-1]!='\n')
nb = textbswidth(t, Knack);
textshow(t, t->q0-nb, t->q0-nb, TRUE);
return;
case Kenq: /* ^E: end of line */
q0 = t->q0;
while(q0<t->rs.nr && t->rs.r[q0]!='\n')
q0++;
textshow(t, q0, q0, TRUE);
return;
}
if(t->q1 > t->q0)
cut(t, t, TRUE, TRUE, nil, 0);
textshow(t, t->q0, t->q0, TRUE);
switch(r){
case Kbs: /* ^H: erase character */
case Knack: /* ^U: erase line */
case Ketb: /* ^W: erase word */
if(t->q0 == 0) /* nothing to erase */
return;
nb = textbswidth(t, r);
q1 = t->q0;
q0 = q1-nb;
/* if selection is at beginning of window, avoid deleting invisible text */
if(q0 < t->org){
q0 = t->org;
nb = q1-q0;
}
if(nb > 0){
textdelete(t, q0, q0+nb);
textsetselect(t, q0, q0);
}
return;
}
/* otherwise ordinary character; just insert */
textinsert(t, t->q0, &r, 1);
if(rp != &r)
free(rp);
textsetselect(t, t->q0+nr, t->q0+nr);
if(t->what == Textarea)
textscrdraw(t);
}
static Text *clicktext;
static uint clickmsec;
static Text *selecttext;
static uint selectq;
/*
* called from frame library
*/
void
framescroll(Frame *f, int dl)
{
if(f != &selecttext->Frame)
error("frameselect not right frame");
textframescroll(selecttext, dl);
}
/* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
t->q0 = q0;
t->q1 = q1;
/* compute desired p0,p1 from q0,q1 */
p0 = q0-t->org;
p1 = q1-t->org;
if(p0 < 0)
p0 = 0;
if(p1 < 0)
p1 = 0;
if(p0 > t->nchars)
p0 = t->nchars;
if(p1 > t->nchars)
p1 = t->nchars;
if(p0==t->p0 && p1==t->p1)
return;
/* screen disagrees with desired selection */
if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
/* no overlap or too easy to bother trying */
frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
goto Return;
}
/* overlap; avoid unnecessary painting */
if(p0 < t->p0){
/* extend selection backwards */
frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
}else if(p0 > t->p0){
/* trim first part of selection */
frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
}
if(p1 > t->p1){
/* extend selection forwards */
frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
}else if(p1 < t->p1){
/* trim last part of selection */
frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
}
Return:
t->p0 = p0;
t->p1 = p1;
}
/*
* Release the button in less than DELAY ms and it's considered a null selection
* if the mouse hardly moved, regardless of whether it crossed a char boundary.
*/
enum {
DELAY = 2,
MINMOVE = 4,
};
uint
xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
{
uint p0, p1, q, tmp;
ulong msec;
Point mp, pt0, pt1, qt;
int reg, b;
uint
textbacknl(Text *t, uint p, uint n)
{
int i, j;
/* look for start of this line if n==0 */
if(n==0 && p>0 && t->rs.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(t->rs.r[p-1]=='\n')
break;
}
return p;
}
void
textsetorigin(Text *t, 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<t->rs.nr; i++){
if(t->rs.r[org] == '\n'){
org++;
break;
}
org++;
}
}
a = org-t->org;
fixup = 0;
if(a>=0 && a<t->nchars){
frdelete(t, 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<t->nchars){
n = t->org - org;
r = runemalloc(n);
runemove(r, t->rs.r+org, n);
frinsert(t, r, r+n, 0);
free(r);
}else
frdelete(t, 0, t->nchars);
t->org = org;
textfill(t);
textscrdraw(t);
textsetselect(t, t->q0, t->q1);
if(fixup && t->p1 > t->p0)
frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
}