Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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 <complete.h>
12
#include "dat.h"
13
#include "fns.h"
14
 
15
Image	*tagcols[NCOL];
16
Image	*textcols[NCOL];
17
 
18
enum{
19
	TABDIR = 3	/* width of tabs in directory windows */
20
};
21
 
22
void
23
textinit(Text *t, File *f, Rectangle r, Reffont *rf, Image *cols[NCOL])
24
{
25
	t->file = f;
26
	t->all = r;
27
	t->scrollr = r;
28
	t->scrollr.max.x = r.min.x+Scrollwid;
29
	t->lastsr = nullrect;
30
	r.min.x += Scrollwid+Scrollgap;
31
	t->eq0 = ~0;
32
	t->ncache = 0;
33
	t->reffont = rf;
34
	t->tabstop = maxtab;
35
	memmove(t->Frame.cols, cols, sizeof t->Frame.cols);
36
	textredraw(t, r, rf->f, screen, -1);
37
}
38
 
39
void
40
textredraw(Text *t, Rectangle r, Font *f, Image *b, int odx)
41
{
42
	int maxt;
43
	Rectangle rr;
44
 
45
	frinit(t, r, f, b, t->Frame.cols);
46
	rr = t->r;
47
	rr.min.x -= Scrollwid+Scrollgap;	/* back fill to scroll bar */
48
	draw(t->b, rr, t->cols[BACK], nil, ZP);
49
	/* use no wider than 3-space tabs in a directory */
50
	maxt = maxtab;
51
	if(t->what == Body){
52
		if(t->w->isdir)
53
			maxt = min(TABDIR, maxtab);
54
		else
55
			maxt = t->tabstop;
56
	}
57
	t->maxtab = maxt*stringwidth(f, "0");
58
	if(t->what==Body && t->w->isdir && odx!=Dx(t->all)){
59
		if(t->maxlines > 0){
60
			textreset(t);
61
			textcolumnate(t, t->w->dlp,  t->w->ndl);
62
			textshow(t, 0, 0, 1);
63
		}
64
	}else{
65
		textfill(t);
66
		textsetselect(t, t->q0, t->q1);
67
	}
68
}
69
 
70
int
71
textresize(Text *t, Rectangle r)
72
{
73
	int odx;
74
 
75
	if(Dy(r) > 0)
76
		r.max.y -= Dy(r)%t->font->height;
77
	else
78
		r.max.y = r.min.y;
79
	odx = Dx(t->all);
80
	t->all = r;
81
	t->scrollr = r;
82
	t->scrollr.max.x = r.min.x+Scrollwid;
83
	t->lastsr = nullrect;
84
	r.min.x += Scrollwid+Scrollgap;
85
	frclear(t, 0);
86
	textredraw(t, r, t->font, t->b, odx);
87
	return r.max.y;
88
}
89
 
90
void
91
textclose(Text *t)
92
{
93
	free(t->cache);
94
	frclear(t, 1);
95
	filedeltext(t->file, t);
96
	t->file = nil;
97
	rfclose(t->reffont);
98
	if(argtext == t)
99
		argtext = nil;
100
	if(typetext == t)
101
		typetext = nil;
102
	if(seltext == t)
103
		seltext = nil;
104
	if(mousetext == t)
105
		mousetext = nil;
106
	if(barttext == t)
107
		barttext = nil;
108
}
109
 
110
int
111
dircmp(void *a, void *b)
112
{
113
	Dirlist *da, *db;
114
	int i, n;
115
 
116
	da = *(Dirlist**)a;
117
	db = *(Dirlist**)b;
118
	n = min(da->nr, db->nr);
119
	i = memcmp(da->r, db->r, n*sizeof(Rune));
120
	if(i)
121
		return i;
122
	return da->nr - db->nr;
123
}
124
 
125
void
126
textcolumnate(Text *t, Dirlist **dlp, int ndl)
127
{
128
	int i, j, w, colw, mint, maxt, ncol, nrow;
129
	Dirlist *dl;
130
	uint q1;
131
 
132
	if(t->file->ntext > 1)
133
		return;
134
	mint = stringwidth(t->font, "0");
135
	/* go for narrower tabs if set more than 3 wide */
136
	t->maxtab = min(maxtab, TABDIR)*mint;
137
	maxt = t->maxtab;
138
	colw = 0;
139
	for(i=0; i<ndl; i++){
140
		dl = dlp[i];
141
		w = dl->wid;
142
		if(maxt-w%maxt < mint || w%maxt==0)
143
			w += mint;
144
		if(w % maxt)
145
			w += maxt-(w%maxt);
146
		if(w > colw)
147
			colw = w;
148
	}
149
	if(colw == 0)
150
		ncol = 1;
151
	else
152
		ncol = max(1, Dx(t->r)/colw);
153
	nrow = (ndl+ncol-1)/ncol;
154
 
155
	q1 = 0;
156
	for(i=0; i<nrow; i++){
157
		for(j=i; j<ndl; j+=nrow){
158
			dl = dlp[j];
159
			fileinsert(t->file, q1, dl->r, dl->nr);
160
			q1 += dl->nr;
161
			if(j+nrow >= ndl)
162
				break;
163
			w = dl->wid;
164
			if(maxt-w%maxt < mint){
165
				fileinsert(t->file, q1, L"\t", 1);
166
				q1++;
167
				w += mint;
168
			}
169
			do{
170
				fileinsert(t->file, q1, L"\t", 1);
171
				q1++;
172
				w += maxt-(w%maxt);
173
			}while(w < colw);
174
		}
175
		fileinsert(t->file, q1, L"\n", 1);
176
		q1++;
177
	}
178
}
179
 
