xref: /inferno-os/appl/acme/text.b (revision 546783184a373a2dd59afef3affe029a6aedd701)
1implement Textm;
2
3include "common.m";
4include "keyboard.m";
5
6sys : Sys;
7utils : Utils;
8framem : Framem;
9drawm : Draw;
10acme : Acme;
11graph : Graph;
12gui : Gui;
13dat : Dat;
14scrl : Scroll;
15bufferm : Bufferm;
16filem : Filem;
17columnm : Columnm;
18windowm : Windowm;
19exec : Exec;
20
21Dir, sprint : import sys;
22frgetmouse : import acme;
23min, warning, error, stralloc, strfree, isalnum : import utils;
24Frame, frinsert, frdelete, frptofchar, frcharofpt, frselect, frdrawsel, frdrawsel0, frtick : import framem;
25BUFSIZE, Astring, SZINT, TRUE, FALSE, XXX, Reffont, Dirlist,Scrollwid, Scrollgap, seq, mouse : import dat;
26EM_NORMAL, EM_RAW, EM_MASK : import dat;
27ALPHA_LATIN, ALPHA_GREEK, ALPHA_CYRILLIC: import Dat;
28BACK, TEXT, HIGH, HTEXT : import Framem;
29Flushon, Flushoff : import Draw;
30Point, Display, Rect, Image : import drawm;
31charwidth, bflush, draw : import graph;
32black, white, mainwin, display : import gui;
33Buffer : import bufferm;
34File : import filem;
35Column : import columnm;
36Window : import windowm;
37scrdraw : import scrl;
38
39cvlist: adt {
40	ld: int;
41	nm: string;
42	si: string;
43	so: string;
44};
45
46#	"@@",  "'EKSTYZekstyz   ",	"ьЕКСТЫЗекстызъЁё",
47
48latintab := array[] of {
49	cvlist(
50		ALPHA_LATIN,
51		"latin",
52		nil,
53		nil
54	),
55	cvlist(
56		ALPHA_GREEK,
57		"greek",
58		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
59		"ΑΒΞΔΕΦΓΘΙΪΚΛΜΝΟΠΨΡΣΤΥΫΩΧΗΖαβξδεφγθιϊκλμνοπψρστυϋωχηζ"
60	),
61	cvlist(
62		ALPHA_CYRILLIC,
63		"cyrillic",
64		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
65		"АБЧДЭФГШИЙХЛМНОПЕРЩЦУВЮХЯЖабчдэфгшийхлмноперщцувюхяж"
66	),
67	cvlist(-1, nil, nil, nil)
68};
69
70alphabet := ALPHA_LATIN;	# per window perhaps
71
72setalphabet(s: string)
73{
74	for(a := 0; latintab[a].ld != -1; a++){
75		k := latintab[a].ld;
76		for(i := 0; latintab[i].ld != -1; i++){
77			if(s == transs(latintab[i].nm, k)){
78				alphabet = latintab[i].ld;
79				return;
80			}
81		}
82	}
83}
84
85transc(c: int, k: int): int
86{
87	for(i := 0; latintab[i].ld != -1; i++){
88		if(k == latintab[i].ld){
89			si := latintab[i].si;
90			so := latintab[i].so;
91			ln := len si;
92			for(j := 0; j < ln; j++)
93				if(c == si[j])
94					return so[j];
95		}
96	}
97	return c;
98}
99
100transs(s: string, k: int): string
101{
102	ln := len s;
103	for(i := 0; i < ln; i++)
104		s[i] = transc(s[i], k);
105	return s;
106}
107
108init(mods : ref Dat->Mods)
109{
110	sys = mods.sys;
111	framem = mods.framem;
112	dat = mods.dat;
113	utils = mods.utils;
114	drawm = mods.draw;
115	acme = mods.acme;
116	graph = mods.graph;
117	gui = mods.gui;
118	scrl = mods.scroll;
119	bufferm = mods.bufferm;
120	filem = mods.filem;
121	columnm = mods.columnm;
122	windowm = mods.windowm;
123	exec = mods.exec;
124}
125
126TABDIR : con 3;	# width of tabs in directory windows
127
128# remove eventually
129KF : con 16rF000;
130Kup : con KF | 16r0E;
131Kleft : con KF | 16r11;
132Kright : con KF | 16r12;
133Kend : con KF | 16r18;
134Kdown : con 16r80;
135
136
137nulltext : Text;
138
139newtext() : ref Text
140{
141	t := ref nulltext;
142	t.frame = framem->newframe();
143	return t;
144}
145
146Text.init(t : self ref Text, f : ref File, r : Rect, rf : ref Dat->Reffont, cols : array of ref Image)
147{
148	t.file = f;
149	t.all = r;
150	t.scrollr = r;
151	t.scrollr.max.x = r.min.x+Scrollwid;
152	t.lastsr = dat->nullrect;
153	r.min.x += Scrollwid+Scrollgap;
154	t.eq0 = ~0;
155	t.ncache = 0;
156	t.reffont = rf;
157	t.tabstop = dat->maxtab;
158	for(i:=0; i<Framem->NCOL; i++)
159		t.frame.cols[i] = cols[i];
160	t.redraw(r, rf.f, mainwin, -1);
161}
162
163Text.redraw(t : self ref Text, r : Rect, f : ref Draw->Font, b : ref Image, odx : int)
164{
165	framem->frinit(t.frame, r, f, b, t.frame.cols);
166	rr := t.frame.r;
167	rr.min.x -= Scrollwid;	# back fill to scroll bar
168	draw(t.frame.b, rr, t.frame.cols[Framem->BACK], nil, (0, 0));
169	# use no wider than 3-space tabs in a directory
170	maxt := dat->maxtab;
171	if(t.what == Body){
172		if(t.w != nil && t.w.isdir)
173			maxt = min(TABDIR, dat->maxtab);
174		else
175			maxt = t.tabstop;
176	}
177	t.frame.maxtab = maxt*charwidth(f, '0');
178	# c = '0';
179	# if(t.what==Body && t.w!=nil && t.w.isdir)
180	#	c = ' ';
181	# t.frame.maxtab = Dat->Maxtab*charwidth(f, c);
182	if(t.what==Body && t.w.isdir && odx!=t.all.dx()){
183		if(t.frame.maxlines > 0){
184			t.reset();
185			t.columnate(t.w.dlp,  t.w.ndl);
186			t.show(0, 0, TRUE);
187		}
188	}else{
189		t.fill();
190		t.setselect(t.q0, t.q1);
191	}
192}
193
194Text.reshape(t : self ref Text, r : Rect) : int
195{
196	odx : int;
197
198	if(r.dy() > 0)
199		r.max.y -= r.dy()%t.frame.font.height;
200	else
201		r.max.y = r.min.y;
202	odx = t.all.dx();
203	t.all = r;
204	t.scrollr = r;
205	t.scrollr.max.x = r.min.x+Scrollwid;
206	t.lastsr = dat->nullrect;
207	r.min.x += Scrollwid+Scrollgap;
208	framem->frclear(t.frame, 0);
209	# t.redraw(r, t.frame.font, t.frame.b, odx);
210	t.redraw(r, t.frame.font, mainwin, odx);
211	return r.max.y;
212}
213
214Text.close(t : self ref Text)
215{
216	t.cache = nil;
217	framem->frclear(t.frame, 1);
218	t.file.deltext(t);
219	t.file = nil;
220	t.reffont.close();
221	if(dat->argtext == t)
222		dat->argtext = nil;
223	if(dat->typetext == t)
224		dat->typetext = nil;
225	if(dat->seltext == t)
226		dat->seltext = nil;
227	if(dat->mousetext == t)
228		dat->mousetext = nil;
229	if(dat->barttext == t)
230		dat->barttext = nil;
231}
232
233dircmp(da : ref Dirlist, db : ref Dirlist) : int
234{
235	if (da.r < db.r)
236		return -1;
237	if (da.r > db.r)
238		return 1;
239	return 0;
240}
241
242qsort(a : array of ref Dirlist, n : int)
243{
244	i, j : int;
245	t : ref Dirlist;
246
247	while(n > 1) {
248		i = n>>1;
249		t = a[0]; a[0] = a[i]; a[i] = t;
250		i = 0;
251		j = n;
252		for(;;) {
253			do
254				i++;
255			while(i < n && dircmp(a[i], a[0]) < 0);
256			do
257				j--;
258			while(j > 0 && dircmp(a[j], a[0]) > 0);
259			if(j < i)
260				break;
261			t = a[i]; a[i] = a[j]; a[j] = t;
262		}
263		t = a[0]; a[0] = a[j]; a[j] = t;
264		n = n-j-1;
265		if(j >= n) {
266			qsort(a, j);
267			a = a[j+1:];
268		} else {
269			qsort(a[j+1:], n);
270			n = j;
271		}
272	}
273}
274
275Text.columnate(t : self ref Text, dlp : array of ref Dirlist, ndl : int)
276{
277	i, j, w, colw, mint, maxt, ncol, nrow : int;
278	dl : ref Dirlist;
279	q1 : int;
280
281	if(t.file.ntext > 1)
282		return;
283	mint = charwidth(t.frame.font, '0');
284	# go for narrower tabs if set more than 3 wide
285	t.frame.maxtab = min(dat->maxtab, TABDIR)*mint;
286	maxt = t.frame.maxtab;
287	colw = 0;
288	for(i=0; i<ndl; i++){
289		dl = dlp[i];
290		w = dl.wid;
291		if(maxt-w%maxt < mint)
292			w += mint;
293		if(w % maxt)
294			w += maxt-(w%maxt);
295		if(w > colw)
296			colw = w;
297	}
298	if(colw == 0)
299		ncol = 1;
300	else
301		ncol = utils->max(1, t.frame.r.dx()/colw);
302	nrow = (ndl+ncol-1)/ncol;
303
304	q1 = 0;
305	for(i=0; i<nrow; i++){
306		for(j=i; j<ndl; j+=nrow){
307			dl = dlp[j];
308			t.file.insert(q1, dl.r, len dl.r);
309			q1 += len dl.r;
310			if(j+nrow >= ndl)
311				break;
312			w = dl.wid;
313			if(maxt-w%maxt < mint){
314				t.file.insert(q1, "\t", 1);
315				q1++;
316				w += mint;
317			}
318			do{
319				t.file.insert(q1, "\t", 1);
320				q1++;
321				w += maxt-(w%maxt);
322			}while(w < colw);
323		}
324		t.file.insert(q1, "\n", 1);
325		q1++;
326	}
327}
328
329Text.loadx(t : self ref Text, q0 : int, file : string, setqid : int) : int
330{
331	rp : ref Astring;
332	dl : ref Dirlist;
333	dlp : array of ref Dirlist;
334	i, n, ndl : int;
335	fd : ref Sys->FD;
336	q, q1 : int;
337	d : Dir;
338	u : ref Text;
339	ok : int;
340
341	if(t.ncache!=0 || t.file.buf.nc || t.w==nil || t!=t.w.body || (t.w.isdir && t.file.name==nil))
342		error("text.load");
343
344	{
345		fd = sys->open(file, Sys->OREAD);
346		if(fd == nil){
347			warning(nil, sprint("can't open %s: %r\n", file));
348			raise "e";
349		}
350		(ok, d) = sys->fstat(fd);
351		if(ok){
352			warning(nil, sprint("can't fstat %s: %r\n", file));
353			raise "e";
354		}
355		if(d.qid.qtype & Sys->QTDIR){
356			# this is checked in get() but it's possible the file changed underfoot
357			if(t.file.ntext > 1){
358				warning(nil, sprint("%s is a directory; can't read with multiple windows on it\n", file));
359				raise "e";
360			}
361			t.w.isdir = TRUE;
362			t.w.filemenu = FALSE;
363			if(t.file.name[len t.file.name-1] != '/')
364				t.w.setname(t.file.name + "/", len t.file.name+1);
365			dlp = nil;
366			ndl = 0;
367			for(;;){
368				(nd, dbuf) := sys->dirread(fd);
369				if(nd <= 0)
370					break;
371				for(i=0; i<nd; i++){
372					dl = ref Dirlist;
373					dl.r = dbuf[i].name;
374					if(dbuf[i].mode & Sys->DMDIR)
375						dl.r = dl.r + "/";
376					dl.wid = graph->strwidth(t.frame.font, dl.r);
377					ndl++;
378					odlp := dlp;
379					dlp = array[ndl] of ref Dirlist;
380					dlp[0:] = odlp[0:ndl-1];
381					odlp = nil;
382					dlp[ndl-1] = dl;
383				}
384			}
385			qsort(dlp, ndl);
386			t.w.dlp = dlp;
387			t.w.ndl = ndl;
388			t.columnate(dlp, ndl);
389			q1 = t.file.buf.nc;
390		}else{
391			tmp : int;
392
393			t.w.isdir = FALSE;
394			t.w.filemenu = TRUE;
395			tmp = t.file.loadx(q0, fd);
396			q1 = q0 + tmp;
397		}
398		fd = nil;
399		if(setqid){
400			t.file.dev = d.dev;
401			t.file.mtime = d.mtime;
402			t.file.qidpath = d.qid.path;
403		}
404		rp = stralloc(BUFSIZE);
405		for(q=q0; q<q1; q+=n){
406			n = q1-q;
407			if(n > Dat->BUFSIZE)
408				n = Dat->BUFSIZE;
409			t.file.buf.read(q, rp, 0, n);
410			if(q < t.org)
411				t.org += n;
412			else if(q <= t.org+t.frame.nchars)
413				frinsert(t.frame, rp.s, n, q-t.org);
414			if(t.frame.lastlinefull)
415				break;
416		}
417		strfree(rp);
418		rp = nil;
419		for(i=0; i<t.file.ntext; i++){
420			u = t.file.text[i];
421			if(u != t){
422				if(u.org > u.file.buf.nc)	# will be 0 because of reset(), but safety first
423					u.org = 0;
424				u.reshape(u.all);
425				u.backnl(u.org, 0);	# go to beginning of line
426			}
427			u.setselect(q0, q0);
428		}
429		return q1-q0;
430	}
431	exception{
432		* =>
433			fd = nil;
434			return 0;
435	}
436	return 0;
437}
438
439Text.bsinsert(t : self ref Text, q0 : int, r : string, n : int, tofile : int) : (int, int)
440{
441	tp : ref Astring;
442	bp, up : int;
443	i, initial : int;
444
445	{
446		if(t.what == Tag)	# can't happen but safety first: mustn't backspace over file name
447			raise "e";
448		bp = 0;
449		for(i=0; i<n; i++)
450			if(r[bp++] == '\b'){
451				--bp;
452				initial = 0;
453				tp = utils->stralloc(n);
454				for (k := 0; k < i; k++)
455					tp.s[k] = r[k];
456				up = i;
457				for(; i<n; i++){
458					tp.s[up] = r[bp++];
459					if(tp.s[up] == '\b')
460						if(up == 0)
461							initial++;
462						else
463							--up;
464					else
465						up++;
466				}
467				if(initial){
468					if(initial > q0)
469						initial = q0;
470					q0 -= initial;
471					t.delete(q0, q0+initial, tofile);
472				}
473				n = up;
474				t.insert(q0, tp.s, n, tofile, 0);
475				strfree(tp);
476				tp = nil;
477				return (q0, n);
478			}
479		raise "e";
480		return(0, 0);
481	}
482	exception{
483		* =>
484			t.insert(q0, r, n, tofile, 0);
485			return (q0, n);
486	}
487	return (0, 0);
488}
489
490Text.insert(t : self ref Text, q0 : int, r : string, n : int, tofile : int, echomode : int)
491{
492	c, i : int;
493	u : ref Text;
494
495	if(tofile && t.ncache != 0)
496		error("text.insert");
497	if(n == 0)
498		return;
499	if(tofile){
500		t.file.insert(q0, r, n);
501		if(t.what == Body){
502			t.w.dirty = TRUE;
503			t.w.utflastqid = -1;
504		}
505		if(t.file.ntext > 1)
506			for(i=0; i<t.file.ntext; i++){
507				u = t.file.text[i];
508				if(u != t){
509					u.w.dirty = TRUE;	# always a body
510					u.insert(q0, r, n, FALSE, echomode);
511					u.setselect(u.q0, u.q1);
512					scrdraw(u);
513				}
514			}
515	}
516	if(q0 < t.q1)
517		t.q1 += n;
518	if(q0 < t.q0)
519		t.q0 += n;
520	if(q0 < t.org)
521		t.org += n;
522	else if(q0 <= t.org+t.frame.nchars) {
523		if (echomode == EM_MASK && len r == 1 && r[0] != '\n')
524			frinsert(t.frame, "*", n, q0-t.org);
525		else
526			frinsert(t.frame, r, n, q0-t.org);
527	}
528	if(t.w != nil){
529		c = 'i';
530		if(t.what == Body)
531			c = 'I';
532		if(n <= Dat->EVENTSIZE)
533			t.w.event(sprint("%c%d %d 0 %d %s\n", c, q0, q0+n, n, r[0:n]));
534		else
535			t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q0+n));
536	}
537}
538
539Text.fill(t : self ref Text)
540{
541	rp : ref Astring;
542	i, n, m, nl : int;
543
544	if(t.frame.lastlinefull || t.nofill)
545		return;
546	if(t.ncache > 0){
547		if(t.w != nil)
548			t.w.commit(t);
549		else
550			t.commit(TRUE);
551	}
552	rp = stralloc(BUFSIZE);
553	do{
554		n = t.file.buf.nc-(t.org+t.frame.nchars);
555		if(n == 0)
556			break;
557		if(n > 2000)	# educated guess at reasonable amount
558			n = 2000;
559		t.file.buf.read(t.org+t.frame.nchars, rp, 0, n);
560		#
561		# it's expensive to frinsert more than we need, so
562		# count newlines.
563		#
564
565		nl = t.frame.maxlines-t.frame.nlines;
566		m = 0;
567		for(i=0; i<n; ){
568			if(rp.s[i++] == '\n'){
569				m++;
570				if(m >= nl)
571					break;
572			}
573		}
574		frinsert(t.frame, rp.s, i, t.frame.nchars);
575	}while(t.frame.lastlinefull == FALSE);
576	strfree(rp);
577	rp = nil;
578}
579
580Text.delete(t : self ref Text, q0 : int, q1 : int, tofile : int)
581{
582	n, p0, p1 : int;
583	i, c : int;
584	u : ref Text;
585
586	if(tofile && t.ncache != 0)
587		error("text.delete");
588	n = q1-q0;
589	if(n == 0)
590		return;
591	if(tofile){
592		t.file.delete(q0, q1);
593		if(t.what == Body){
594			t.w.dirty = TRUE;
595			t.w.utflastqid = -1;
596		}
597		if(t.file.ntext > 1)
598			for(i=0; i<t.file.ntext; i++){
599				u = t.file.text[i];
600				if(u != t){
601					u.w.dirty = TRUE;	# always a body
602					u.delete(q0, q1, FALSE);
603					u.setselect(u.q0, u.q1);
604					scrdraw(u);
605				}
606			}
607	}
608	if(q0 < t.q0)
609		t.q0 -= min(n, t.q0-q0);
610	if(q0 < t.q1)
611		t.q1 -= min(n, t.q1-q0);
612	if(q1 <= t.org)
613		t.org -= n;
614	else if(q0 < t.org+t.frame.nchars){
615		p1 = q1 - t.org;
616		if(p1 > t.frame.nchars)
617			p1 = t.frame.nchars;
618		if(q0 < t.org){
619			t.org = q0;
620			p0 = 0;
621		}else
622			p0 = q0 - t.org;
623		frdelete(t.frame, p0, p1);
624		t.fill();
625	}
626	if(t.w != nil){
627		c = 'd';
628		if(t.what == Body)
629			c = 'D';
630		t.w.event(sprint("%c%d %d 0 0 \n", c, q0, q1));
631	}
632}
633
634onechar : ref Astring;
635
636Text.readc(t : self ref Text, q : int) : int
637{
638	if(t.cq0<=q && q<t.cq0+t.ncache)
639		return t.cache[q-t.cq0];
640	if (onechar == nil)
641		onechar = stralloc(1);
642	t.file.buf.read(q, onechar, 0, 1);
643	return onechar.s[0];
644}
645
646Text.bswidth(t : self ref Text, c : int) : int
647{
648	q, eq : int;
649	r : int;
650	skipping : int;
651
652	# there is known to be at least one character to erase
653	if(c == 16r08)	# ^H: erase character
654		return 1;
655	q = t.q0;
656	skipping = TRUE;
657	while(q > 0){
658		r = t.readc(q-1);
659		if(r == '\n'){		# eat at most one more character
660			if(q == t.q0)	# eat the newline
661				--q;
662			break;
663		}
664		if(c == 16r17){
665			eq = isalnum(r);
666			if(eq && skipping)	# found one; stop skipping
667				skipping = FALSE;
668			else if(!eq && !skipping)
669				break;
670		}
671		--q;
672	}
673	return t.q0-q;
674}
675
676Text.typex(t : self ref Text, r : int, echomode : int)
677{
678	q0, q1 : int;
679	nnb, nb, n, i : int;
680	u : ref Text;
681
682	if(alphabet != ALPHA_LATIN)
683		r = transc(r, alphabet);
684	if (echomode == EM_RAW && t.what == Body) {
685		if (t.w != nil) {
686			s := "a";
687			s[0] = r;
688			t.w.event(sprint("R0 0 0 1 %s\n", s));
689		}
690		return;
691	}
692	if(t.what!=Body && r=='\n')
693		return;
694	case r {
695		Dat->Kscrolldown=>
696			if(t.what == Body){
697				q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+2*t.frame.font.height));
698				t.setorigin(q0, FALSE);
699			}
700			return;
701		Dat->Kscrollup=>
702			if(t.what == Body){
703				q0 = t.backnl(t.org, 4);
704				t.setorigin(q0, FALSE);
705			}
706			return;
707		Kdown or Keyboard->Down =>
708			n = t.frame.maxlines/3;
709			q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height));
710			t.setorigin(q0, FALSE);
711			return;
712		Keyboard->Pgdown =>
713			n = 2*t.frame.maxlines/3;
714			q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+n*t.frame.font.height));
715			t.setorigin(q0, FALSE);
716			return;
717		Kup or Keyboard->Up =>
718			n = t.frame.maxlines/3;
719			q0 = t.backnl(t.org, n);
720			t.setorigin(q0, FALSE);
721			return;
722		Keyboard->Pgup =>
723			n = 2*t.frame.maxlines/3;
724			q0 = t.backnl(t.org, n);
725			t.setorigin(q0, FALSE);
726			return;
727		Keyboard->Home =>
728			t.commit(TRUE);
729			t.show(0, 0, FALSE);
730			return;
731		Kend or Keyboard->End =>
732			t.commit(TRUE);
733			t.show(t.file.buf.nc, t.file.buf.nc, FALSE);
734			return;
735		Kleft or Keyboard->Left =>
736			t.commit(TRUE);
737			if(t.q0 != t.q1)
738				t.show(t.q0, t.q0, TRUE);
739			else if(t.q0 != 0)
740				t.show(t.q0-1, t.q0-1, TRUE);
741			return;
742		Kright or Keyboard->Right =>
743			t.commit(TRUE);
744			if(t.q0 != t.q1)
745				t.show(t.q1, t.q1, TRUE);
746			else if(t.q1 != t.file.buf.nc)
747				t.show(t.q1+1, t.q1+1, TRUE);
748			return;
749		1 =>  	# ^A: beginning of line
750			t.commit(TRUE);
751			# go to where ^U would erase, if not already at BOL
752			nnb = 0;
753			if(t.q0>0 && t.readc(t.q0-1)!='\n')
754				nnb = t.bswidth(16r15);
755			t.show(t.q0-nnb, t.q0-nnb, TRUE);
756			return;
757		5 =>  	# ^E: end of line
758			t.commit(TRUE);
759			q0 = t.q0;
760			while(q0<t.file.buf.nc && t.readc(q0)!='\n')
761				q0++;
762			t.show(q0, q0, TRUE);
763			return;
764	}
765	if(t.what == Body){
766		seq++;
767		t.file.mark();
768	}
769	if(t.q1 > t.q0){
770		if(t.ncache != 0)
771			error("text.type");
772		exec->cut(t, t, TRUE, TRUE);
773		t.eq0 = ~0;
774		if (r == 16r08 || r == 16r7f){	# erase character : odd if a char then erased
775			t.show(t.q0, t.q0,TRUE);
776			return;
777		}
778	}
779	t.show(t.q0, t.q0, TRUE);
780	case(r){
781	16r1B =>
782		if(t.eq0 != ~0)
783			t.setselect(t.eq0, t.q0);
784		if(t.ncache > 0){
785			if(t.w != nil)
786				t.w.commit(t);
787			else
788				t.commit(TRUE);
789		}
790		return;
791	16r08 or 16r15 or 16r17 =>
792		# ^H: erase character or ^U: erase line or ^W: erase word
793		if(t.q0 == 0)
794			return;
795if(0)	# DEBUGGING
796	for(i=0; i<t.file.ntext; i++){
797		u = t.file.text[i];
798		if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0))
799			error("text.type inconsistent caches");
800	}
801		nnb = t.bswidth(r);
802		q1 = t.q0;
803		q0 = q1-nnb;
804		for(i=0; i<t.file.ntext; i++){
805			u = t.file.text[i];
806			u.nofill = TRUE;
807			nb = nnb;
808			n = u.ncache;
809			if(n > 0){
810				if(q1 != u.cq0+n)
811					error("text.type backspace");
812				if(n > nb)
813					n = nb;
814				u.ncache -= n;
815				u.delete(q1-n, q1, FALSE);
816				nb -= n;
817			}
818			if(u.eq0==q1 || u.eq0==~0)
819				u.eq0 = q0;
820			if(nb && u==t)
821				u.delete(q0, q0+nb, TRUE);
822			if(u != t)
823				u.setselect(u.q0, u.q1);
824			else
825				t.setselect(q0, q0);
826			u.nofill = FALSE;
827		}
828		for(i=0; i<t.file.ntext; i++)
829			t.file.text[i].fill();
830		return;
831	16r7f or Keyboard->Del =>
832		# Delete character - forward delete
833		t.commit(TRUE);
834		if(t.q0 >= t.file.buf.nc)
835			return;
836		nnb = 1;
837		q0 = t.q0;
838		q1 = q0+nnb;
839		for(i=0; i<t.file.ntext; i++){
840			u = t.file.text[i];
841			if (u!=t)
842				u.commit(FALSE);
843			u.nofill = TRUE;
844			if(u.eq0==q1 || u.eq0==~0)
845				u.eq0 = q0;
846			if(u==t)
847				u.delete(q0, q1, TRUE);
848			if(u != t)
849				u.setselect(u.q0, u.q1);
850			else
851				t.setselect(q0, q0);
852			u.nofill = FALSE;
853		}
854		for(i=0; i<t.file.ntext; i++)
855			t.file.text[i].fill();
856		return;
857	}
858	# otherwise ordinary character; just insert, typically in caches of all texts
859if(0)	# DEBUGGING
860	for(i=0; i<t.file.ntext; i++){
861		u = t.file.text[i];
862		if(u.cq0!=t.cq0 && (u.ncache!=t.ncache || t.ncache!=0))
863			error("text.type inconsistent caches");
864	}
865	for(i=0; i<t.file.ntext; i++){
866		u = t.file.text[i];
867		if(u.eq0 == ~0)
868			u.eq0 = t.q0;
869		if(u.ncache == 0)
870			u.cq0 = t.q0;
871		else if(t.q0 != u.cq0+u.ncache)
872			error("text.type cq1");
873		str := "Z";
874		str[0] = r;
875		u.insert(t.q0, str, 1, FALSE, echomode);
876		str = nil;
877		if(u != t)
878			u.setselect(u.q0, u.q1);
879		if(u.ncache == u.ncachealloc){
880			u.ncachealloc += 10;
881			u.cache += "1234567890";
882		}
883		u.cache[u.ncache++] = r;
884	}
885	t.setselect(t.q0+1, t.q0+1);
886	if(r=='\n' && t.w!=nil)
887		t.w.commit(t);
888}
889
890Text.commit(t : self ref Text, tofile : int)
891{
892	if(t.ncache == 0)
893		return;
894	if(tofile)
895		t.file.insert(t.cq0, t.cache, t.ncache);
896	if(t.what == Body){
897		t.w.dirty = TRUE;
898		t.w.utflastqid = -1;
899	}
900	t.ncache = 0;
901}
902
903clicktext : ref Text;
904clickmsec : int = 0;
905selecttext : ref Text;
906selectq : int = 0;
907
908#
909# called from frame library
910#
911
912framescroll(f : ref Frame, dl : int)
913{
914	if(f != selecttext.frame)
915		error("frameselect not right frame");
916	selecttext.framescroll(dl);
917}
918
919Text.framescroll(t : self ref Text, dl : int)
920{
921	q0 : int;
922
923	if(dl == 0){
924		scrl->scrsleep(100);
925		return;
926	}
927	if(dl < 0){
928		q0 = t.backnl(t.org, -dl);
929		if(selectq > t.org+t.frame.p0)
930			t.setselect0(t.org+t.frame.p0, selectq);
931		else
932			t.setselect0(selectq, t.org+t.frame.p0);
933	}else{
934		if(t.org+t.frame.nchars == t.file.buf.nc)
935			return;
936		q0 = t.org+frcharofpt(t.frame, (t.frame.r.min.x, t.frame.r.min.y+dl*t.frame.font.height));
937		if(selectq > t.org+t.frame.p1)
938			t.setselect0(t.org+t.frame.p1, selectq);
939		else
940			t.setselect0(selectq, t.org+t.frame.p1);
941	}
942	t.setorigin(q0, TRUE);
943}
944
945
946Text.select(t : self ref Text, double : int)
947{
948	q0, q1 : int;
949	b, x, y : int;
950	state : int;
951
952	selecttext = t;
953	#
954	# To have double-clicking and chording, we double-click
955	# immediately if it might make sense.
956	#
957
958	b = mouse.buttons;
959	q0 = t.q0;
960	q1 = t.q1;
961	selectq = t.org+frcharofpt(t.frame, mouse.xy);
962	if(double || (clicktext==t && mouse.msec-clickmsec<500))
963	if(q0==q1 && selectq==q0){
964		(q0, q1) = t.doubleclick(q0, q1);
965		t.setselect(q0, q1);
966		bflush();
967		x = mouse.xy.x;
968		y = mouse.xy.y;
969		# stay here until something interesting happens
970		do
971			frgetmouse();
972		while(mouse.buttons==b && utils->abs(mouse.xy.x-x)<3 && utils->abs(mouse.xy.y-y)<3);
973		mouse.xy.x = x;	# in case we're calling frselect
974		mouse.xy.y = y;
975		q0 = t.q0;	# may have changed
976		q1 = t.q1;
977		selectq = q0;
978	}
979	if(mouse.buttons == b){
980		t.frame.scroll = 1;
981		frselect(t.frame, mouse);
982		# horrible botch: while asleep, may have lost selection altogether
983		if(selectq > t.file.buf.nc)
984			selectq = t.org + t.frame.p0;
985		t.frame.scroll = 0;
986		if(selectq < t.org)
987			q0 = selectq;
988		else
989			q0 = t.org + t.frame.p0;
990		if(selectq > t.org+t.frame.nchars)
991			q1 = selectq;
992		else
993			q1 = t.org+t.frame.p1;
994	}
995	if(q0 == q1){
996		if(q0==t.q0 && (double || clicktext==t && mouse.msec-clickmsec<500)){
997			(q0, q1) = t.doubleclick(q0, q1);
998			clicktext = nil;
999		}else{
1000			clicktext = t;
1001			clickmsec = mouse.msec;
1002		}
1003	}else
1004		clicktext = nil;
1005	t.setselect(q0, q1);
1006	bflush();
1007	state = 0;	# undo when possible; +1 for cut, -1 for paste
1008	while(mouse.buttons){
1009		mouse.msec = 0;
1010		b = mouse.buttons;
1011		if(b & 6){
1012			if(state==0 && t.what==Body){
1013				seq++;
1014				t.w.body.file.mark();
1015			}
1016			if(b & 2){
1017				if(state==-1 && t.what==Body){
1018					t.w.undo(TRUE);
1019					t.setselect(q0, t.q0);
1020					state = 0;
1021				}else if(state != 1){
1022					exec->cut(t, t, TRUE, TRUE);
1023					state = 1;
1024				}
1025			}else{
1026				if(state==1 && t.what==Body){
1027					t.w.undo(TRUE);
1028					t.setselect(q0, t.q1);
1029					state = 0;
1030				}else if(state != -1){
1031					exec->paste(t, t, TRUE, FALSE);
1032					state = -1;
1033				}
1034			}
1035			scrdraw(t);
1036			utils->clearmouse();
1037		}
1038		bflush();
1039		while(mouse.buttons == b)
1040			frgetmouse();
1041		clicktext = nil;
1042	}
1043}
1044
1045Text.show(t : self ref Text, q0 : int, q1 : int, doselect : int)
1046{
1047	qe : int;
1048	nl : int;
1049	q : int;
1050
1051	if(t.what != Body){
1052		if(doselect)
1053			t.setselect(q0, q1);
1054		return;
1055	}
1056	if(t.w!=nil && t.frame.maxlines==0)
1057		t.col.grow(t.w, 1, 0);
1058	if(doselect)
1059		t.setselect(q0, q1);
1060	qe = t.org+t.frame.nchars;
1061	if(t.org<=q0 && (q0<qe || (q0==qe && qe==t.file.buf.nc+t.ncache)))
1062		scrdraw(t);
1063	else{
1064		if(t.w.nopen[Dat->QWevent]>byte 0)
1065			nl = 3*t.frame.maxlines/4;
1066		else
1067			nl = t.frame.maxlines/4;
1068		q = t.backnl(q0, nl);
1069		# avoid going backwards if trying to go forwards - long lines!
1070		if(!(q0>t.org && q<t.org))
1071			t.setorigin(q, TRUE);
1072		while(q0 > t.org+t.frame.nchars)
1073			t.setorigin(t.org+1, FALSE);
1074	}
1075}
1076
1077region(a, b : int) : int
1078{
1079	if(a < b)
1080		return -1;
1081	if(a == b)
1082		return 0;
1083	return 1;
1084}
1085
1086selrestore(f : ref Frame, pt0 : Point, p0 : int, p1 : int)
1087{
1088	if(p1<=f.p0 || p0>=f.p1){
1089		# no overlap
1090		frdrawsel0(f, pt0, p0, p1, f.cols[BACK], f.cols[TEXT]);
1091		return;
1092	}
1093	if(p0>=f.p0 && p1<=f.p1){
1094		# entirely inside
1095		frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]);
1096		return;
1097	}
1098	# they now are known to overlap
1099	# before selection
1100	if(p0 < f.p0){
1101		frdrawsel0(f, pt0, p0, f.p0, f.cols[BACK], f.cols[TEXT]);
1102		p0 = f.p0;
1103		pt0 = frptofchar(f, p0);
1104	}
1105	# after selection
1106	if(p1 > f.p1){
1107		frdrawsel0(f, frptofchar(f, f.p1), f.p1, p1, f.cols[BACK], f.cols[TEXT]);
1108		p1 = f.p1;
1109	}
1110	# inside selection
1111	frdrawsel0(f, pt0, p0, p1, f.cols[HIGH], f.cols[HTEXT]);
1112}
1113
1114Text.setselect(t : self ref Text, q0 : int, q1 : int)
1115{
1116	p0, p1 : int;
1117
1118	# t.p0 and t.p1 are always right; t.q0 and t.q1 may be off
1119	t.q0 = q0;
1120	t.q1 = q1;
1121	# compute desired p0,p1 from q0,q1
1122	p0 = q0-t.org;
1123	p1 = q1-t.org;
1124	if(p0 < 0)
1125		p0 = 0;
1126	if(p1 < 0)
1127		p1 = 0;
1128	if(p0 > t.frame.nchars)
1129		p0 = t.frame.nchars;
1130	if(p1 > t.frame.nchars)
1131		p1 = t.frame.nchars;
1132	if(p0==t.frame.p0 && p1==t.frame.p1)
1133		return;
1134	# screen disagrees with desired selection
1135	if(t.frame.p1<=p0 || p1<=t.frame.p0 || p0==p1 || t.frame.p1==t.frame.p0){
1136		# no overlap or too easy to bother trying
1137		frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, t.frame.p1, 0);
1138		frdrawsel(t.frame, frptofchar(t.frame, p0), p0, p1, 1);
1139		t.frame.p0 = p0;
1140		t.frame.p1 = p1;
1141		return;
1142	}
1143	# overlap; avoid unnecessary painting
1144	if(p0 < t.frame.p0){
1145		# extend selection backwards
1146		frdrawsel(t.frame, frptofchar(t.frame, p0), p0, t.frame.p0, 1);
1147	}else if(p0 > t.frame.p0){
1148		# trim first part of selection
1149		frdrawsel(t.frame, frptofchar(t.frame, t.frame.p0), t.frame.p0, p0, 0);
1150	}
1151	if(p1 > t.frame.p1){
1152		# extend selection forwards
1153		frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1), t.frame.p1, p1, 1);
1154	}else if(p1 < t.frame.p1){
1155		# trim last part of selection
1156		frdrawsel(t.frame, frptofchar(t.frame, p1), p1, t.frame.p1, 0);
1157	}
1158	t.frame.p0 = p0;
1159	t.frame.p1 = p1;
1160}
1161
1162Text.setselect0(t : self ref Text, q0 : int, q1 : int)
1163{
1164	t.q0 = q0;
1165	t.q1 = q1;
1166}
1167
1168xselect(f : ref Frame, mc : ref Draw->Pointer, col, colt : ref Image) : (int, int)
1169{
1170	p0, p1, q, tmp : int;
1171	mp, pt0, pt1, qt : Point;
1172	reg, b : int;
1173
1174	# when called button 1 is down
1175	mp = mc.xy;
1176	b = mc.buttons;
1177
1178	# remove tick
1179	if(f.p0 == f.p1)
1180		frtick(f, frptofchar(f, f.p0), 0);
1181	p0 = p1 = frcharofpt(f, mp);
1182	pt0 = frptofchar(f, p0);
1183	pt1 = frptofchar(f, p1);
1184	reg = 0;
1185	frtick(f, pt0, 1);
1186	do{
1187		q = frcharofpt(f, mc.xy);
1188		if(p1 != q){
1189			if(p0 == p1)
1190				frtick(f, pt0, 0);
1191			if(reg != region(q, p0)){	# crossed starting point; reset
1192				if(reg > 0)
1193					selrestore(f, pt0, p0, p1);
1194				else if(reg < 0)
1195					selrestore(f, pt1, p1, p0);
1196				p1 = p0;
1197				pt1 = pt0;
1198				reg = region(q, p0);
1199				if(reg == 0)
1200					frdrawsel0(f, pt0, p0, p1, col, colt);
1201			}
1202			qt = frptofchar(f, q);
1203			if(reg > 0){
1204				if(q > p1)
1205					frdrawsel0(f, pt1, p1, q, col, colt);
1206				else if(q < p1)
1207					selrestore(f, qt, q, p1);
1208			}else if(reg < 0){
1209				if(q > p1)
1210					selrestore(f, pt1, p1, q);
1211				else
1212					frdrawsel0(f, qt, q, p1, col, colt);
1213			}
1214			p1 = q;
1215			pt1 = qt;
1216		}
1217		if(p0 == p1)
1218			frtick(f, pt0, 1);
1219		bflush();
1220		frgetmouse();
1221	}while(mc.buttons == b);
1222	if(p1 < p0){
1223		tmp = p0;
1224		p0 = p1;
1225		p1 = tmp;
1226	}
1227	pt0 = frptofchar(f, p0);
1228	if(p0 == p1)
1229		frtick(f, pt0, 0);
1230	selrestore(f, pt0, p0, p1);
1231	# restore tick
1232	if(f.p0 == f.p1)
1233		frtick(f, frptofchar(f, f.p0), 1);
1234	bflush();
1235	return (p0, p1);
1236}
1237
1238Text.select23(t : self ref Text, q0 : int, q1 : int, high, low : ref Image, mask : int) : (int, int, int)
1239{
1240	p0, p1 : int;
1241	buts : int;
1242
1243	(p0, p1) = xselect(t.frame, mouse, high, low);
1244	buts = mouse.buttons;
1245	if((buts & mask) == 0){
1246		q0 = p0+t.org;
1247		q1 = p1+t.org;
1248	}
1249	while(mouse.buttons)
1250		frgetmouse();
1251	return (buts, q0, q1);
1252}
1253
1254Text.select2(t : self ref Text, q0 : int, q1 : int) : (int, ref Text, int, int)
1255{
1256	buts : int;
1257
1258	(buts, q0, q1) = t.select23(q0, q1, acme->but2col, acme->but2colt, 4);
1259	if(buts & 4)
1260		return (0, nil, q0, q1);
1261	if(buts & 1)	# pick up argument
1262		return (1, dat->argtext, q0, q1);
1263	return (1, nil, q0, q1);
1264}
1265
1266Text.select3(t : self ref Text, q0 : int, q1 : int) : (int, int, int)
1267{
1268	buts : int;
1269
1270	(buts, q0, q1) = t.select23(q0, q1, acme->but3col, acme->but3colt, 1|2);
1271	return (buts == 0, q0, q1);
1272}
1273
1274left := array[4] of {
1275	"{[(<«",
1276	"\n",
1277	"'\"`",
1278	nil
1279};
1280right := array[4] of {
1281	"}])>»",
1282	"\n",
1283	"'\"`",
1284	nil
1285};
1286
1287Text.doubleclick(t : self ref Text, q0 : int, q1 : int) : (int, int)
1288{
1289	c, i : int;
1290	r, l : string;
1291	p : int;
1292	q : int;
1293	res : int;
1294
1295	for(i=0; left[i]!=nil; i++){
1296		q = q0;
1297		l = left[i];
1298		r = right[i];
1299		# try matching character to left, looking right
1300		if(q == 0)
1301			c = '\n';
1302		else
1303			c = t.readc(q-1);
1304		p = utils->strchr(l, c);
1305		if(p >= 0){
1306			(res, q) = t.clickmatch(c, r[p], 1, q);
1307			if (res)
1308				q1 = q-(c!='\n');
1309			return (q0, q1);
1310		}
1311		# try matching character to right, looking left
1312		if(q == t.file.buf.nc)
1313			c = '\n';
1314		else
1315			c = t.readc(q);
1316		p = utils->strchr(r, c);
1317		if(p >= 0){
1318			(res, q) = t.clickmatch(c, l[p], -1, q);
1319			if (res){
1320				q1 = q0+(q0<t.file.buf.nc && c=='\n');
1321				q0 = q;
1322				if(c!='\n' || q!=0 || t.readc(0)=='\n')
1323					q0++;
1324			}
1325			return (q0, q1);
1326		}
1327	}
1328	# try filling out word to right
1329	while(q1<t.file.buf.nc && isalnum(t.readc(q1)))
1330		q1++;
1331	# try filling out word to left
1332	while(q0>0 && isalnum(t.readc(q0-1)))
1333		q0--;
1334	return (q0, q1);
1335}
1336
1337Text.clickmatch(t : self ref Text, cl : int, cr : int, dir : int, q : int) : (int, int)
1338{
1339	c : int;
1340	nest : int;
1341
1342	nest = 1;
1343	for(;;){
1344		if(dir > 0){
1345			if(q == t.file.buf.nc)
1346				break;
1347			c = t.readc(q);
1348			q++;
1349		}else{
1350			if(q == 0)
1351				break;
1352			q--;
1353			c = t.readc(q);
1354		}
1355		if(c == cr){
1356			if(--nest==0)
1357				return (1, q);
1358		}else if(c == cl)
1359			nest++;
1360	}
1361	return (cl=='\n' && nest==1, q);
1362}
1363
1364Text.forwnl(t : self ref Text, p : int, n : int) : int
1365{
1366	i, j : int;
1367
1368	e := t.file.buf.nc-1;
1369	i = n;
1370	while(i-- > 0 && p<e){
1371		++p;
1372		if(p == e)
1373			break;
1374		for(j=128; --j>0 && p<e; p++)
1375			if(t.readc(p)=='\n')
1376				break;
1377	}
1378	return p;
1379}
1380
1381Text.backnl(t : self ref Text, p : int, n : int) : int
1382{
1383	i, j : int;
1384
1385	# look for start of this line if n==0
1386	if(n==0 && p>0 && t.readc(p-1)!='\n')
1387		n = 1;
1388	i = n;
1389	while(i-- > 0 && p>0){
1390		--p;	# it's at a newline now; back over it
1391		if(p == 0)
1392			break;
1393		# at 128 chars, call it a line anyway
1394		for(j=128; --j>0 && p>0; p--)
1395			if(t.readc(p-1)=='\n')
1396				break;
1397	}
1398	return p;
1399}
1400
1401Text.setorigin(t : self ref Text, org : int, exact : int)
1402{
1403	i, a : int;
1404	r : ref Astring;
1405	n : int;
1406
1407	t.frame.b.flush(Flushoff);
1408	if(org>0 && !exact){
1409		# org is an estimate of the char posn; find a newline
1410		# don't try harder than 256 chars
1411		for(i=0; i<256 && org<t.file.buf.nc; i++){
1412			if(t.readc(org) == '\n'){
1413				org++;
1414				break;
1415			}
1416			org++;
1417		}
1418	}
1419	a = org-t.org;
1420	fixup := 0;
1421	if(a>=0 && a<t.frame.nchars){
1422		frdelete(t.frame, 0, a);
1423		fixup = 1;		# frdelete can leave end of last line in wrong selection mode; it doesn't know what follows
1424	}
1425	else if(a<0 && -a<t.frame.nchars){
1426		n = t.org - org;
1427		r = utils->stralloc(n);
1428		t.file.buf.read(org, r, 0, n);
1429		frinsert(t.frame, r.s, n, 0);
1430		utils->strfree(r);
1431		r = nil;
1432	}else
1433		frdelete(t.frame, 0, t.frame.nchars);
1434	t.org = org;
1435	t.fill();
1436	scrdraw(t);
1437	t.setselect(t.q0, t.q1);
1438	if(fixup && t.frame.p1 > t.frame.p0)
1439		frdrawsel(t.frame, frptofchar(t.frame, t.frame.p1-1), t.frame.p1-1, t.frame.p1, 1);
1440	t.frame.b.flush(Flushon);
1441}
1442
1443Text.reset(t : self ref Text)
1444{
1445	t.file.seq = 0;
1446	t.eq0 = ~0;
1447	# do t.delete(0, t.nc, TRUE) without building backup stuff
1448	t.setselect(t.org, t.org);
1449	frdelete(t.frame, 0, t.frame.nchars);
1450	t.org = 0;
1451	t.q0 = 0;
1452	t.q1 = 0;
1453	t.file.reset();
1454	t.file.buf.reset();
1455}
1456