master
text.c
ohmu
·
2026-09-13
1#include <u.h>
2#include <libc.h>
3#include <draw.h>
4#include <thread.h>
5#include <cursor.h>
6#include <mouse.h>
7#include <keyboard.h>
8#include <frame.h>
9#include <fcall.h>
10#include <plumb.h>
11#include <libsec.h>
12#include <complete.h>
13#include "dat.h"
14#include "fns.h"
15#include "parser.h"
16
17Image *tagcols[NCOL];
18Image *textcols[NCOL];
19static Rune Ldot[] = { '.', 0 };
20
21enum{
22 TABDIR = 3 /* width of tabs in directory windows */
23};
24
25void
26textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
27{
28 t->file = f;
29 t->all = r;
30 t->scrollr = r;
31 t->scrollr.max.x = r.min.x+Scrollwid;
32 t->lastsr = nullrect;
33 r.min.x += Scrollwid+Scrollgap;
34 t->eq0 = ~0;
35 t->ncache = 0;
36 t->reffont = rf;
37 t->tabstop = maxtab;
38 memmove(t->fr.cols, cols, sizeof t->fr.cols);
39 textredraw(t, r, rf->f, screen, -1);
40}
41
42void
43textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
44{
45 int maxt;
46 Rectangle rr;
47
48 frinit(&t->fr, r, f, b, t->fr.cols);
49 rr = t->fr.r;
50 rr.min.x -= Scrollwid+Scrollgap; /* back fill to scroll bar */
51 if(!t->fr.noredraw)
52 draw(t->fr.b, rr, t->fr.cols[BACK], nil, ZP);
53 /* use no wider than 3-space tabs in a directory */
54 maxt = maxtab;
55 if(t->what == Body){
56 if(t->w->isdir)
57 maxt = min(TABDIR, maxtab);
58 else
59 maxt = t->tabstop;
60 }
61 t->fr.maxtab = maxt*stringwidth(f, "0");
62 if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
63 if(t->fr.maxlines > 0){
64 textreset(t);
65 textcolumnate(t, t->w->dlp, t->w->ndl);
66 textshow(t, 0, 0, 1);
67 }
68 }else{
69 textfill(t);
70 textsetselect(t, t->q0, t->q1);
71 }
72}
73
74int
75textresize(Text *t, Rectangle r, int keepextra)
76{
77 int odx;
78
79 if(Dy(r) <= 0)
80 r.max.y = r.min.y;
81 else if(!keepextra)
82 r.max.y -= Dy(r)%t->fr.font->height;
83 odx = Dx(t->all);
84 t->all = r;
85 t->scrollr = r;
86 t->scrollr.max.x = r.min.x+Scrollwid;
87 t->lastsr = nullrect;
88 r.min.x += Scrollwid+Scrollgap;
89 frclear(&t->fr, 0);
90 textredraw(t, r, t->fr.font, t->fr.b, odx);
91 if(keepextra && t->fr.r.max.y < t->all.max.y && !t->fr.noredraw){
92 /* draw background in bottom fringe of window */
93 r.min.x -= Scrollgap;
94 r.min.y = t->fr.r.max.y;
95 r.max.y = t->all.max.y;
96 draw(screen, r, t->fr.cols[BACK], nil, ZP);
97 }
98 return t->all.max.y;
99}
100
101void
102textclose(Text *t)
103{
104 free(t->cache);
105 frclear(&t->fr, 1);
106 filedeltext(t->file, t);
107 t->file = nil;
108 rfclose(t->reffont);
109 if(argtext == t)
110 argtext = nil;
111 if(typetext == t)
112 typetext = nil;
113 if(seltext == t)
114 seltext = nil;
115 if(mousetext == t)
116 mousetext = nil;
117 if(barttext == t)
118 barttext = nil;
119}
120
121int
122dircmp(const void *a, const void *b)
123{
124 Dirlist *da, *db;
125 int i, n;
126
127 da = *(Dirlist**)a;
128 db = *(Dirlist**)b;
129 n = min(da->nr, db->nr);
130 i = memcmp(da->r, db->r, n*sizeof(Rune));
131 if(i)
132 return i;
133 return da->nr - db->nr;
134}
135
136void
137textcolumnate(Text *t, Dirlist **dlp, int ndl)
138{
139 int i, j, w, colw, mint, maxt, ncol, nrow;
140 Dirlist *dl;
141 uint q1;
142 static Rune Lnl[] = { '\n', 0 };
143 static Rune Ltab[] = { '\t', 0 };
144
145 if(t->file->ntext > 1)
146 return;
147 mint = stringwidth(t->fr.font, "0");
148 /* go for narrower tabs if set more than 3 wide */
149 t->fr.maxtab = min(maxtab, TABDIR)*mint;
150 maxt = t->fr.maxtab;
151 colw = 0;
152 for(i=0; i<ndl; i++){
153 dl = dlp[i];
154 w = dl->wid;
155 if(maxt-w%maxt < mint || w%maxt==0)
156 w += mint;
157 if(w % maxt)
158 w += maxt-(w%maxt);
159 if(w > colw)
160 colw = w;
161 }
162 if(colw == 0)
163 ncol = 1;
164 else
165 ncol = max(1, Dx(t->fr.r)/colw);
166 nrow = (ndl+ncol-1)/ncol;
167
168 q1 = 0;
169 for(i=0; i<nrow; i++){
170 for(j=i; j<ndl; j+=nrow){
171 dl = dlp[j];
172 fileinsert(t->file, q1, dl->r, dl->nr);
173 q1 += dl->nr;
174 if(j+nrow >= ndl)
175 break;
176 w = dl->wid;
177 if(maxt-w%maxt < mint){
178 fileinsert(t->file, q1, Ltab, 1);
179 q1++;
180 w += mint;
181 }
182 do{
183 fileinsert(t->file, q1, Ltab, 1);
184 q1++;
185 w += maxt-(w%maxt);
186 }while(w < colw);
187 }
188 fileinsert(t->file, q1, Lnl, 1);
189 q1++;
190 }
191}
192
193int
194textload(Text *t, uint q0, char *file, int setqid)
195{
196 Rune *rp;
197 Dirlist *dl, **dlp;
198 int fd, i, j, n, ndl, nulls;
199 uint q, q1;
200 Dir *d, *dbuf;
201 char *tmp;
202 Text *u;
203 DigestState *h;
204
205 if(t->ncache!=0 || t->file->b.nc || t->w==nil || t!=&t->w->body)
206 error("text.load");
207 if(t->w->isdir && t->file->nname==0){
208 warning(nil, "empty directory name");
209 return -1;
210 }
211 if(ismtpt(file)){
212 warning(nil, "will not open self mount point %s\n", file);
213 return -1;
214 }
215 fd = open(file, OREAD);
216 if(fd < 0){
217 warning(nil, "can't open %s: %r\n", file);
218 return -1;
219 }
220 d = dirfstat(fd);
221 if(d == nil){
222 warning(nil, "can't fstat %s: %r\n", file);
223 goto Rescue;
224 }
225 nulls = FALSE;
226 h = nil;
227 if(d->qid.type & QTDIR){
228 /* this is checked in get() but it's possible the file changed underfoot */
229 if(t->file->ntext > 1){
230 warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
231 goto Rescue;
232 }
233 t->w->isdir = TRUE;
234 t->w->filemenu = FALSE;
235 if(t->file->nname > 0 && t->file->name[t->file->nname-1] != '/'){
236 rp = runemalloc(t->file->nname+1);
237 runemove(rp, t->file->name, t->file->nname);
238 rp[t->file->nname] = '/';
239 winsetname(t->w, rp, t->file->nname+1);
240 free(rp);
241 }
242 dlp = nil;
243 ndl = 0;
244 dbuf = nil;
245 while((n=dirread(fd, &dbuf)) > 0){
246 for(i=0; i<n; i++){
247 if (!showdotfiles) {
248 if (dbuf[i].name[0] == '.') {
249 continue;
250 }
251 }
252 dl = emalloc(sizeof(Dirlist));
253 j = strlen(dbuf[i].name);
254 tmp = emalloc(j+1+1);
255 memmove(tmp, dbuf[i].name, j);
256 if(dbuf[i].qid.type & QTDIR)
257 tmp[j++] = '/';
258 tmp[j] = '\0';
259 dl->r = bytetorune(tmp, &dl->nr);
260 dl->wid = stringwidth(t->fr.font, tmp);
261 free(tmp);
262 ndl++;
263 dlp = realloc(dlp, ndl*sizeof(Dirlist*));
264 dlp[ndl-1] = dl;
265 }
266 free(dbuf);
267 }
268 qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
269 t->w->dlp = dlp;
270 t->w->ndl = ndl;
271 textcolumnate(t, dlp, ndl);
272 q1 = t->file->b.nc;
273 }else{
274 t->w->isdir = FALSE;
275 t->w->filemenu = TRUE;
276 if(q0 == 0)
277 h = sha1(nil, 0, nil, nil);
278 q1 = q0 + fileload(t->file, q0, fd, &nulls, h);
279 }
280 if(setqid){
281 if(h != nil) {
282 sha1(nil, 0, t->file->sha1, h);
283 h = nil;
284 } else {
285 memset(t->file->sha1, 0, sizeof t->file->sha1);
286 }
287 t->file->dev = d->dev;
288 t->file->mtime = d->mtime;
289 t->file->qidpath = d->qid.path;
290 }
291 close(fd);
292 rp = fbufalloc();
293 for(q=q0; q<q1; q+=n){
294 n = q1-q;
295 if(n > RBUFSIZE)
296 n = RBUFSIZE;
297 bufread(&t->file->b, q, rp, n);
298 if(q < t->org)
299 t->org += n;
300 else if(q <= t->org+t->fr.nchars)
301 frinsert(&t->fr, rp, rp+n, q-t->org);
302 if(t->fr.lastlinefull)
303 break;
304 }
305 fbuffree(rp);
306 for(i=0; i<t->file->ntext; i++){
307 u = t->file->text[i];
308 if(u != t){
309 if(u->org > u->file->b.nc) /* will be 0 because of reset(), but safety first */
310 u->org = 0;
311 textresize(u, u->all, TRUE);
312 textbacknl(u, u->org, 0); /* go to beginning of line */
313 }
314 textsetselect(u, q0, q0);
315 }
316 if(nulls)
317 warning(nil, "%s: NUL bytes elided\n", file);
318 free(d);
319 return q1-q0;
320
321 Rescue:
322 close(fd);
323 return -1;
324}
325
326uint
327textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
328{
329 Rune *bp, *tp, *up;
330 int i, initial;
331
332 if(t->what == Tag){ /* can't happen but safety first: mustn't backspace over file name */
333 Err:
334 textinsert(t, q0, r, n, tofile);
335 *nrp = n;
336 return q0;
337 }
338 bp = r;
339 for(i=0; i<n; i++)
340 if(*bp++ == '\b'){
341 --bp;
342 initial = 0;
343 tp = runemalloc(n);
344 runemove(tp, r, i);
345 up = tp+i;
346 for(; i<n; i++){
347 *up = *bp++;
348 if(*up == '\b')
349 if(up == tp)
350 initial++;
351 else
352 --up;
353 else
354 up++;
355 }
356 if(initial){
357 if(initial > q0)
358 initial = q0;
359 q0 -= initial;
360 textdelete(t, q0, q0+initial, tofile);
361 }
362 n = up-tp;
363 textinsert(t, q0, tp, n, tofile);
364 free(tp);
365 *nrp = n;
366 return q0;
367 }
368 goto Err;
369}
370
371void
372textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
373{
374 int c, i;
375 Text *u;
376
377 if(tofile && t->ncache != 0)
378 error("text.insert");
379 if(n == 0)
380 return;
381 if(tofile){
382 fileinsert(t->file, q0, r, n);
383 if(t->what == Body){
384 t->w->dirty = TRUE;
385 t->w->utflastqid = -1;
386 }
387 if(t->file->ntext > 1)
388 for(i=0; i<t->file->ntext; i++){
389 u = t->file->text[i];
390 if(u != t){
391 u->w->dirty = TRUE; /* always a body */
392 textinsert(u, q0, r, n, FALSE);
393 textsetselect(u, u->q0, u->q1);
394 textscrdraw(u);
395 }
396 }
397
398 }
399 if(q0 < t->iq1)
400 t->iq1 += n;
401 if(q0 < t->q1)
402 t->q1 += n;
403 if(q0 < t->q0)
404 t->q0 += n;
405 if(q0 < t->org)
406 t->org += n;
407 else if(q0 <= t->org+t->fr.nchars)
408 frinsert(&t->fr, r, r+n, q0-t->org);
409 if(t->w){
410 c = 'i';
411 if(t->what == Body)
412 c = 'I';
413 if(n <= EVENTSIZE)
414 winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
415 else
416 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
417 }
418}
419
420void
421typecommit(Text *t)
422{
423 if(t->w != nil)
424 wincommit(t->w, t);
425 else
426 textcommit(t, TRUE);
427}
428
429void
430textfill(Text *t)
431{
432 Rune *rp;
433 int i, n, m, nl;
434
435 if(t->fr.lastlinefull || t->nofill)
436 return;
437 if(t->ncache > 0)
438 typecommit(t);
439 rp = fbufalloc();
440 do{
441 n = t->file->b.nc-(t->org+t->fr.nchars);
442 if(n == 0)
443 break;
444 if(n > 2000) /* educated guess at reasonable amount */
445 n = 2000;
446 bufread(&t->file->b, t->org+t->fr.nchars, rp, n);
447 /*
448 * it's expensive to frinsert more than we need, so
449 * count newlines.
450 */
451 nl = t->fr.maxlines-t->fr.nlines;
452 m = 0;
453 for(i=0; i<n; ){
454 if(rp[i++] == '\n'){
455 m++;
456 if(m >= nl)
457 break;
458 }
459 }
460 frinsert(&t->fr, rp, rp+i, t->fr.nchars);
461 }while(t->fr.lastlinefull == FALSE);
462 fbuffree(rp);
463}
464
465void
466textdelete(Text *t, uint q0, uint q1, int tofile)
467{
468 uint n, p0, p1;
469 int i, c;
470 Text *u;
471
472 if(tofile && t->ncache != 0)
473 error("text.delete");
474 n = q1-q0;
475 if(n == 0)
476 return;
477 if(tofile){
478 filedelete(t->file, q0, q1);
479 if(t->what == Body){
480 t->w->dirty = TRUE;
481 t->w->utflastqid = -1;
482 }
483 if(t->file->ntext > 1)
484 for(i=0; i<t->file->ntext; i++){
485 u = t->file->text[i];
486 if(u != t){
487 u->w->dirty = TRUE; /* always a body */
488 textdelete(u, q0, q1, FALSE);
489 textsetselect(u, u->q0, u->q1);
490 textscrdraw(u);
491 }
492 }
493 }
494 if(q0 < t->iq1)
495 t->iq1 -= min(n, t->iq1-q0);
496 if(q0 < t->q0)
497 t->q0 -= min(n, t->q0-q0);
498 if(q0 < t->q1)
499 t->q1 -= min(n, t->q1-q0);
500 if(q1 <= t->org)
501 t->org -= n;
502 else if(q0 < t->org+t->fr.nchars){
503 p1 = q1 - t->org;
504 if(p1 > t->fr.nchars)
505 p1 = t->fr.nchars;
506 if(q0 < t->org){
507 t->org = q0;
508 p0 = 0;
509 }else
510 p0 = q0 - t->org;
511 frdelete(&t->fr, p0, p1);
512 textfill(t);
513 }
514 if(t->w){
515 c = 'd';
516 if(t->what == Body)
517 c = 'D';
518 winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
519 }
520}
521
522void
523textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
524{
525 *p0 = min(q0, t->file->b.nc);
526 *p1 = min(q1, t->file->b.nc);
527}
528
529Rune
530textreadc(Text *t, uint q)
531{
532 Rune r;
533
534 if(t->cq0<=q && q<t->cq0+t->ncache)
535 r = t->cache[q-t->cq0];
536 else
537 bufread(&t->file->b, q, &r, 1);
538 return r;
539}
540
541int
542textbswidth(Text *t, Rune c)
543{
544 uint q, eq;
545 Rune r;
546 int skipping;
547
548 /* there is known to be at least one character to erase */
549 if(c == 0x08) /* ^H: erase character */
550 return 1;
551 q = t->q0;
552 skipping = TRUE;
553 while(q > 0){
554 r = textreadc(t, q-1);
555 if(r == '\n'){ /* eat at most one more character */
556 if(q == t->q0) /* eat the newline */
557 --q;
558 break;
559 }
560 if(c == 0x17){
561 eq = isalnum(r);
562 if(eq && skipping) /* found one; stop skipping */
563 skipping = FALSE;
564 else if(!eq && !skipping)
565 break;
566 }
567 --q;
568 }
569 return t->q0-q;
570}
571
572int
573textfilewidth(Text *t, uint q0, int oneelement)
574{
575 uint q;
576 Rune r;
577
578 q = q0;
579 while(q > 0){
580 r = textreadc(t, q-1);
581 if(r <= ' ')
582 break;
583 if(oneelement && r=='/')
584 break;
585 --q;
586 }
587 return q0-q;
588}
589
590Rune*
591textcomplete(Text *t)
592{
593 int i, nstr, npath;
594 uint q;
595 Rune tmp[200];
596 Rune *str, *path;
597 Rune *rp;
598 Completion *c;
599 char *s, *dirs;
600 Runestr dir;
601
602 /* control-f: filename completion; works back to white space or / */
603 if(t->q0<t->file->b.nc && textreadc(t, t->q0)>' ') /* must be at end of word */
604 return nil;
605 nstr = textfilewidth(t, t->q0, TRUE);
606 str = runemalloc(nstr);
607 npath = textfilewidth(t, t->q0-nstr, FALSE);
608 path = runemalloc(npath);
609
610 c = nil;
611 rp = nil;
612 dirs = nil;
613
614 q = t->q0-nstr;
615 for(i=0; i<nstr; i++)
616 str[i] = textreadc(t, q++);
617 q = t->q0-nstr-npath;
618 for(i=0; i<npath; i++)
619 path[i] = textreadc(t, q++);
620 /* is path rooted? if not, we need to make it relative to window path */
621 if(npath>0 && path[0]=='/')
622 dir = runestr(path, npath);
623 else{
624 dir = dirname(t, nil, 0);
625 if(dir.nr + 1 + npath > nelem(tmp)){
626 free(dir.r);
627 goto Return;
628 }
629 if(dir.nr == 0){
630 dir.nr = 1;
631 dir.r = runestrdup(Ldot);
632 }
633 runemove(tmp, dir.r, dir.nr);
634 tmp[dir.nr] = '/';
635 runemove(tmp+dir.nr+1, path, npath);
636 free(dir.r);
637 dir.r = tmp;
638 dir.nr += 1+npath;
639 dir = cleanrname(dir);
640 }
641
642 s = smprint("%.*S", nstr, str);
643 dirs = smprint("%.*S", dir.nr, dir.r);
644 c = complete(dirs, s);
645 free(s);
646 if(c == nil){
647 warning(nil, "error attempting completion: %r\n");
648 goto Return;
649 }
650
651 if(!c->advance){
652 warning(nil, "%.*S%s%.*S*%s\n",
653 dir.nr, dir.r,
654 dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
655 nstr, str,
656 c->nmatch ? "" : ": no matches in:");
657 for(i=0; i<c->nfile; i++)
658 warning(nil, " %s\n", c->filename[i]);
659 }
660
661 if(c->advance)
662 rp = runesmprint("%s", c->string);
663 else
664 rp = nil;
665 Return:
666 freecompletion(c);
667 free(dirs);
668 free(str);
669 free(path);
670 return rp;
671}
672
673void
674texttype(Text *t, Rune r)
675{
676 uint q0, q1;
677 int nnb, nb, n, i;
678 int nr;
679 Rune *rp;
680 Text *u;
681
682 if(t->what!=Body && t->what!=Tag && r=='\n')
683 return;
684 if(t->what == Tag)
685 t->w->tagsafe = FALSE;
686
687 nr = 1;
688 rp = &r;
689 switch(r){
690 case Kleft:
691 typecommit(t);
692 if(t->q0 > 0)
693 textshow(t, t->q0-1, t->q0-1, TRUE);
694 return;
695 case Kright:
696 typecommit(t);
697 if(t->q1 < t->file->b.nc)
698 textshow(t, t->q1+1, t->q1+1, TRUE);
699 return;
700 case Kscrollonedown:
701 if(t->what == Tag)
702 goto Tagdown;
703 n = mousescrollsize(t->fr.maxlines);
704 if(n <= 0)
705 n = 1;
706 goto case_Down;
707 case Kpgdown:
708 n = 2*t->fr.maxlines/3;
709 case_Down:
710 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+n*t->fr.font->height));
711 textsetorigin(t, q0, TRUE);
712 return;
713 case Kscrolloneup:
714 if(t->what == Tag)
715 goto Tagup;
716 n = mousescrollsize(t->fr.maxlines);
717 goto case_Up;
718 case Kpgup:
719 n = 2*t->fr.maxlines/3;
720 case_Up:
721 q0 = textbacknl(t, t->org, n);
722 textsetorigin(t, q0, TRUE);
723 return;
724
725/*
726 * Keybindings for moving the cursor up and
727 * down the text via up and down arrow keys.
728 */
729
730 case Kdown:
731 if(t->what == Tag)
732 goto Tagdown;
733 typecommit(t);
734 /* 1rst check for being in the last line*/
735 q0 = t->q0;
736 q1 = q0;
737 if (q1) q1--;
738 nnb = 0;
739 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
740 q0++;
741 if (q0 == (t->file->b.nc)-1) {
742 textshow(t, q0, q0, TRUE);
743 return;
744 }
745 q0++;
746 /* find old pos in ln */
747 while(q1>1 && textreadc(t, q1)!='\n'){
748 nnb++;
749 q1--;
750 }
751 /* go right until reachg pos or \n */
752 while(q0<t->file->b.nc && (nnb>0 && textreadc(t, q0)!='\n')){
753 q0++;
754 nnb--;
755 }
756 if (q0>1 && q0<t->file->b.nc)
757 textshow(t, q0, q0, TRUE);
758 return;
759 case Kup:
760 if(t->what == Tag)
761 goto Tagup;
762 typecommit(t);
763 nnb = 0;
764 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
765 nnb = textbswidth(t, 0x15);
766 /* BOL - 1 if not first line of txt BOL*/
767 if( t->q0-nnb > 1 && textreadc(t, t->q0-nnb-1)=='\n' ) nnb++;
768 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
769 return;
770
771/*
772 * Home and End now respectively take you to the
773 * beginning and to the end of the line respectively.
774 * Also keep the original ^A and ^E keybindings.
775 */
776
777 case 0x01:
778 case Khome:
779 typecommit(t);
780 /* go to where ^U would erase, if not already at BOL */
781 nnb = 0;
782 if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
783 nnb = textbswidth(t, 0x15);
784 textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
785 return;
786 case 0x05:
787 case Kend:
788 typecommit(t);
789 q0 = t->q0;
790 while(q0<t->file->b.nc && textreadc(t, q0)!='\n')
791 q0++;
792 textshow(t, q0, q0, TRUE);
793 return;
794
795/* I'll keep the MAC-keybindings 'cuz im such a nice guy */
796
797 case Kcmd+'c': /* %C: copy */
798 typecommit(t);
799 cut(t, t, nil, TRUE, FALSE, nil, 0);
800 return;
801 case Kcmd+'z': /* %Z: undo */
802 typecommit(t);
803 undo(t, nil, nil, TRUE, 0, nil, 0);
804 return;
805 case Kcmd+'Z': /* %-shift-Z: redo */
806 typecommit(t);
807 undo(t, nil, nil, FALSE, 0, nil, 0);
808 return;
809
810/*
811 * Adding Windows and X11 -compatible Ctrl+C, Ctrl+Z and
812 * Ctrl+Y (for redoing). TODO: find out how to check out for
813 * shift press, for Ctrl+Shift+Z in this godforsaken switch()
814 */
815
816 case 0x03: /* Ctrl+C: copy */
817 typecommit(t);
818 cut(t, t, nil, TRUE, FALSE, nil, 0);
819 return;
820 case 0x1A: /* Ctrl+Z: undo */
821 typecommit(t);
822 undo(t, nil, nil, TRUE, 0, nil, 0);
823 return;
824 case 0x19: /* Ctrl+Y: redo */
825 typecommit(t);
826 undo(t, nil, nil, FALSE, 0, nil, 0);
827 return;
828
829 Tagdown:
830 /* expand tag to show all text */
831 if(!t->w->tagexpand){
832 t->w->tagexpand = TRUE;
833 winresize(t->w, t->w->r, FALSE, TRUE);
834 }
835 return;
836 Tagup:
837 /* shrink tag to single line */
838 if(t->w->tagexpand){
839 t->w->tagexpand = FALSE;
840 t->w->taglines = 1;
841 winresize(t->w, t->w->r, FALSE, TRUE);
842 }
843 return;
844 }
845
846
847
848 if(t->what == Body){
849 seq++;
850 filemark(t->file);
851 }
852
853 /* cut/paste must be done after the seq++/filemark */
854
855 switch(r){
856 case Kcmd+'x': /* %X: cut */
857 typecommit(t);
858 if(t->what == Body){
859 seq++;
860 filemark(t->file);
861 }
862 cut(t, t, nil, TRUE, TRUE, nil, 0);
863 textshow(t, t->q0, t->q0, 1);
864 t->iq1 = t->q0;
865 return;
866 case Kcmd+'v': /* %V: paste */
867 typecommit(t);
868 if(t->what == Body){
869 seq++;
870 filemark(t->file);
871 }
872 paste(t, t, nil, TRUE, FALSE, nil, 0);
873 textshow(t, t->q0, t->q1, 1);
874 t->iq1 = t->q1;
875 return;
876
877 /* Same for Windows / X11 */
878
879 case 0x18: /* Ctrl+X: cut */
880 typecommit(t);
881 if(t->what == Body){
882 seq++;
883 filemark(t->file);
884 }
885 cut(t, t, nil, TRUE, TRUE, nil, 0);
886 textshow(t, t->q0, t->q0, 1);
887 t->iq1 = t->q0;
888 return;
889 case 0x16: /* Ctrl+V: paste */
890 typecommit(t);
891 if(t->what == Body){
892 seq++;
893 filemark(t->file);
894 }
895 paste(t, t, nil, TRUE, FALSE, nil, 0);
896 textshow(t, t->q0, t->q1, 1);
897 t->iq1 = t->q1;
898 return;
899 case 0x08: /* Backspace */
900 typecommit(t);
901 if(t->q0 == t->q1){
902 if(t->q0 == 0) return;
903 q0 = t->q0 - 1;
904 q1 = t->q0;
905 } else {
906 q0 = t->q0;
907 q1 = t->q1;
908 }
909 textdelete(t, q0, q1, TRUE);
910 textsetselect(t, q0, q0);
911 t->iq1 = q0;
912 return;
913
914 case 0x7F: /* Delete */
915 typecommit(t);
916 if(t->q0 == t->file->b.nc) return;
917 if(t->q0 == t->q1){
918 q0 = t->q0;
919 q1 = q0 + 1;
920 } else {
921 q0 = t->q0;
922 q1 = t->q1;
923 }
924 textdelete(t, q0, q1, TRUE);
925 textsetselect(t, q0, q0);
926 t->iq1 = q0;
927 return;
928 }
929
930 if(t->q1 > t->q0){
931 if(t->ncache != 0)
932 error("text.type");
933 cut(t, t, nil, TRUE, TRUE, nil, 0);
934 t->eq0 = ~0;
935 }
936 textshow(t, t->q0, t->q0, 1);
937
938 switch(r){
939 case Kins:
940 case 0x06:
941 typecommit(t);
942 rp = textcomplete(t);
943 if(rp == nil)
944 return;
945 nr = runestrlen(rp);
946 break; /* fall through to normal insertion case */
947 case 0x1B:
948 if(t->eq0 != ~0) {
949 if(t->eq0 <= t->q0)
950 textsetselect(t, t->eq0, t->q0);
951 else
952 textsetselect(t, t->q0, t->eq0);
953 }
954 if(t->ncache > 0)
955 typecommit(t);
956 t->iq1 = t->q0;
957 return;
958
959/*
960 * quick hack for DELETE-key, just added it to see what happens and
961 * apparently it works like ^U and erases whole lines. i should learn c
962 */
963
964 case 0x0C:
965 typecommit(t);
966 q0 = 0;
967 q1 = t->file->b.nc;
968 textsetselect(t,q0,q1);
969 textshow(t, q0, q1, 1);
970 t->q1 = q1;
971 return;
972 case 0x15: /* ^U: erase line */
973 case 0x17: /* ^W: erase word */
974 if(t->q0 == 0) /* nothing to erase */
975 return;
976 nnb = textbswidth(t, r);
977 q1 = t->q0;
978 q0 = q1-nnb;
979 /* if selection is at beginning of window, avoid deleting invisible text */
980 if(q0 < t->org){
981 q0 = t->org;
982 nnb = q1-q0;
983 }
984 if(nnb <= 0)
985 return;
986 for(i=0; i<t->file->ntext; i++){
987 u = t->file->text[i];
988 u->nofill = TRUE;
989 nb = nnb;
990 n = u->ncache;
991 if(n > 0){
992 if(q1 != u->cq0+n)
993 error("text.type backspace");
994 if(n > nb)
995 n = nb;
996 u->ncache -= n;
997 textdelete(u, q1-n, q1, FALSE);
998 nb -= n;
999 }
1000 if(u->eq0==q1 || u->eq0==~0)
1001 u->eq0 = q0;
1002 if(nb && u==t)
1003 textdelete(u, q0, q0+nb, TRUE);
1004 if(u != t)
1005 textsetselect(u, u->q0, u->q1);
1006 else
1007 textsetselect(t, q0, q0);
1008 u->nofill = FALSE;
1009 }
1010 for(i=0; i<t->file->ntext; i++)
1011 textfill(t->file->text[i]);
1012 t->iq1 = t->q0;
1013 return;
1014 case '\n':
1015 if(t->w->autoindent){
1016 /* find beginning of previous line using backspace code */
1017 nnb = textbswidth(t, 0x15); /* ^U case */
1018 rp = runemalloc(nnb + 1);
1019 nr = 0;
1020 rp[nr++] = r;
1021 for(i=0; i<nnb; i++){
1022 r = textreadc(t, t->q0-nnb+i);
1023 if(r != ' ' && r != '\t')
1024 break;
1025 rp[nr++] = r;
1026 }
1027 }
1028 break; /* fall through to normal code */
1029 }
1030 /* otherwise ordinary character; just insert, typically in caches of all texts */
1031 for(i=0; i<t->file->ntext; i++){
1032 u = t->file->text[i];
1033 if(u->eq0 == ~0)
1034 u->eq0 = t->q0;
1035 if(u->ncache == 0)
1036 u->cq0 = t->q0;
1037 else if(t->q0 != u->cq0+u->ncache)
1038 error("text.type cq1");
1039 /*
1040 * Change the tag before we add to ncache,
1041 * so that if the window body is resized the
1042 * commit will not find anything in ncache.
1043 */
1044 if(u->what==Body && u->ncache == 0){
1045 u->needundo = TRUE;
1046 winsettag(t->w);
1047 u->needundo = FALSE;
1048 }
1049 textinsert(u, t->q0, rp, nr, FALSE);
1050 if(u != t)
1051 textsetselect(u, u->q0, u->q1);
1052 if(u->ncache+nr > u->ncachealloc){
1053 u->ncachealloc += 10 + nr;
1054 u->cache = runerealloc(u->cache, u->ncachealloc);
1055 }
1056 runemove(u->cache+u->ncache, rp, nr);
1057 u->ncache += nr;
1058 }
1059 if(rp != &r)
1060 free(rp);
1061 textsetselect(t, t->q0+nr, t->q0+nr);
1062 if(r=='\n' && t->w!=nil)
1063 wincommit(t->w, t);
1064 t->iq1 = t->q0;
1065}
1066
1067void
1068textcommit(Text *t, int tofile)
1069{
1070 if(t->ncache == 0)
1071 return;
1072 if(tofile)
1073 fileinsert(t->file, t->cq0, t->cache, t->ncache);
1074 if(t->what == Body){
1075 t->w->dirty = TRUE;
1076 t->w->utflastqid = -1;
1077 }
1078 t->ncache = 0;
1079}
1080
1081static Text *clicktext;
1082static uint clickmsec;
1083static Text *selecttext;
1084static uint selectq;
1085
1086/*
1087 * called from frame library
1088 */
1089void
1090framescroll(Frame *f, int dl)
1091{
1092 if(f != &selecttext->fr)
1093 error("frameselect not right frame");
1094 textframescroll(selecttext, dl);
1095}
1096
1097void
1098textframescroll(Text *t, int dl)
1099{
1100 uint q0;
1101
1102 if(dl == 0){
1103 scrsleep(100);
1104 return;
1105 }
1106 if(dl < 0){
1107 q0 = textbacknl(t, t->org, -dl);
1108 if(selectq > t->org+t->fr.p0)
1109 textsetselect(t, t->org+t->fr.p0, selectq);
1110 else
1111 textsetselect(t, selectq, t->org+t->fr.p0);
1112 }else{
1113 if(t->org+t->fr.nchars == t->file->b.nc)
1114 return;
1115 q0 = t->org+frcharofpt(&t->fr, Pt(t->fr.r.min.x, t->fr.r.min.y+dl*t->fr.font->height));
1116 if(selectq > t->org+t->fr.p1)
1117 textsetselect(t, t->org+t->fr.p1, selectq);
1118 else
1119 textsetselect(t, selectq, t->org+t->fr.p1);
1120 }
1121 textsetorigin(t, q0, TRUE);
1122}
1123
1124
1125void
1126textselect(Text *t)
1127{
1128 uint q0, q1;
1129 int b, x, y;
1130 int state;
1131 enum { None, Cut, Paste };
1132
1133 selecttext = t;
1134 /*
1135 * To have double-clicking and chording, we double-click
1136 * immediately if it might make sense.
1137 */
1138 b = mouse->buttons;
1139 q0 = t->q0;
1140 q1 = t->q1;
1141 selectq = t->org+frcharofpt(&t->fr, mouse->xy);
1142 if(clicktext==t && mouse->msec-clickmsec<500)
1143 if(q0==q1 && selectq==q0){
1144 textdoubleclick(t, &q0, &q1);
1145 textsetselect(t, q0, q1);
1146 flushimage(display, 1);
1147 x = mouse->xy.x;
1148 y = mouse->xy.y;
1149 /* stay here until something interesting happens */
1150 do
1151 readmouse(mousectl);
1152 while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
1153 mouse->xy.x = x; /* in case we're calling frselect */
1154 mouse->xy.y = y;
1155 q0 = t->q0; /* may have changed */
1156 q1 = t->q1;
1157 selectq = q0;
1158 }
1159 if(mouse->buttons == b){
1160 t->fr.scroll = framescroll;
1161 frselect(&t->fr, mousectl);
1162 /* horrible botch: while asleep, may have lost selection altogether */
1163 if(selectq > t->file->b.nc)
1164 selectq = t->org + t->fr.p0;
1165 t->fr.scroll = nil;
1166 if(selectq < t->org)
1167 q0 = selectq;
1168 else
1169 q0 = t->org + t->fr.p0;
1170 if(selectq > t->org+t->fr.nchars)
1171 q1 = selectq;
1172 else
1173 q1 = t->org+t->fr.p1;
1174 }
1175 if(q0 == q1){
1176 if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
1177 textdoubleclick(t, &q0, &q1);
1178 clicktext = nil;
1179 }else{
1180 clicktext = t;
1181 clickmsec = mouse->msec;
1182 }
1183 }else
1184 clicktext = nil;
1185 textsetselect(t, q0, q1);
1186 flushimage(display, 1);
1187 state = None; /* what we've done; undo when possible */
1188 while(mouse->buttons){
1189 mouse->msec = 0;
1190 b = mouse->buttons;
1191 if((b&1) && (b&6)){
1192 if(state==None && t->what==Body){
1193 seq++;
1194 filemark(t->w->body.file);
1195 }
1196 if(b & 2){
1197 if(state==Paste && t->what==Body){
1198 winundo(t->w, TRUE);
1199 textsetselect(t, q0, t->q1);
1200 state = None;
1201 }else if(state != Cut){
1202 cut(t, t, nil, TRUE, TRUE, nil, 0);
1203 state = Cut;
1204 }
1205 }else{
1206 if(state==Cut && t->what==Body){
1207 winundo(t->w, TRUE);
1208 textsetselect(t, q0, t->q1);
1209 state = None;
1210 }else if(state != Paste){
1211 paste(t, t, nil, TRUE, FALSE, nil, 0);
1212 state = Paste;
1213 }
1214 }
1215 textscrdraw(t);
1216 clearmouse();
1217 }
1218 flushimage(display, 1);
1219 while(mouse->buttons == b)
1220 readmouse(mousectl);
1221 clicktext = nil;
1222 }
1223}
1224
1225void
1226textshow(Text *t, uint q0, uint q1, int doselect)
1227{
1228 int qe;
1229 int nl;
1230 int tsd;
1231 int nc;
1232 uint q;
1233
1234 if(t->what != Body){
1235 if(doselect)
1236 textsetselect(t, q0, q1);
1237 return;
1238 }
1239 if(t->w!=nil && t->fr.maxlines==0)
1240 colgrow(t->col, t->w, 1);
1241 if(doselect)
1242 textsetselect(t, q0, q1);
1243 qe = t->org+t->fr.nchars;
1244 tsd = FALSE; /* do we call textscrdraw? */
1245 nc = t->file->b.nc+t->ncache;
1246 if(t->org <= q0){
1247 if(nc==0 || q0<qe)
1248 tsd = TRUE;
1249 else if(q0==qe && qe==nc){
1250 if(textreadc(t, nc-1) == '\n'){
1251 if(t->fr.nlines<t->fr.maxlines)
1252 tsd = TRUE;
1253 }else
1254 tsd = TRUE;
1255 }
1256 }
1257 if(tsd)
1258 textscrdraw(t);
1259 else{
1260 if(t->w->nopen[QWevent] > 0)
1261 nl = 3*t->fr.maxlines/4;
1262 else
1263 nl = t->fr.maxlines/4;
1264 q = textbacknl(t, q0, nl);
1265 /* avoid going backwards if trying to go forwards - long lines! */
1266 if(!(q0>t->org && q<t->org))
1267 textsetorigin(t, q, TRUE);
1268 while(q0 > t->org+t->fr.nchars)
1269 textsetorigin(t, t->org+1, FALSE);
1270 }
1271}
1272
1273static
1274int
1275region(int a, int b)
1276{
1277 if(a < b)
1278 return -1;
1279 if(a == b)
1280 return 0;
1281 return 1;
1282}
1283
1284void
1285selrestore(Frame *f, Point pt0, uint p0, uint p1)
1286{
1287 if(p1<=f->p0 || p0>=f->p1){
1288 /* no overlap */
1289 frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1290 return;
1291 }
1292 if(p0>=f->p0 && p1<=f->p1){
1293 /* entirely inside */
1294 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1295 return;
1296 }
1297
1298 /* they now are known to overlap */
1299
1300 /* before selection */
1301 if(p0 < f->p0){
1302 frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1303 p0 = f->p0;
1304 pt0 = frptofchar(f, p0);
1305 }
1306 /* after selection */
1307 if(p1 > f->p1){
1308 frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1309 p1 = f->p1;
1310 }
1311 /* inside selection */
1312 frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1313}
1314
1315void
1316textsetselect(Text *t, uint q0, uint q1)
1317{
1318 int p0, p1, ticked;
1319
1320 /* t->fr.p0 and t->fr.p1 are always right; t->q0 and t->q1 may be off */
1321 t->q0 = q0;
1322 t->q1 = q1;
1323 /* compute desired p0,p1 from q0,q1 */
1324 p0 = q0-t->org;
1325 p1 = q1-t->org;
1326 ticked = 1;
1327 if(p0 < 0){
1328 ticked = 0;
1329 p0 = 0;
1330 }
1331 if(p1 < 0)
1332 p1 = 0;
1333 if(p0 > t->fr.nchars)
1334 p0 = t->fr.nchars;
1335 if(p1 > t->fr.nchars){
1336 ticked = 0;
1337 p1 = t->fr.nchars;
1338 }
1339 if(p0==t->fr.p0 && p1==t->fr.p1){
1340 if(p0 == p1 && ticked != t->fr.ticked)
1341 frtick(&t->fr, frptofchar(&t->fr, p0), ticked);
1342 return;
1343 }
1344 if(p0 > p1)
1345 sysfatal("acme: textsetselect p0=%d p1=%d q0=%ud q1=%ud t->org=%d nchars=%d", p0, p1, q0, q1, (int)t->org, (int)t->fr.nchars);
1346 /* screen disagrees with desired selection */
1347 if(t->fr.p1<=p0 || p1<=t->fr.p0 || p0==p1 || t->fr.p1==t->fr.p0){
1348 /* no overlap or too easy to bother trying */
1349 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, t->fr.p1, 0);
1350 if(p0 != p1 || ticked)
1351 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, p1, 1);
1352 goto Return;
1353 }
1354 /* overlap; avoid unnecessary painting */
1355 if(p0 < t->fr.p0){
1356 /* extend selection backwards */
1357 frdrawsel(&t->fr, frptofchar(&t->fr, p0), p0, t->fr.p0, 1);
1358 }else if(p0 > t->fr.p0){
1359 /* trim first part of selection */
1360 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p0), t->fr.p0, p0, 0);
1361 }
1362 if(p1 > t->fr.p1){
1363 /* extend selection forwards */
1364 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1), t->fr.p1, p1, 1);
1365 }else if(p1 < t->fr.p1){
1366 /* trim last part of selection */
1367 frdrawsel(&t->fr, frptofchar(&t->fr, p1), p1, t->fr.p1, 0);
1368 }
1369
1370 Return:
1371 t->fr.p0 = p0;
1372 t->fr.p1 = p1;
1373}
1374
1375/*
1376 * Release the button in less than DELAY ms and it's considered a null selection
1377 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1378 */
1379enum {
1380 DELAY = 2,
1381 MINMOVE = 4
1382};
1383
1384uint
1385xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p) /* when called, button is down */
1386{
1387 uint p0, p1, q, tmp;
1388 ulong msec;
1389 Point mp, pt0, pt1, qt;
1390 int reg, b;
1391
1392 mp = mc->m.xy;
1393 b = mc->m.buttons;
1394 msec = mc->m.msec;
1395
1396 /* remove tick */
1397 if(f->p0 == f->p1)
1398 frtick(f, frptofchar(f, f->p0), 0);
1399 p0 = p1 = frcharofpt(f, mp);
1400 pt0 = frptofchar(f, p0);
1401 pt1 = frptofchar(f, p1);
1402 reg = 0;
1403 frtick(f, pt0, 1);
1404 do{
1405 q = frcharofpt(f, mc->m.xy);
1406 if(p1 != q){
1407 if(p0 == p1)
1408 frtick(f, pt0, 0);
1409 if(reg != region(q, p0)){ /* crossed starting point; reset */
1410 if(reg > 0)
1411 selrestore(f, pt0, p0, p1);
1412 else if(reg < 0)
1413 selrestore(f, pt1, p1, p0);
1414 p1 = p0;
1415 pt1 = pt0;
1416 reg = region(q, p0);
1417 if(reg == 0)
1418 frdrawsel0(f, pt0, p0, p1, col, display->white);
1419 }
1420 qt = frptofchar(f, q);
1421 if(reg > 0){
1422 if(q > p1)
1423 frdrawsel0(f, pt1, p1, q, col, display->white);
1424
1425 else if(q < p1)
1426 selrestore(f, qt, q, p1);
1427 }else if(reg < 0){
1428 if(q > p1)
1429 selrestore(f, pt1, p1, q);
1430 else
1431 frdrawsel0(f, qt, q, p1, col, display->white);
1432 }
1433 p1 = q;
1434 pt1 = qt;
1435 }
1436 if(p0 == p1)
1437 frtick(f, pt0, 1);
1438 flushimage(f->display, 1);
1439 readmouse(mc);
1440 }while(mc->m.buttons == b);
1441 if(mc->m.msec-msec < DELAY && p0!=p1
1442 && abs(mp.x-mc->m.xy.x)<MINMOVE
1443 && abs(mp.y-mc->m.xy.y)<MINMOVE) {
1444 if(reg > 0)
1445 selrestore(f, pt0, p0, p1);
1446 else if(reg < 0)
1447 selrestore(f, pt1, p1, p0);
1448 p1 = p0;
1449 }
1450 if(p1 < p0){
1451 tmp = p0;
1452 p0 = p1;
1453 p1 = tmp;
1454 }
1455 pt0 = frptofchar(f, p0);
1456 if(p0 == p1)
1457 frtick(f, pt0, 0);
1458 selrestore(f, pt0, p0, p1);
1459 /* restore tick */
1460 if(f->p0 == f->p1)
1461 frtick(f, frptofchar(f, f->p0), 1);
1462 flushimage(f->display, 1);
1463 *p1p = p1;
1464 return p0;
1465}
1466
1467int
1468textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1469{
1470 uint p0, p1;
1471 int buts;
1472
1473 p0 = xselect(&t->fr, mousectl, high, &p1);
1474 buts = mousectl->m.buttons;
1475 if((buts & mask) == 0){
1476 *q0 = p0+t->org;
1477 *q1 = p1+t->org;
1478 }
1479
1480 while(mousectl->m.buttons)
1481 readmouse(mousectl);
1482 return buts;
1483}
1484
1485int
1486textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1487{
1488 int buts;
1489
1490 *tp = nil;
1491 buts = textselect23(t, q0, q1, but2col, 4);
1492 if(buts & 4)
1493 return 0;
1494 if(buts & 1){ /* pick up argument */
1495 *tp = argtext;
1496 return 1;
1497 }
1498 return 1;
1499}
1500
1501int
1502textselect3(Text *t, uint *q0, uint *q1)
1503{
1504 int h;
1505
1506 h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1507 return h;
1508}
1509
1510static Rune left1[] = { '{', '[', '(', '<', 0xab, 0 };
1511static Rune right1[] = { '}', ']', ')', '>', 0xbb, 0 };
1512static Rune left2[] = { '\n', 0 };
1513static Rune left3[] = { '\'', '"', '`', 0 };
1514
1515static
1516Rune *left[] = {
1517 left1,
1518 left2,
1519 left3,
1520 nil
1521};
1522static
1523Rune *right[] = {
1524 right1,
1525 left2,
1526 left3,
1527 nil
1528};
1529
1530void
1531textdoubleclick(Text *t, uint *q0, uint *q1)
1532{
1533 int c, i;
1534 Rune *r, *l, *p;
1535 uint q;
1536
1537 if(textclickhtmlmatch(t, q0, q1))
1538 return;
1539
1540 for(i=0; left[i]!=nil; i++){
1541 q = *q0;
1542 l = left[i];
1543 r = right[i];
1544 /* try matching character to left, looking right */
1545 if(q == 0)
1546 c = '\n';
1547 else
1548 c = textreadc(t, q-1);
1549 p = runestrchr(l, c);
1550 if(p != nil){
1551 if(textclickmatch(t, c, r[p-l], 1, &q))
1552 *q1 = q-(c!='\n');
1553 return;
1554 }
1555 /* try matching character to right, looking left */
1556 if(q == t->file->b.nc)
1557 c = '\n';
1558 else
1559 c = textreadc(t, q);
1560 p = runestrchr(r, c);
1561 if(p != nil){
1562 if(textclickmatch(t, c, l[p-r], -1, &q)){
1563 *q1 = *q0+(*q0<t->file->b.nc && c=='\n');
1564 *q0 = q;
1565 if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1566 (*q0)++;
1567 }
1568 return;
1569 }
1570 }
1571
1572 /* try filling out word to right */
1573 while(*q1<t->file->b.nc && isalnum(textreadc(t, *q1)))
1574 (*q1)++;
1575 /* try filling out word to left */
1576 while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1577 (*q0)--;
1578}
1579
1580int
1581textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1582{
1583 Rune c;
1584 int nest;
1585
1586 nest = 1;
1587 for(;;){
1588 if(dir > 0){
1589 if(*q == t->file->b.nc)
1590 break;
1591 c = textreadc(t, *q);
1592 (*q)++;
1593 }else{
1594 if(*q == 0)
1595 break;
1596 (*q)--;
1597 c = textreadc(t, *q);
1598 }
1599 if(c == cr){
1600 if(--nest==0)
1601 return 1;
1602 }else if(c == cl)
1603 nest++;
1604 }
1605 return cl=='\n' && nest==1;
1606}
1607
1608// Is the text starting at location q an html tag?
1609// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1610// Set *q1, if non-nil, to the location after the tag.
1611static int
1612ishtmlstart(Text *t, uint q, uint *q1)
1613{
1614 int c, c1, c2;
1615
1616 if(q+2 > t->file->b.nc)
1617 return 0;
1618 if(textreadc(t, q++) != '<')
1619 return 0;
1620 c = textreadc(t, q++);
1621 c1 = c;
1622 c2 = c;
1623 while(c != '>') {
1624 if(q >= t->file->b.nc)
1625 return 0;
1626 c2 = c;
1627 c = textreadc(t, q++);
1628 }
1629 if(q1)
1630 *q1 = q;
1631 if(c1 == '/') // closing tag
1632 return -1;
1633 if(c2 == '/' || c2 == '!') // open + close tag or comment
1634 return 0;
1635 return 1;
1636}
1637
1638// Is the text ending at location q an html tag?
1639// Return 1 for <a>, -1 for </a>, 0 for no tag or <a />.
1640// Set *q0, if non-nil, to the start of the tag.
1641static int
1642ishtmlend(Text *t, uint q, uint *q0)
1643{
1644 int c, c1, c2;
1645
1646 if(q < 2)
1647 return 0;
1648 if(textreadc(t, --q) != '>')
1649 return 0;
1650 c = textreadc(t, --q);
1651 c1 = c;
1652 c2 = c;
1653 while(c != '<') {
1654 if(q == 0)
1655 return 0;
1656 c1 = c;
1657 c = textreadc(t, --q);
1658 }
1659 if(q0)
1660 *q0 = q;
1661 if(c1 == '/') // closing tag
1662 return -1;
1663 if(c2 == '/' || c2 == '!') // open + close tag or comment
1664 return 0;
1665 return 1;
1666}
1667
1668int
1669textclickhtmlmatch(Text *t, uint *q0, uint *q1)
1670{
1671 int depth, n;
1672 uint q, nq;
1673
1674 q = *q0;
1675 // after opening tag? scan forward for closing tag
1676 if(ishtmlend(t, q, nil) == 1) {
1677 depth = 1;
1678 while(q < t->file->b.nc) {
1679 n = ishtmlstart(t, q, &nq);
1680 if(n != 0) {
1681 depth += n;
1682 if(depth == 0) {
1683 *q1 = q;
1684 return 1;
1685 }
1686 q = nq;
1687 continue;
1688 }
1689 q++;
1690 }
1691 }
1692
1693 // before closing tag? scan backward for opening tag
1694 if(ishtmlstart(t, q, nil) == -1) {
1695 depth = -1;
1696 while(q > 0) {
1697 n = ishtmlend(t, q, &nq);
1698 if(n != 0) {
1699 depth += n;
1700 if(depth == 0) {
1701 *q0 = q;
1702 return 1;
1703 }
1704 q = nq;
1705 continue;
1706 }
1707 q--;
1708 }
1709 }
1710
1711 return 0;
1712}
1713
1714uint
1715textbacknl(Text *t, uint p, uint n)
1716{
1717 int i, j;
1718
1719 /* look for start of this line if n==0 */
1720 if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1721 n = 1;
1722 i = n;
1723 while(i-->0 && p>0){
1724 --p; /* it's at a newline now; back over it */
1725 if(p == 0)
1726 break;
1727 /* at 128 chars, call it a line anyway */
1728 for(j=128; --j>0 && p>0; p--)
1729 if(textreadc(t, p-1)=='\n')
1730 break;
1731 }
1732 return p;
1733}
1734
1735void
1736textsetorigin(Text *t, uint org, int exact)
1737{
1738 int i, a, fixup;
1739 Rune *r;
1740 uint n;
1741
1742 if(org>0 && !exact && textreadc(t, org-1) != '\n'){
1743 /* org is an estimate of the char posn; find a newline */
1744 /* don't try harder than 256 chars */
1745 for(i=0; i<256 && org<t->file->b.nc; i++){
1746 if(textreadc(t, org) == '\n'){
1747 org++;
1748 break;
1749 }
1750 org++;
1751 }
1752 }
1753 a = org-t->org;
1754 fixup = 0;
1755 if(a>=0 && a<t->fr.nchars){
1756 frdelete(&t->fr, 0, a);
1757 fixup = 1; /* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1758 }
1759 else if(a<0 && -a<t->fr.nchars){
1760 n = t->org - org;
1761 r = runemalloc(n);
1762 bufread(&t->file->b, org, r, n);
1763 frinsert(&t->fr, r, r+n, 0);
1764 free(r);
1765 }else
1766 frdelete(&t->fr, 0, t->fr.nchars);
1767 t->org = org;
1768 textfill(t);
1769 textscrdraw(t);
1770 textsetselect(t, t->q0, t->q1);
1771 if(fixup && t->fr.p1 > t->fr.p0)
1772 frdrawsel(&t->fr, frptofchar(&t->fr, t->fr.p1-1), t->fr.p1-1, t->fr.p1, 1);
1773}
1774
1775void
1776textreset(Text *t)
1777{
1778 t->file->seq = 0;
1779 t->eq0 = ~0;
1780 /* do t->delete(0, t->nc, TRUE) without building backup stuff */
1781 textsetselect(t, t->org, t->org);
1782 frdelete(&t->fr, 0, t->fr.nchars);
1783 t->org = 0;
1784 t->q0 = 0;
1785 t->q1 = 0;
1786 filereset(t->file);
1787 bufreset(&t->file->b);
1788}