180
uint
181
textload(Text *t, uint q0, char *file, int setqid)
182
{
183
	Rune *rp;
184
	Dirlist *dl, **dlp;
185
	int fd, i, j, n, ndl, nulls;
186
	uint q, q1;
187
	Dir *d, *dbuf;
188
	char *tmp;
189
	Text *u;
190
 
191
	if(t->ncache!=0 || t->file->nc || t->w==nil || t!=&t->w->body)
192
		error("text.load");
193
	if(t->w->isdir && t->file->nname==0){
194
		warning(nil, "empty directory name\n");
195
		return 0;
196
	}
197
	fd = open(file, OREAD);
198
	if(fd < 0){
199
		warning(nil, "can't open %s: %r\n", file);
200
		return 0;
201
	}
202
	d = dirfstat(fd);
203
	if(d == nil){
204
		warning(nil, "can't fstat %s: %r\n", file);
205
		goto Rescue;
206
	}
207
	nulls = FALSE;
208
	if(d->qid.type & QTDIR){
209
		/* this is checked in get() but it's possible the file changed underfoot */
210
		if(t->file->ntext > 1){
211
			warning(nil, "%s is a directory; can't read with multiple windows on it\n", file);
212
			goto Rescue;
213
		}
214
		t->w->isdir = TRUE;
215
		t->w->filemenu = FALSE;
216
		if(t->file->name[t->file->nname-1] != '/'){
217
			rp = runemalloc(t->file->nname+1);
218
			runemove(rp, t->file->name, t->file->nname);
219
			rp[t->file->nname] = '/';
220
			winsetname(t->w, rp, t->file->nname+1);
221
			free(rp);
222
		}
223
		dlp = nil;
224
		ndl = 0;
225
		dbuf = nil;
226
		while((n=dirread(fd, &dbuf)) > 0){
227
			for(i=0; i<n; i++){
228
				dl = emalloc(sizeof(Dirlist));
229
				j = strlen(dbuf[i].name);
230
				tmp = emalloc(j+1+1);
231
				memmove(tmp, dbuf[i].name, j);
232
				if(dbuf[i].qid.type & QTDIR)
233
					tmp[j++] = '/';
234
				tmp[j] = '\0';
235
				dl->r = bytetorune(tmp, &dl->nr);
236
				dl->wid = stringwidth(t->font, tmp);
237
				free(tmp);
238
				ndl++;
239
				dlp = realloc(dlp, ndl*sizeof(Dirlist*));
240
				dlp[ndl-1] = dl;
241
			}
242
			free(dbuf);
243
		}
244
		qsort(dlp, ndl, sizeof(Dirlist*), dircmp);
245
		t->w->dlp = dlp;
246
		t->w->ndl = ndl;
247
		textcolumnate(t, dlp, ndl);
248
		q1 = t->file->nc;
249
	}else{
250
		t->w->isdir = FALSE;
251
		t->w->filemenu = TRUE;
252
		q1 = q0 + fileload(t->file, q0, fd, &nulls);
253
	}
254
	if(setqid){
255
		t->file->dev = d->dev;
256
		t->file->mtime = d->mtime;
257
		t->file->qidpath = d->qid.path;
258
	}
259
	close(fd);
260
	rp = fbufalloc();
261
	for(q=q0; q<q1; q+=n){
262
		n = q1-q;
263
		if(n > RBUFSIZE)
264
			n = RBUFSIZE;
265
		bufread(t->file, q, rp, n);
266
		if(q < t->org)
267
			t->org += n;
268
		else if(q <= t->org+t->nchars)
269
			frinsert(t, rp, rp+n, q-t->org);
270
		if(t->lastlinefull)
271
			break;
272
	}
273
	fbuffree(rp);
274
	for(i=0; i<t->file->ntext; i++){
275
		u = t->file->text[i];
276
		if(u != t){
277
			if(u->org > u->file->nc)	/* will be 0 because of reset(), but safety first */
278
				u->org = 0;
279
			textresize(u, u->all);
280
			textbacknl(u, u->org, 0);	/* go to beginning of line */
281
		}
282
		textsetselect(u, q0, q0);
283
	}
284
	if(nulls)
285
		warning(nil, "%s: NUL bytes elided\n", file);
286
	free(d);
287
	return q1-q0;
288
 
289
    Rescue:
290
	close(fd);
291
	return 0;
292
}
293
 
294
uint
295
textbsinsert(Text *t, uint q0, Rune *r, uint n, int tofile, int *nrp)
296
{
297
	Rune *bp, *tp, *up;
298
	int i, initial;
299
 
300
	if(t->what == Tag){	/* can't happen but safety first: mustn't backspace over file name */
301
    Err:
302
		textinsert(t, q0, r, n, tofile);
303
		*nrp = n;
304
		return q0;
305
	}
306
	bp = r;
307
	for(i=0; i<n; i++)
308
		if(*bp++ == '\b'){
309
			--bp;
310
			initial = 0;
311
			tp = runemalloc(n);
312
			runemove(tp, r, i);
313
			up = tp+i;
314
			for(; i<n; i++){
315
				*up = *bp++;
316
				if(*up == '\b')
317
					if(up == tp)
318
						initial++;
319
					else
320
						--up;
321
				else
322
					up++;
323
			}
324
			if(initial){
325
				if(initial > q0)
326
					initial = q0;
327
				q0 -= initial;
328
				textdelete(t, q0, q0+initial, tofile);
329
			}
330
			n = up-tp;
331
			textinsert(t, q0, tp, n, tofile);
332
			free(tp);
333
			*nrp = n;
334
			return q0;
335
		}
336
	goto Err;
337
}
338
 
