diff options
Diffstat (limited to 'libtk/tindx.c')
| -rw-r--r-- | libtk/tindx.c | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/libtk/tindx.c b/libtk/tindx.c new file mode 100644 index 00000000..8eeffc69 --- /dev/null +++ b/libtk/tindx.c @@ -0,0 +1,609 @@ +#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: + SET(ans); + }; + + 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; +} + |
