ref: db0eab9ca04b896e5760081efd854c57f65f36c3
dir: /libtk/tindx.c/
#include "lib9.h"
#include "draw.h"
#include "tk.h"
#include "textw.h"
#define istring u.string
#define iwin u.win
#define imark u.mark
#define iline u.line
/* debugging */
extern int tktdbg;
extern void tktprinttext(TkText*);
extern void tktprintindex(TkTindex*);
extern void tktprintitem(TkTitem*);
extern void tktprintline(TkTline*);
char*
tktindparse(Tk *tk, char **pspec, TkTindex *ans)
{
int m, n, done, neg, modstart;
char *s, *mod;
TkTline *lend;
TkText *tkt;
char *buf;
buf = mallocz(Tkmaxitem, 0);
if(buf == nil)
return TkNomem;
tkt = TKobj(TkText, tk);
lend = &tkt->end;
*pspec = tkword(tk->env->top, *pspec, buf, buf+Tkmaxitem, nil);
modstart = 0;
for(mod = buf; *mod != '\0'; mod++)
if(*mod == ' ' || *mod == '-' || *mod == '+') {
modstart = *mod;
*mod = '\0';
break;
}
/*
* XXX there's a problem here - if either coordinate is negative
* which shouldn't be precluded, then the above scanning code
* will break up the coordinate pair, so @-23,45 for example
* yields a bad index, when it should probably return the index
* of the character at the start of the line containing y=45.
* i've seen this cause wm/sh to crash.
*/
if(strcmp(buf, "end") == 0)
tktendind(tkt, ans);
else
if(*buf == '@') {
/* by coordinates */
s = strchr(buf, ',');
if(s == nil) {
free(buf);
return TkBadix;
}
*s = '\0';
m = atoi(buf+1);
n = atoi(s+1);
tktxyind(tk, m, n, ans);
}
else
if(*buf >= '0' && *buf <= '9') {
/* line.char */
s = strchr(buf, '.');
if(s == nil) {
free(buf);
return TkBadix;
}
*s = '\0';
m = atoi(buf);
n = atoi(s+1);
if(m < 1)
m = 1;
tktstartind(tkt, ans);
while(--m > 0 && ans->line->next != lend)
tktadjustind(tkt, TkTbyline, ans);
while(n-- > 0 && ans->item->kind != TkTnewline)
tktadjustind(tkt, TkTbychar, ans);
}
else
if(*buf == '.') {
/* window */
tktstartind(tkt, ans);
while(ans->line != lend) {
if(ans->item->kind == TkTwin &&
ans->item->iwin->sub != nil &&
ans->item->iwin->sub->name != nil &&
strcmp(ans->item->iwin->sub->name->name, buf) == 0)
break;
if(!tktadjustind(tkt, TkTbyitem, ans))
ans->line = lend;
}
if(ans->line == lend) {
free(buf);
return TkBadix;
}
}
else {
s = strchr(buf, '.');
if(s == nil) {
if(tktmarkind(tk, buf, ans) == 0) {
free(buf);
return TkBadix;
}
}
else {
/* tag.first or tag.last */
*s = '\0';
if(strcmp(s+1, "first") == 0) {
if(tkttagind(tk, buf, 1, ans) == 0) {
free(buf);
return TkBadix;
}
}
else
if(strcmp(s+1, "last") == 0) {
if(tkttagind(tk, buf, 0, ans) == 0) {
free(buf);
return TkBadix;
}
}
else {
free(buf);
return TkBadix;
}
}
}
if(modstart == 0) {
free(buf);
return nil;
}
*mod = modstart;
while(*mod == ' ')
mod++;
while(*mod != '\0') {
done = 0;
switch(*mod) {
case '+':
case '-':
neg = (*mod == '-');
mod++;
while(*mod == ' ')
mod++;
n = strtol(mod, &mod, 10);
while(*mod == ' ')
mod++;
while(n-- > 0) {
if(*mod == 'c')
tktadjustind(tkt, neg? TkTbycharback : TkTbychar, ans);
else
if(*mod == 'l')
tktadjustind(tkt, neg? TkTbylineback : TkTbyline, ans);
else
done = 1;
}
break;
case 'l':
if(strncmp(mod, "lines", 5) == 0)
tktadjustind(tkt, TkTbylinestart, ans);
else
if(strncmp(mod, "linee", 5) == 0)
tktadjustind(tkt, TkTbylineend, ans);
else
done = 1;
break;
case 'w':
if(strncmp(mod, "words", 5) == 0)
tktadjustind(tkt, TkTbywordstart, ans);
else
if(strncmp(mod, "worde", 5) == 0)
tktadjustind(tkt, TkTbywordend, ans);
else
done = 1;
break;
default:
done = 1;
}
if(done)
break;
while(tkiswordchar(*mod))
mod++;
while(*mod == ' ')
mod++;
}
free(buf);
return nil;
}
int
tktisbreak(int c)
{
/* unicode rules suggest / as well but that would split dates, and URLs might as well char. wrap */
return c == ' ' || c == '\t' || c == '\n' || c == '-' || c == ',';
/* previously included . but would probably need more then to handle ." */
}
/*
* Adjust the index p by units (one of TkTbyitem, etc.).
* The TkTbychar units mean that the final point should rest on a
* "character" (in text widget index space; i.e., a newline, a rune,
* and an embedded window are each 1 character, but marks and contlines are not).
*
* Indexes may not point in the tkt->start or tkt->end lines (which have
* no items); tktadjustind sticks at the beginning or end of the buffer.
*
* Return 1 if the index changes at all, 0 otherwise.
*/
int
tktadjustind(TkText *tkt, int units, TkTindex *p)
{
int n, opos, count, c;
TkTitem *i, *it, *oit;
TkTindex q;
oit = p->item;
opos = p->pos;
count = 1;
switch(units) {
case TkTbyitemback:
it = p->item;
p->item = p->line->items;
p->pos = 0;
if(it == p->item) {
if(p->line->prev != &tkt->start) {
p->line = p->line->prev;
p->item = tktlastitem(p->line->items);
}
}
else {
while(p->item->next != it) {
p->item = p->item->next;
if(tktdbg && p->item == nil) {
print("tktadjustind: botch 1\n");
break;
}
}
}
break;
case TkTbyitem:
p->pos = 0;
i = p->item->next;
if(i == nil) {
if(p->line->next != &tkt->end) {
p->line = p->line->next;
p->item = p->line->items;
}
}
else
p->item = i;
break;
case TkTbytlineback:
if(p->line->prev != &tkt->start)
p->line = p->line->prev;
p->item = p->line->items;
p->pos = 0;
break;
case TkTbytline:
if(p->line->next != &tkt->end)
p->line = p->line->next;
p->item = p->line->items;
p->pos = 0;
break;
case TkTbycharstart:
count = 0;
case TkTbychar:
while(count > 0) {
i = p->item;
n = tktposcount(i) - p->pos;
if(count >= n) {
if(tktadjustind(tkt, TkTbyitem, p))
count -= n;
else
break;
}
else {
p->pos += count;
break;
}
}
while(p->item->kind == TkTmark || p->item->kind == TkTcontline)
if(!tktadjustind(tkt, TkTbyitem, p))
break;
break;
case TkTbycharback:
count = -1;
while(count < 0) {
if(p->pos + count >= 0) {
p->pos += count;
count = 0;
}
else {
count += p->pos;
if(!tktadjustind(tkt, TkTbyitemback, p))
break;
n = tktposcount(p->item);
p->pos = n;
}
}
break;
case TkTbylineback:
count = -1;
/* fall through */
case TkTbyline:
n = tktlinepos(tkt, p);
while(count > 0) {
if(p->line->next == &tkt->end) {
count = 0;
break;
}
if(p->line->flags&TkTlast)
count--;
p->line = p->line->next;
}
while(count < 0 && p->line->prev != &tkt->start) {
if(p->line->flags&TkTfirst)
count++;
p->line = p->line->prev;
}
tktadjustind(tkt, TkTbylinestart, p);
while(n > 0) {
if(p->item->kind == TkTnewline)
break;
if(!tktadjustind(tkt, TkTbychar, p))
break;
n--;
}
break;
case TkTbylinestart:
/* note: can call this with only p->line set correctly in *p */
while(!(p->line->flags&TkTfirst))
p->line = p->line->prev;
p->item = p->line->items;
p->pos = 0;
break;
case TkTbylineend:
while(p->item->kind != TkTnewline)
if(!tktadjustind(tkt, TkTbychar, p))
break;
break;
case TkTbywordstart:
tktadjustind(tkt, TkTbycharstart, p);
q = *p;
c = tktindrune(p);
while(tkiswordchar(c)) {
q = *p;
if(!tktadjustind(tkt, TkTbycharback, p))
break;
c = tktindrune(p);
}
*p = q;
break;
case TkTbywordend:
tktadjustind(tkt, TkTbycharstart, p);
if(p->item->kind == TkTascii || p->item->kind == TkTrune) {
c = tktindrune(p);
if(tkiswordchar(c)) {
do {
if(!tktadjustind(tkt, TkTbychar, p))
break;
c = tktindrune(p);
} while(tkiswordchar(c));
}
else
tktadjustind(tkt, TkTbychar, p);
}
else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end))
tktadjustind(tkt, TkTbychar, p);
break;
case TkTbywrapstart:
tktadjustind(tkt, TkTbycharstart, p);
q = *p;
c = tktindrune(p);
while(!tktisbreak(c)) {
q = *p;
if(!tktadjustind(tkt, TkTbycharback, p))
break;
c = tktindrune(p);
}
*p = q;
break;
case TkTbywrapend:
tktadjustind(tkt, TkTbycharstart, p);
if(p->item->kind == TkTascii || p->item->kind == TkTrune) {
c = tktindrune(p);
if(!tktisbreak(c)) {
do {
if(!tktadjustind(tkt, TkTbychar, p))
break;
c = tktindrune(p);
} while(!tktisbreak(c) && (p->item->kind == TkTascii || p->item->kind == TkTrune));
while(tktisbreak(c) && tktadjustind(tkt, TkTbychar, p))
c = tktindrune(p); /* could limit it */
}
else
tktadjustind(tkt, TkTbychar, p);
}
else if(!(p->item->kind == TkTnewline && p->line->next == &tkt->end))
tktadjustind(tkt, TkTbychar, p);
break;
}
return (p->item != oit || p->pos != opos);
}
/* return 1 if advancing i1 by item eventually hits i2 */
int
tktindbefore(TkTindex *i1, TkTindex *i2)
{
int ans;
TkTitem *i;
TkTline *l1, *l2;
ans = 0;
l1 = i1->line;
l2 = i2->line;
if(l1 == l2) {
if(i1->item == i2->item)
ans = (i1->pos < i2->pos);
else {
for(i = i1->item; i != nil; i = i->next)
if(i->next == i2->item) {
ans = 1;
break;
}
}
}
else {
if(l1->orig.y < l2->orig.y)
ans = 1;
else
if(l1->orig.y == l2->orig.y) {
for(; l1 != nil; l1 = l1->next) {
if(l1->next == l2) {
ans = 1;
break;
}
if(l1->orig.y > l2->orig.y)
break;
}
}
}
return ans;
}
/*
* This comparison only cares which characters the indices are before.
* So two marks should be called "equal" (and not "less" or "greater")
* if they are adjacent.
*/
int
tktindcompare(TkText *tkt, TkTindex *i1, int op, TkTindex *i2)
{
int eq, ans;
TkTindex x1, x2;
x1 = *i1;
x2 = *i2;
/* skip over any marks, contlines, to see if on same character */
tktadjustind(tkt, TkTbycharstart, &x1);
tktadjustind(tkt, TkTbycharstart, &x2);
eq = (x1.item == x2.item && x1.pos == x2.pos);
switch(op) {
case TkEq:
ans = eq;
break;
case TkNeq:
ans = !eq;
break;
case TkLte:
ans = eq || tktindbefore(i1, i2);
break;
case TkLt:
ans = !eq && tktindbefore(i1, i2);
break;
case TkGte:
ans = eq || tktindbefore(i2, i1);
break;
case TkGt:
ans = !eq && tktindbefore(i2, i1);
break;
default:
ans = 0; /* not reached */
break;
};
return ans;
}
void
tktstartind(TkText *tkt, TkTindex *ans)
{
ans->line = tkt->start.next;
ans->item = ans->line->items;
ans->pos = 0;
}
void
tktendind(TkText *tkt, TkTindex *ans)
{
ans->line = tkt->end.prev;
ans->item = tktlastitem(ans->line->items);
ans->pos = 0;
}
void
tktitemind(TkTitem *it, TkTindex *ans)
{
ans->item = it;
ans->line = tktitemline(it);
ans->pos = 0;
}
/*
* Fill ans with the item that (x,y) (in V space) is over.
* Return 0 if it is over the first half of the width,
* and 1 if it is over the second half.
*/
int
tktxyind(Tk *tk, int x, int y, TkTindex *ans)
{
int n, w, secondhalf, k;
Point p, q;
TkTitem *i;
TkText *tkt;
tkt = TKobj(TkText, tk);
tktstartind(tkt, ans);
secondhalf = 0;
/* (x,y), p, q in V space */
p = subpt(ans->line->orig, tkt->deltatv);
q = subpt(ans->line->next->orig, tkt->deltatv);
while(ans->line->next != &tkt->end) {
if(q.y > y)
break;
tktadjustind(tkt, TkTbytline, ans);
p = q;
q = subpt(ans->line->next->orig, tkt->deltatv);
}
if (ans->line->next == &tkt->end) {
Point ep = subpt(tkt->end.orig, tkt->deltatv);
if (ep.y < y)
x = 1000000;
}
while(ans->item->next != nil) {
i = ans->item;
w = i->width;
if(p.x+w > x) {
n = tktposcount(i);
if(n > 1) {
for(k = 0; k < n; k++) {
/* probably wrong w.r.t tag tabs */
w = tktdispwidth(tk, nil, i, nil, p.x, k, 1);
if(p.x+w > x) {
ans->pos = k;
break;
}
p.x += w;
}
}
secondhalf = (p.x + w/2 <= x);
break;
}
p.x += w;
if(!tktadjustind(tkt, TkTbyitem, ans))
break;
}
tktadjustind(tkt, TkTbycharstart, ans);
return secondhalf;
}