339
void
340
textinsert(Text *t, uint q0, Rune *r, uint n, int tofile)
341
{
342
	int c, i;
343
	Text *u;
344
 
345
	if(tofile && t->ncache != 0)
346
		error("text.insert");
347
	if(n == 0)
348
		return;
349
	if(tofile){
350
		fileinsert(t->file, q0, r, n);
351
		if(t->what == Body){
352
			t->w->dirty = TRUE;
353
			t->w->utflastqid = -1;
354
		}
355
		if(t->file->ntext > 1)
356
			for(i=0; i<t->file->ntext; i++){
357
				u = t->file->text[i];
358
				if(u != t){
359
					u->w->dirty = TRUE;	/* always a body */
360
					textinsert(u, q0, r, n, FALSE);
361
					textsetselect(u, u->q0, u->q1);
362
					textscrdraw(u);
363
				}
364
			}
365
 
366
	}
367
	if(q0 < t->q1)
368
		t->q1 += n;
369
	if(q0 < t->q0)
370
		t->q0 += n;
371
	if(q0 < t->org)
372
		t->org += n;
373
	else if(q0 <= t->org+t->nchars)
374
		frinsert(t, r, r+n, q0-t->org);
375
	if(t->w){
376
		c = 'i';
377
		if(t->what == Body)
378
			c = 'I';
379
		if(n <= EVENTSIZE)
380
			winevent(t->w, "%c%d %d 0 %d %.*S\n", c, q0, q0+n, n, n, r);
381
		else
382
			winevent(t->w, "%c%d %d 0 0 \n", c, q0, q0+n, n);
383
	}
384
}
385
 
386
void
387
typecommit(Text *t)
388
{
389
	if(t->w != nil)
390
		wincommit(t->w, t);
391
	else
392
		textcommit(t, TRUE);
393
}
394
 
395
void
396
textfill(Text *t)
397
{
398
	Rune *rp;
399
	int i, n, m, nl;
400
 
401
	if(t->lastlinefull || t->nofill)
402
		return;
403
	if(t->ncache > 0)
404
		typecommit(t);
405
	rp = fbufalloc();
406
	do{
407
		n = t->file->nc-(t->org+t->nchars);
408
		if(n == 0)
409
			break;
410
		if(n > 2000)	/* educated guess at reasonable amount */
411
			n = 2000;
412
		bufread(t->file, t->org+t->nchars, rp, n);
413
		/*
414
		 * it's expensive to frinsert more than we need, so
415
		 * count newlines.
416
		 */
417
		nl = t->maxlines-t->nlines;
418
		m = 0;
419
		for(i=0; i<n; ){
420
			if(rp[i++] == '\n'){
421
				m++;
422
				if(m >= nl)
423
					break;
424
			}
425
		}
426
		frinsert(t, rp, rp+i, t->nchars);
427
	}while(t->lastlinefull == FALSE);
428
	fbuffree(rp);
429
}
430
 
431
void
432
textdelete(Text *t, uint q0, uint q1, int tofile)
433
{
434
	uint n, p0, p1;
435
	int i, c;
436
	Text *u;
437
 
438
	if(tofile && t->ncache != 0)
439
		error("text.delete");
440
	n = q1-q0;
441
	if(n == 0)
442
		return;
443
	if(tofile){
444
		filedelete(t->file, q0, q1);
445
		if(t->what == Body){
446
			t->w->dirty = TRUE;
447
			t->w->utflastqid = -1;
448
		}
449
		if(t->file->ntext > 1)
450
			for(i=0; i<t->file->ntext; i++){
451
				u = t->file->text[i];
452
				if(u != t){
453
					u->w->dirty = TRUE;	/* always a body */
454
					textdelete(u, q0, q1, FALSE);
455
					textsetselect(u, u->q0, u->q1);
456
					textscrdraw(u);
457
				}
458
			}
459
	}
460
	if(q0 < t->q0)
461
		t->q0 -= min(n, t->q0-q0);
462
	if(q0 < t->q1)
463
		t->q1 -= min(n, t->q1-q0);
464
	if(q1 <= t->org)
465
		t->org -= n;
466
	else if(q0 < t->org+t->nchars){
467
		p1 = q1 - t->org;
468
		if(p1 > t->nchars)
469
			p1 = t->nchars;
470
		if(q0 < t->org){
471
			t->org = q0;
472
			p0 = 0;
473
		}else
474
			p0 = q0 - t->org;
475
		frdelete(t, p0, p1);
476
		textfill(t);
477
	}
478
	if(t->w){
479
		c = 'd';
480
		if(t->what == Body)
481
			c = 'D';
482
		winevent(t->w, "%c%d %d 0 0 \n", c, q0, q1);
483
	}
484
}
485
 
486
void
487
textconstrain(Text *t, uint q0, uint q1, uint *p0, uint *p1)
488
{
489
	*p0 = min(q0, t->file->nc);
490
	*p1 = min(q1, t->file->nc);
491
}
492
 
493
Rune
494
textreadc(Text *t, uint q)
495
{
496
	Rune r;
497
 
498
	if(t->cq0<=q && q<t->cq0+t->ncache)
499
		r = t->cache[q-t->cq0];
500
	else
501
		bufread(t->file, q, &r, 1);
502
	return r;
503
}
504
 
505
int
506
textbswidth(Text *t, Rune c)
507
{
508
	uint q, eq;
509
	Rune r;
510
	int skipping;
511
 
512
	/* there is known to be at least one character to erase */
513
	if(c == 0x08)	/* ^H: erase character */
514
		return 1;
515
	q = t->q0;
516
	skipping = TRUE;
517
	while(q > 0){
518
		r = textreadc(t, q-1);
519
		if(r == '\n'){		/* eat at most one more character */
520
			if(q == t->q0)	/* eat the newline */
521
				--q;
522
			break; 
523
		}
524
		if(c == 0x17){
525
			eq = isalnum(r);
526
			if(eq && skipping)	/* found one; stop skipping */
527
				skipping = FALSE;
528
			else if(!eq && !skipping)
529
				break;
530
		}
531
		--q;
532
	}
533
	return t->q0-q;
534
}
535
 
536
int
537
textfilewidth(Text *t, uint q0, int oneelement)
538
{
539
	uint q;
540
	Rune r;
541
 
542
	q = q0;
543
	while(q > 0){
544
		r = textreadc(t, q-1);
545
		if(r <= ' ')
546
			break;
547
		if(oneelement && r=='/')
548
			break;
549
		--q;
550
	}
551
	return q0-q;
552
}
553
 
