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