554
Rune*
555
textcomplete(Text *t)
556
{
557
	int i, nstr, npath;
558
	uint q;
559
	Rune tmp[200];
560
	Rune *str, *path;
561
	Rune *rp;
562
	Completion *c;
563
	char *s, *dirs;
564
	Runestr dir;
565
 
566
	/* control-f: filename completion; works back to white space or / */
567
	if(t->q0<t->file->nc && textreadc(t, t->q0)>' ')	/* must be at end of word */
568
		return nil;
569
	nstr = textfilewidth(t, t->q0, TRUE);
570
	str = runemalloc(nstr);
571
	npath = textfilewidth(t, t->q0-nstr, FALSE);
572
	path = runemalloc(npath);
573
 
574
	c = nil;
575
	rp = nil;
576
	dirs = nil;
577
 
578
	q = t->q0-nstr;
579
	for(i=0; i<nstr; i++)
580
		str[i] = textreadc(t, q++);
581
	q = t->q0-nstr-npath;
582
	for(i=0; i<npath; i++)
583
		path[i] = textreadc(t, q++);
584
	/* is path rooted? if not, we need to make it relative to window path */
585
	if(npath>0 && path[0]=='/')
586
		dir = (Runestr){path, npath};
587
	else{
588
		dir = dirname(t, nil, 0);
589
		if(dir.nr + 1 + npath > nelem(tmp)){
590
			free(dir.r);
591
			goto Return;
592
		}
593
		if(dir.nr == 0){
594
			dir.nr = 1;
595
			dir.r = runestrdup(L".");
596
		}
597
		runemove(tmp, dir.r, dir.nr);
598
		tmp[dir.nr] = '/';
599
		runemove(tmp+dir.nr+1, path, npath);
600
		free(dir.r);
601
		dir.r = tmp;
602
		dir.nr += 1+npath;
603
		dir = cleanrname(dir);
604
	}
605
 
606
	s = smprint("%.*S", nstr, str);
607
	dirs = smprint("%.*S", dir.nr, dir.r);
608
	c = complete(dirs, s);
609
	free(s);
610
	if(c == nil){
611
		warning(nil, "error attempting completion: %r\n");
612
		goto Return;
613
	}
614
 
615
	if(!c->advance){
616
		warning(nil, "%.*S%s%.*S*%s\n",
617
			dir.nr, dir.r,
618
			dir.nr>0 && dir.r[dir.nr-1]!='/' ? "/" : "",
619
			nstr, str,
620
			c->nmatch? "" : ": no matches in:");
621
		for(i=0; i<c->nfile; i++)
622
			warning(nil, " %s\n", c->filename[i]);
623
	}
624
 
625
	if(c->advance)
626
		rp = runesmprint("%s", c->string);
627
	else
628
		rp = nil;
629
  Return:
630
	freecompletion(c);
631
	free(dirs);
632
	free(str);
633
	free(path);
634
	return rp;
635
}
636
 
637
void
638
texttype(Text *t, Rune r)
639
{
640
	uint q0, q1;
641
	int nnb, nb, n, i;
642
	int nr;
643
	Rune *rp;
644
	Text *u;
645
 
646
	if(t->what!=Body && r=='\n')
647
		return;
648
	nr = 1;
649
	rp = &r;
650
	switch(r){
651
	case Kleft:
652
		if(t->q0 > 0){
653
			typecommit(t);
654
			textshow(t, t->q0-1, t->q0-1, TRUE);
655
		}
656
		return;
657
	case Kright:
658
		if(t->q1 < t->file->nc){
659
			typecommit(t);
660
			textshow(t, t->q1+1, t->q1+1, TRUE);
661
		}
662
		return;
663
	case Kdown:
664
		n = t->maxlines/3;
665
		goto case_Down;
666
	case Kscrollonedown:
667
		n = mousescrollsize(t->maxlines);
668
		if(n <= 0)
669
			n = 1;
670
		goto case_Down;
671
	case Kpgdown:
672
		n = 2*t->maxlines/3;
673
	case_Down:
674
		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+n*t->font->height));
675
		textsetorigin(t, q0, TRUE);
676
		return;
677
	case Kup:
678
		n = t->maxlines/3;
679
		goto case_Up;
680
	case Kscrolloneup:
681
		n = mousescrollsize(t->maxlines);
682
		goto case_Up;
683
	case Kpgup:
684
		n = 2*t->maxlines/3;
685
	case_Up:
686
		q0 = textbacknl(t, t->org, n);
687
		textsetorigin(t, q0, TRUE);
688
		return;
689
	case Khome:
690
		typecommit(t);
691
		textshow(t, 0, 0, FALSE);
692
		return;
693
	case Kend:
694
		typecommit(t);
695
		textshow(t, t->file->nc, t->file->nc, FALSE);
696
		return;
697
	case 0x01:	/* ^A: beginning of line */
698
		typecommit(t);
699
		/* go to where ^U would erase, if not already at BOL */
700
		nnb = 0;
701
		if(t->q0>0 && textreadc(t, t->q0-1)!='\n')
702
			nnb = textbswidth(t, 0x15);
703
		textshow(t, t->q0-nnb, t->q0-nnb, TRUE);
704
		return;
705
	case 0x05:	/* ^E: end of line */
706
		typecommit(t);
707
		q0 = t->q0;
708
		while(q0<t->file->nc && textreadc(t, q0)!='\n')
709
			q0++;
710
		textshow(t, q0, q0, TRUE);
711
		return;
712
	}
713
	if(t->what == Body){
714
		seq++;
715
		filemark(t->file);
716
	}
717
	if(t->q1 > t->q0){
718
		if(t->ncache != 0)
719
			error("text.type");
720
		cut(t, t, nil, TRUE, TRUE, nil, 0);
721
		t->eq0 = ~0;
722
	}
723
	textshow(t, t->q0, t->q0, 1);
724
	switch(r){
725
	case 0x06:
726
	case Kins:
727
		rp = textcomplete(t);
728
		if(rp == nil)
729
			return;
730
		nr = runestrlen(rp);
731
		break;	/* fall through to normal insertion case */
732
	case 0x1B:
733
		if(t->eq0 != ~0)
734
			textsetselect(t, t->eq0, t->q0);
735
		if(t->ncache > 0)
736
			typecommit(t);
737
		return;
738
	case 0x08:	/* ^H: erase character */
739
	case 0x15:	/* ^U: erase line */
740
	case 0x17:	/* ^W: erase word */
741
		if(t->q0 == 0)	/* nothing to erase */
742
			return;
743
		nnb = textbswidth(t, r);
744
		q1 = t->q0;
745
		q0 = q1-nnb;
746
		/* if selection is at beginning of window, avoid deleting invisible text */
747
		if(q0 < t->org){
748
			q0 = t->org;
749
			nnb = q1-q0;
750
		}
751
		if(nnb <= 0)
752
			return;
753
		for(i=0; i<t->file->ntext; i++){
754
			u = t->file->text[i];
755
			u->nofill = TRUE;
756
			nb = nnb;
757
			n = u->ncache;
758
			if(n > 0){
759
				if(q1 != u->cq0+n)
760
					error("text.type backspace");
761
				if(n > nb)
762
					n = nb;
763
				u->ncache -= n;
764
				textdelete(u, q1-n, q1, FALSE);
765
				nb -= n;
766
			}
767
			if(u->eq0==q1 || u->eq0==~0)
768
				u->eq0 = q0;
769
			if(nb && u==t)
770
				textdelete(u, q0, q0+nb, TRUE);
771
			if(u != t)
772
				textsetselect(u, u->q0, u->q1);
773
			else
774
				textsetselect(t, q0, q0);
775
			u->nofill = FALSE;
776
		}
777
		for(i=0; i<t->file->ntext; i++)
778
			textfill(t->file->text[i]);
779
		return;
780
	case '\n':
781
		if(t->w->autoindent){
782
			/* find beginning of previous line using backspace code */
783
			nnb = textbswidth(t, 0x15); /* ^U case */
784
			rp = runemalloc(nnb + 1);
785
			nr = 0;
786
			rp[nr++] = r;
787
			for(i=0; i<nnb; i++){
788
				r = textreadc(t, t->q0-nnb+i);
789
				if(r != ' ' && r != '\t')
790
					break;
791
				rp[nr++] = r;
792
			}
793
		}
794
		break; /* fall through to normal code */
795
	}
796
	/* otherwise ordinary character; just insert, typically in caches of all texts */
797
	for(i=0; i<t->file->ntext; i++){
798
		u = t->file->text[i];
799
		if(u->eq0 == ~0)
800
			u->eq0 = t->q0;
801
		if(u->ncache == 0)
802
			u->cq0 = t->q0;
803
		else if(t->q0 != u->cq0+u->ncache)
804
			error("text.type cq1");
805
		textinsert(u, t->q0, rp, nr, FALSE);
806
		if(u != t)
807
			textsetselect(u, u->q0, u->q1);
808
		if(u->ncache+nr > u->ncachealloc){
809
			u->ncachealloc += 10 + nr;
810
			u->cache = runerealloc(u->cache, u->ncachealloc);
811
		}
812
		runemove(u->cache+u->ncache, rp, nr);
813
		u->ncache += nr;
814
	}
815
	if(rp != &r)
816
		free(rp);
817
	textsetselect(t, t->q0+nr, t->q0+nr);
818
	if(r=='\n' && t->w!=nil)
819
		wincommit(t->w, t);
820
}
821
 
822
void
823
textcommit(Text *t, int tofile)
824
{
825
	if(t->ncache == 0)
826
		return;
827
	if(tofile)
828
		fileinsert(t->file, t->cq0, t->cache, t->ncache);
829
	if(t->what == Body){
830
		t->w->dirty = TRUE;
831
		t->w->utflastqid = -1;
832
	}
833
	t->ncache = 0;
834
}
835
 
836
static	Text	*clicktext;
837
static	uint	clickmsec;
838
static	Text	*selecttext;
839
static	uint	selectq;
840
 
841
/*
842
 * called from frame library
843
 */
844
void
845
framescroll(Frame *f, int dl)
846
{
847
	if(f != &selecttext->Frame)
848
		error("frameselect not right frame");
849
	textframescroll(selecttext, dl);
850
}
851
 
852
void
853
textframescroll(Text *t, int dl)
854
{
855
	uint q0;
856
 
857
	if(dl == 0){
858
		scrsleep(100);
859
		return;
860
	}
861
	if(dl < 0){
862
		q0 = textbacknl(t, t->org, -dl);
863
		if(selectq > t->org+t->p0)
864
			textsetselect(t, t->org+t->p0, selectq);
865
		else
866
			textsetselect(t, selectq, t->org+t->p0);
867
	}else{
868
		if(t->org+t->nchars == t->file->nc)
869
			return;
870
		q0 = t->org+frcharofpt(t, Pt(t->r.min.x, t->r.min.y+dl*t->font->height));
871
		if(selectq > t->org+t->p1)
872
			textsetselect(t, t->org+t->p1, selectq);
873
		else
874
			textsetselect(t, selectq, t->org+t->p1);
875
	}
876
	textsetorigin(t, q0, TRUE);
877
}
878
 
879
 
880
void
881
textselect(Text *t)
882
{
883
	uint q0, q1;
884
	int b, x, y;
885
	int state;
886
 
887
	selecttext = t;
888
	/*
889
	 * To have double-clicking and chording, we double-click
890
	 * immediately if it might make sense.
891
	 */
892
	b = mouse->buttons;
893
	q0 = t->q0;
894
	q1 = t->q1;
895
	selectq = t->org+frcharofpt(t, mouse->xy);
896
	if(clicktext==t && mouse->msec-clickmsec<500)
897
	if(q0==q1 && selectq==q0){
898
		textdoubleclick(t, &q0, &q1);
899
		textsetselect(t, q0, q1);
900
		flushimage(display, 1);
901
		x = mouse->xy.x;
902
		y = mouse->xy.y;
903
		/* stay here until something interesting happens */
904
		do
905
			readmouse(mousectl);
906
		while(mouse->buttons==b && abs(mouse->xy.x-x)<3 && abs(mouse->xy.y-y)<3);
907
		mouse->xy.x = x;	/* in case we're calling frselect */
908
		mouse->xy.y = y;
909
		q0 = t->q0;	/* may have changed */
910
		q1 = t->q1;
911
		selectq = q0;
912
	}
913
	if(mouse->buttons == b){
914
		t->Frame.scroll = framescroll;
915
		frselect(t, mousectl);
916
		/* horrible botch: while asleep, may have lost selection altogether */
917
		if(selectq > t->file->nc)
918
			selectq = t->org + t->p0;
919
		t->Frame.scroll = nil;
920
		if(selectq < t->org)
921
			q0 = selectq;
922
		else
923
			q0 = t->org + t->p0;
924
		if(selectq > t->org+t->nchars)
925
			q1 = selectq;
926
		else
927
			q1 = t->org+t->p1;
928
	}
929
	if(q0 == q1){
930
		if(q0==t->q0 && clicktext==t && mouse->msec-clickmsec<500){
931
			textdoubleclick(t, &q0, &q1);
932
			clicktext = nil;
933
		}else{
934
			clicktext = t;
935
			clickmsec = mouse->msec;
936
		}
937
	}else
938
		clicktext = nil;
939
	textsetselect(t, q0, q1);
940
	flushimage(display, 1);
941
	state = 0;	/* undo when possible; +1 for cut, -1 for paste */
942
	while(mouse->buttons){
943
		mouse->msec = 0;
944
		b = mouse->buttons;
945
		if((b&1) && (b&6)){
946
			if(state==0 && t->what==Body){
947
				seq++;
948
				filemark(t->w->body.file);
949
			}
950
			if(b & 2){
951
				if(state==-1 && t->what==Body){
952
					winundo(t->w, TRUE);
953
					textsetselect(t, q0, t->q0);
954
					state = 0;
955
				}else if(state != 1){
956
					cut(t, t, nil, TRUE, TRUE, nil, 0);
957
					state = 1;
958
				}
959
			}else{
960
				if(state==1 && t->what==Body){
961
					winundo(t->w, TRUE);
962
					textsetselect(t, q0, t->q1);
963
					state = 0;
964
				}else if(state != -1){
965
					paste(t, t, nil, TRUE, FALSE, nil, 0);
966
					state = -1;
967
				}
968
			}
969
			textscrdraw(t);
970
			clearmouse();
971
		}
972
		flushimage(display, 1);
973
		while(mouse->buttons == b)
974
			readmouse(mousectl);
975
		clicktext = nil;
976
	}
977
}
978
 
979
void
980
textshow(Text *t, uint q0, uint q1, int doselect)
981
{
982
	int qe;
983
	int nl;
984
	uint q;
985
 
986
	if(t->what != Body){
987
		if(doselect)
988
			textsetselect(t, q0, q1);
989
		return;
990
	}
991
	if(t->w!=nil && t->maxlines==0)
992
		colgrow(t->col, t->w, 1);
993
	if(doselect)
994
		textsetselect(t, q0, q1);
995
	qe = t->org+t->nchars;
996
	if(t->org<=q0 && (q0<qe || (q0==qe && qe==t->file->nc+t->ncache)))
997
		textscrdraw(t);
998
	else{
999
		if(t->w->nopen[QWevent] > 0)
1000
			nl = 3*t->maxlines/4;
1001
		else
1002
			nl = t->maxlines/4;
1003
		q = textbacknl(t, q0, nl);
1004
		/* avoid going backwards if trying to go forwards - long lines! */
1005
		if(!(q0>t->org && q<t->org))
1006
			textsetorigin(t, q, TRUE);
1007
		while(q0 > t->org+t->nchars)
1008
			textsetorigin(t, t->org+1, FALSE);
1009
	}
1010
}
1011
 
1012
static
1013
int
1014
region(int a, int b)
1015
{
1016
	if(a < b)
1017
		return -1;
1018
	if(a == b)
1019
		return 0;
1020
	return 1;
1021
}
1022
 
1023
void
1024
selrestore(Frame *f, Point pt0, uint p0, uint p1)
1025
{
1026
	if(p1<=f->p0 || p0>=f->p1){
1027
		/* no overlap */
1028
		frdrawsel0(f, pt0, p0, p1, f->cols[BACK], f->cols[TEXT]);
1029
		return;
1030
	}
1031
	if(p0>=f->p0 && p1<=f->p1){
1032
		/* entirely inside */
1033
		frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1034
		return;
1035
	}
1036
 
1037
	/* they now are known to overlap */
1038
 
1039
	/* before selection */
1040
	if(p0 < f->p0){
1041
		frdrawsel0(f, pt0, p0, f->p0, f->cols[BACK], f->cols[TEXT]);
1042
		p0 = f->p0;
1043
		pt0 = frptofchar(f, p0);
1044
	}
1045
	/* after selection */
1046
	if(p1 > f->p1){
1047
		frdrawsel0(f, frptofchar(f, f->p1), f->p1, p1, f->cols[BACK], f->cols[TEXT]);
1048
		p1 = f->p1;
1049
	}
1050
	/* inside selection */
1051
	frdrawsel0(f, pt0, p0, p1, f->cols[HIGH], f->cols[HTEXT]);
1052
}
1053
 
1054
void
1055
textsetselect(Text *t, uint q0, uint q1)
1056
{
1057
	int p0, p1;
1058
 
1059
	/* t->p0 and t->p1 are always right; t->q0 and t->q1 may be off */
1060
	t->q0 = q0;
1061
	t->q1 = q1;
1062
	/* compute desired p0,p1 from q0,q1 */
1063
	p0 = q0-t->org;
1064
	p1 = q1-t->org;
1065
	if(p0 < 0)
1066
		p0 = 0;
1067
	if(p1 < 0)
1068
		p1 = 0;
1069
	if(p0 > t->nchars)
1070
		p0 = t->nchars;
1071
	if(p1 > t->nchars)
1072
		p1 = t->nchars;
1073
	if(p0==t->p0 && p1==t->p1)
1074
		return;
1075
	/* screen disagrees with desired selection */
1076
	if(t->p1<=p0 || p1<=t->p0 || p0==p1 || t->p1==t->p0){
1077
		/* no overlap or too easy to bother trying */
1078
		frdrawsel(t, frptofchar(t, t->p0), t->p0, t->p1, 0);
1079
		frdrawsel(t, frptofchar(t, p0), p0, p1, 1);
1080
		goto Return;
1081
	}
1082
	/* overlap; avoid unnecessary painting */
1083
	if(p0 < t->p0){
1084
		/* extend selection backwards */
1085
		frdrawsel(t, frptofchar(t, p0), p0, t->p0, 1);
1086
	}else if(p0 > t->p0){
1087
		/* trim first part of selection */
1088
		frdrawsel(t, frptofchar(t, t->p0), t->p0, p0, 0);
1089
	}
1090
	if(p1 > t->p1){
1091
		/* extend selection forwards */
1092
		frdrawsel(t, frptofchar(t, t->p1), t->p1, p1, 1);
1093
	}else if(p1 < t->p1){
1094
		/* trim last part of selection */
1095
		frdrawsel(t, frptofchar(t, p1), p1, t->p1, 0);
1096
	}
1097
 
1098
    Return:
1099
	t->p0 = p0;
1100
	t->p1 = p1;
1101
}
1102
 
1103
/*
1104
 * Release the button in less than DELAY ms and it's considered a null selection
1105
 * if the mouse hardly moved, regardless of whether it crossed a char boundary.
1106
 */
1107
enum {
1108
	DELAY = 2,
1109
	MINMOVE = 4,
1110
};
1111
 
1112
uint
1113
xselect(Frame *f, Mousectl *mc, Image *col, uint *p1p)	/* when called, button is down */
1114
{
1115
	uint p0, p1, q, tmp;
1116
	ulong msec;
1117
	Point mp, pt0, pt1, qt;
1118
	int reg, b;
1119
 
1120
	mp = mc->xy;
1121
	b = mc->buttons;
1122
	msec = mc->msec;
1123
 
1124
	/* remove tick */
1125
	if(f->p0 == f->p1)
1126
		frtick(f, frptofchar(f, f->p0), 0);
1127
	p0 = p1 = frcharofpt(f, mp);
1128
	pt0 = frptofchar(f, p0);
1129
	pt1 = frptofchar(f, p1);
1130
	reg = 0;
1131
	frtick(f, pt0, 1);
1132
	do{
1133
		q = frcharofpt(f, mc->xy);
1134
		if(p1 != q){
1135
			if(p0 == p1)
1136
				frtick(f, pt0, 0);
1137
			if(reg != region(q, p0)){	/* crossed starting point; reset */
1138
				if(reg > 0)
1139
					selrestore(f, pt0, p0, p1);
1140
				else if(reg < 0)
1141
					selrestore(f, pt1, p1, p0);
1142
				p1 = p0;
1143
				pt1 = pt0;
1144
				reg = region(q, p0);
1145
				if(reg == 0)
1146
					frdrawsel0(f, pt0, p0, p1, col, display->white);
1147
			}
1148
			qt = frptofchar(f, q);
1149
			if(reg > 0){
1150
				if(q > p1)
1151
					frdrawsel0(f, pt1, p1, q, col, display->white);
1152
 
1153
				else if(q < p1)
1154
					selrestore(f, qt, q, p1);
1155
			}else if(reg < 0){
1156
				if(q > p1)
1157
					selrestore(f, pt1, p1, q);
1158
				else
1159
					frdrawsel0(f, qt, q, p1, col, display->white);
1160
			}
1161
			p1 = q;
1162
			pt1 = qt;
1163
		}
1164
		if(p0 == p1)
1165
			frtick(f, pt0, 1);
1166
		flushimage(f->display, 1);
1167
		readmouse(mc);
1168
	}while(mc->buttons == b);
1169
	if(mc->msec-msec < DELAY && p0!=p1
1170
	&& abs(mp.x-mc->xy.x)<MINMOVE
1171
	&& abs(mp.y-mc->xy.y)<MINMOVE) {
1172
		if(reg > 0)
1173
			selrestore(f, pt0, p0, p1);
1174
		else if(reg < 0)
1175
			selrestore(f, pt1, p1, p0);
1176
		p1 = p0;
1177
	}
1178
	if(p1 < p0){
1179
		tmp = p0;
1180
		p0 = p1;
1181
		p1 = tmp;
1182
	}
1183
	pt0 = frptofchar(f, p0);
1184
	if(p0 == p1)
1185
		frtick(f, pt0, 0);
1186
	selrestore(f, pt0, p0, p1);
1187
	/* restore tick */
1188
	if(f->p0 == f->p1)
1189
		frtick(f, frptofchar(f, f->p0), 1);
1190
	flushimage(f->display, 1);
1191
	*p1p = p1;
1192
	return p0;
1193
}
1194
 
1195
int
1196
textselect23(Text *t, uint *q0, uint *q1, Image *high, int mask)
1197
{
1198
	uint p0, p1;
1199
	int buts;
1200
 
1201
	p0 = xselect(t, mousectl, high, &p1);
1202
	buts = mousectl->buttons;
1203
	if((buts & mask) == 0){
1204
		*q0 = p0+t->org;
1205
		*q1 = p1+t->org;
1206
	}
1207
 
1208
	while(mousectl->buttons)
1209
		readmouse(mousectl);
1210
	return buts;
1211
}
1212
 
1213
int
1214
textselect2(Text *t, uint *q0, uint *q1, Text **tp)
1215
{
1216
	int buts;
1217
 
1218
	*tp = nil;
1219
	buts = textselect23(t, q0, q1, but2col, 4);
1220
	if(buts & 4)
1221
		return 0;
1222
	if(buts & 1){	/* pick up argument */
1223
		*tp = argtext;
1224
		return 1;
1225
	}
1226
	return 1;
1227
}
1228
 
1229
int
1230
textselect3(Text *t, uint *q0, uint *q1)
1231
{
1232
	int h;
1233
 
1234
	h = (textselect23(t, q0, q1, but3col, 1|2) == 0);
1235
	return h;
1236
}
1237
 
1238
static Rune left1[] =  { L'{', L'[', L'(', L'<', L'«', 0 };
1239
static Rune right1[] = { L'}', L']', L')', L'>', L'»', 0 };
1240
static Rune left2[] =  { L'\n', 0 };
1241
static Rune left3[] =  { L'\'', L'"', L'`', 0 };
1242
 
1243
static
1244
Rune *left[] = {
1245
	left1,
1246
	left2,
1247
	left3,
1248
	nil
1249
};
1250
static
1251
Rune *right[] = {
1252
	right1,
1253
	left2,
1254
	left3,
1255
	nil
1256
};
1257
 
1258
void
1259
textdoubleclick(Text *t, uint *q0, uint *q1)
1260
{
1261
	int c, i;
1262
	Rune *r, *l, *p;
1263
	uint q;
1264
 
1265
	for(i=0; left[i]!=nil; i++){
1266
		q = *q0;
1267
		l = left[i];
1268
		r = right[i];
1269
		/* try matching character to left, looking right */
1270
		if(q == 0)
1271
			c = '\n';
1272
		else
1273
			c = textreadc(t, q-1);
1274
		p = runestrchr(l, c);
1275
		if(p != nil){
1276
			if(textclickmatch(t, c, r[p-l], 1, &q))
1277
				*q1 = q-(c!='\n');
1278
			return;
1279
		}
1280
		/* try matching character to right, looking left */
1281
		if(q == t->file->nc)
1282
			c = '\n';
1283
		else
1284
			c = textreadc(t, q);
1285
		p = runestrchr(r, c);
1286
		if(p != nil){
1287
			if(textclickmatch(t, c, l[p-r], -1, &q)){
1288
				*q1 = *q0+(*q0<t->file->nc && c=='\n');
1289
				*q0 = q;
1290
				if(c!='\n' || q!=0 || textreadc(t, 0)=='\n')
1291
					(*q0)++;
1292
			}
1293
			return;
1294
		}
1295
	}
1296
	/* try filling out word to right */
1297
	while(*q1<t->file->nc && isalnum(textreadc(t, *q1)))
1298
		(*q1)++;
1299
	/* try filling out word to left */
1300
	while(*q0>0 && isalnum(textreadc(t, *q0-1)))
1301
		(*q0)--;
1302
}
1303
 
1304
int
1305
textclickmatch(Text *t, int cl, int cr, int dir, uint *q)
1306
{
1307
	Rune c;
1308
	int nest;
1309
 
1310
	nest = 1;
1311
	for(;;){
1312
		if(dir > 0){
1313
			if(*q == t->file->nc)
1314
				break;
1315
			c = textreadc(t, *q);
1316
			(*q)++;
1317
		}else{
1318
			if(*q == 0)
1319
				break;
1320
			(*q)--;
1321
			c = textreadc(t, *q);
1322
		}
1323
		if(c == cr){
1324
			if(--nest==0)
1325
				return 1;
1326
		}else if(c == cl)
1327
			nest++;
1328
	}
1329
	return cl=='\n' && nest==1;
1330
}
1331
 
1332
uint
1333
textbacknl(Text *t, uint p, uint n)
1334
{
1335
	int i, j;
1336
 
1337
	/* look for start of this line if n==0 */
1338
	if(n==0 && p>0 && textreadc(t, p-1)!='\n')
1339
		n = 1;
1340
	i = n;
1341
	while(i-->0 && p>0){
1342
		--p;	/* it's at a newline now; back over it */
1343
		if(p == 0)
1344
			break;
1345
		/* at 128 chars, call it a line anyway */
1346
		for(j=128; --j>0 && p>0; p--)
1347
			if(textreadc(t, p-1)=='\n')
1348
				break;
1349
	}
1350
	return p;
1351
}
1352
 
1353
void
1354
textsetorigin(Text *t, uint org, int exact)
1355
{
1356
	int i, a, fixup;
1357
	Rune *r;
1358
	uint n;
1359
 
1360
	if(org>0 && !exact){
1361
		/* org is an estimate of the char posn; find a newline */
1362
		/* don't try harder than 256 chars */
1363
		for(i=0; i<256 && org<t->file->nc; i++){
1364
			if(textreadc(t, org) == '\n'){
1365
				org++;
1366
				break;
1367
			}
1368
			org++;
1369
		}
1370
	}
1371
	a = org-t->org;
1372
	fixup = 0;
1373
	if(a>=0 && a<t->nchars){
1374
		frdelete(t, 0, a);
1375
		fixup = 1;	/* frdelete can leave end of last line in wrong selection mode; it doesn't know what follows */
1376
	}
1377
	else if(a<0 && -a<t->nchars){
1378
		n = t->org - org;
1379
		r = runemalloc(n);
1380
		bufread(t->file, org, r, n);
1381
		frinsert(t, r, r+n, 0);
1382
		free(r);
1383
	}else
1384
		frdelete(t, 0, t->nchars);
1385
	t->org = org;
1386
	textfill(t);
1387
	textscrdraw(t);
1388
	textsetselect(t, t->q0, t->q1);
1389
	if(fixup && t->p1 > t->p0)
1390
		frdrawsel(t, frptofchar(t, t->p1-1), t->p1-1, t->p1, 1);
1391
}
1392
 
1393
void
1394
textreset(Text *t)
1395
{
1396
	t->file->seq = 0;
1397
	t->eq0 = ~0;
1398
	/* do t->delete(0, t->nc, TRUE) without building backup stuff */
1399
	textsetselect(t, t->org, t->org);
1400
	frdelete(t, 0, t->nchars);
1401
	t->org = 0;
1402
	t->q0 = 0;
1403
	t->q1 = 0;
1404
	filereset(t->file);
1405
	bufreset(t->file);
1406
}