master ohmu/acme2k / src / cmd / acme / rows.c
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 <bio.h>
 11#include <plumb.h>
 12#include <libsec.h>
 13#include "dat.h"
 14#include "fns.h"
 15
 16static Rune Lcolhdr[] = {
 17	'N', 'e', 'w', 'c', 'o', 'l', ' ',
 18	'K', 'i', 'l', 'l', ' ',
 19	'P', 'u', 't', 'a', 'l', 'l', ' ',
 20	'D', 'u', 'm', 'p', ' ',
 21	'E', 'x', 'i', 't', ' ',
 22	0
 23};
 24
 25void
 26rowinit(Row *row, Rectangle r)
 27{
 28	Rectangle r1;
 29	Text *t;
 30
 31	draw(screen, r, display->white, nil, ZP);
 32	row->r = r;
 33	row->col = nil;
 34	row->ncol = 0;
 35	r1 = r;
 36	r1.max.y = r1.min.y + font->height;
 37	t = &row->tag;
 38	textinit(t, fileaddtext(nil, t), r1, rfget(FALSE, FALSE, FALSE, nil), tagcols);
 39	t->what = Rowtag;
 40	t->row = row;
 41	t->w = nil;
 42	t->col = nil;
 43	r1.min.y = r1.max.y;
 44	r1.max.y += Border;
 45	draw(screen, r1, display->black, nil, ZP);
 46	textinsert(t, 0, Lcolhdr, 29, TRUE);
 47	textsetselect(t, t->file->b.nc, t->file->b.nc);
 48}
 49
 50Column*
 51rowadd(Row *row, Column *c, int x)
 52{
 53	Rectangle r, r1;
 54	Column *d;
 55	int i;
 56
 57	d = nil;
 58	r = row->r;
 59	r.min.y = row->tag.fr.r.max.y+Border;
 60	if(x<r.min.x && row->ncol>0){	/*steal 40% of last column by default */
 61		d = row->col[row->ncol-1];
 62		x = d->r.min.x + 3*Dx(d->r)/5;
 63	}
 64	/* look for column we'll land on */
 65	for(i=0; i<row->ncol; i++){
 66		d = row->col[i];
 67		if(x < d->r.max.x)
 68			break;
 69	}
 70	if(row->ncol > 0){
 71		if(i < row->ncol)
 72			i++;	/* new column will go after d */
 73		r = d->r;
 74		if(Dx(r) < 100)
 75			return nil;
 76		draw(screen, r, display->white, nil, ZP);
 77		r1 = r;
 78		r1.max.x = min(x-Border, r.max.x-50);
 79		if(Dx(r1) < 50)
 80			r1.max.x = r1.min.x+50;
 81		colresize(d, r1);
 82		r1.min.x = r1.max.x;
 83		r1.max.x = r1.min.x+Border;
 84		draw(screen, r1, display->black, nil, ZP);
 85		r.min.x = r1.max.x;
 86	}
 87	if(c == nil){
 88		c = emalloc(sizeof(Column));
 89		colinit(c, r);
 90		incref(&reffont.ref);
 91	}else
 92		colresize(c, r);
 93	c->row = row;
 94	c->tag.row = row;
 95	row->col = realloc(row->col, (row->ncol+1)*sizeof(Column*));
 96	memmove(row->col+i+1, row->col+i, (row->ncol-i)*sizeof(Column*));
 97	row->col[i] = c;
 98	row->ncol++;
 99	clearmouse();
100	return c;
101}
102
103void
104rowresize(Row *row, Rectangle r)
105{
106	int i, deltax;
107	Rectangle or, r1, r2;
108	Column *c;
109
110	or = row->r;
111	deltax = r.min.x - or.min.x;
112	row->r = r;
113	r1 = r;
114	r1.max.y = r1.min.y + font->height;
115	textresize(&row->tag, r1, TRUE);
116	r1.min.y = r1.max.y;
117	r1.max.y += Border;
118	draw(screen, r1, display->black, nil, ZP);
119	r.min.y = r1.max.y;
120	r1 = r;
121	r1.max.x = r1.min.x;
122	for(i=0; i<row->ncol; i++){
123		c = row->col[i];
124		r1.min.x = r1.max.x;
125		/* the test should not be necessary, but guarantee we don't lose a pixel */
126		if(i == row->ncol-1)
127			r1.max.x = r.max.x;
128		else
129			r1.max.x = (c->r.max.x-or.min.x)*Dx(r)/Dx(or) + deltax;
130		if(i > 0){
131			r2 = r1;
132			r2.max.x = r2.min.x+Border;
133			draw(screen, r2, display->black, nil, ZP);
134			r1.min.x = r2.max.x;
135		}
136		colresize(c, r1);
137	}
138}
139
140void
141rowdragcol(Row *row, Column *c, int _0)
142{
143	Rectangle r;
144	int i, b, x;
145	Point p, op;
146	Column *d;
147
148	USED(_0);
149
150	clearmouse();
151	setcursor(mousectl, &boxcursor);
152	b = mouse->buttons;
153	op = mouse->xy;
154	while(mouse->buttons == b)
155		readmouse(mousectl);
156	setcursor(mousectl, nil);
157	if(mouse->buttons){
158		while(mouse->buttons)
159			readmouse(mousectl);
160		return;
161	}
162
163	for(i=0; i<row->ncol; i++)
164		if(row->col[i] == c)
165			goto Found;
166	error("can't find column");
167
168  Found:
169	p = mouse->xy;
170	if((abs(p.x-op.x)<5 && abs(p.y-op.y)<5))
171		return;
172	if((i>0 && p.x<row->col[i-1]->r.min.x) || (i<row->ncol-1 && p.x>c->r.max.x)){
173		/* shuffle */
174		x = c->r.min.x;
175		rowclose(row, c, FALSE);
176		if(rowadd(row, c, p.x) == nil)	/* whoops! */
177		if(rowadd(row, c, x) == nil)		/* WHOOPS! */
178		if(rowadd(row, c, -1)==nil){		/* shit! */
179			rowclose(row, c, TRUE);
180			return;
181		}
182		colmousebut(c);
183		return;
184	}
185	if(i == 0)
186		return;
187	d = row->col[i-1];
188	if(p.x < d->r.min.x+80+Scrollwid)
189		p.x = d->r.min.x+80+Scrollwid;
190	if(p.x > c->r.max.x-80-Scrollwid)
191		p.x = c->r.max.x-80-Scrollwid;
192	r = d->r;
193	r.max.x = c->r.max.x;
194	draw(screen, r, display->white, nil, ZP);
195	r.max.x = p.x;
196	colresize(d, r);
197	r = c->r;
198	r.min.x = p.x;
199	r.max.x = r.min.x;
200	r.max.x += Border;
201	draw(screen, r, display->black, nil, ZP);
202	r.min.x = r.max.x;
203	r.max.x = c->r.max.x;
204	colresize(c, r);
205	colmousebut(c);
206}
207
208void
209rowclose(Row *row, Column *c, int dofree)
210{
211	Rectangle r;
212	int i;
213
214	for(i=0; i<row->ncol; i++)
215		if(row->col[i] == c)
216			goto Found;
217	error("can't find column");
218  Found:
219	r = c->r;
220	if(dofree)
221		colcloseall(c);
222	row->ncol--;
223	memmove(row->col+i, row->col+i+1, (row->ncol-i)*sizeof(Column*));
224	row->col = realloc(row->col, row->ncol*sizeof(Column*));
225	if(row->ncol == 0){
226		draw(screen, r, display->white, nil, ZP);
227		return;
228	}
229	if(i == row->ncol){		/* extend last column right */
230		c = row->col[i-1];
231		r.min.x = c->r.min.x;
232		r.max.x = row->r.max.x;
233	}else{			/* extend next window left */
234		c = row->col[i];
235		r.max.x = c->r.max.x;
236	}
237	draw(screen, r, display->white, nil, ZP);
238	colresize(c, r);
239}
240
241Column*
242rowwhichcol(Row *row, Point p)
243{
244	int i;
245	Column *c;
246
247	for(i=0; i<row->ncol; i++){
248		c = row->col[i];
249		if(ptinrect(p, c->r))
250			return c;
251	}
252	return nil;
253}
254
255Text*
256rowwhich(Row *row, Point p)
257{
258	Column *c;
259
260	if(ptinrect(p, row->tag.all))
261		return &row->tag;
262	c = rowwhichcol(row, p);
263	if(c)
264		return colwhich(c, p);
265	return nil;
266}
267
268Text*
269rowtype(Row *row, Rune r, Point p)
270{
271	Window *w;
272	Text *t;
273
274	if(r == 0)
275		r = Runeerror;
276
277	clearmouse();
278	qlock(&row->lk);
279	if(bartflag)
280		t = barttext;
281	else
282		t = rowwhich(row, p);
283	if(t!=nil && !(t->what==Tag && ptinrect(p, t->scrollr))){
284		w = t->w;
285		if(w == nil)
286			texttype(t, r);
287		else{
288			winlock(w, 'K');
289			wintype(w, t, r);
290			/* Expand tag if necessary */
291			if(t->what == Tag){
292				t->w->tagsafe = FALSE;
293				if(r == '\n')
294					t->w->tagexpand = TRUE;
295				winresize(w, w->r, TRUE, TRUE);
296			}
297			winunlock(w);
298		}
299	}
300	qunlock(&row->lk);
301	return t;
302}
303
304int
305rowclean(Row *row)
306{
307	int clean;
308	int i;
309
310	clean = TRUE;
311	for(i=0; i<row->ncol; i++)
312		clean &= colclean(row->col[i]);
313	return clean;
314}
315
316void
317rowdump(Row *row, char *file)
318{
319	int i, j, fd, m, n, dumped;
320	uint q0, q1;
321	Biobuf *b;
322	char *buf, *a, *fontname;
323	Rune *r;
324	Column *c;
325	Window *w, *w1;
326	Text *t;
327
328	if(row->ncol == 0)
329		return;
330	buf = fbufalloc();
331	if(file == nil){
332		if(home == nil){
333			warning(nil, "can't find file for dump: $home not defined\n");
334			goto Rescue;
335		}
336		sprint(buf, "%s/acme.dump", home);
337		file = buf;
338	}
339	fd = create(file, OWRITE, 0600);
340	if(fd < 0){
341		warning(nil, "can't open %s: %r\n", file);
342		goto Rescue;
343	}
344	b = emalloc(sizeof(Biobuf));
345	Binit(b, fd, OWRITE);
346	r = fbufalloc();
347	Bprint(b, "%s\n", wdir);
348	Bprint(b, "%s\n", fontnames[0]);
349	Bprint(b, "%s\n", fontnames[1]);
350	for(i=0; i<row->ncol; i++){
351		c = row->col[i];
352		Bprint(b, "%11.7f", 100.0*(c->r.min.x-row->r.min.x)/Dx(row->r));
353		if(i == row->ncol-1)
354			Bputc(b, '\n');
355		else
356			Bputc(b, ' ');
357	}
358	for(i=0; i<row->ncol; i++){
359		c = row->col[i];
360		for(j=0; j<c->nw; j++)
361			c->w[j]->body.file->dumpid = 0;
362	}
363	m = min(RBUFSIZE, row->tag.file->b.nc);
364	bufread(&row->tag.file->b, 0, r, m);
365	n = 0;
366	while(n<m && r[n]!='\n')
367		n++;
368	Bprint(b, "w %.*S\n", n, r);
369	for(i=0; i<row->ncol; i++){
370		c = row->col[i];
371		m = min(RBUFSIZE, c->tag.file->b.nc);
372		bufread(&c->tag.file->b, 0, r, m);
373		n = 0;
374		while(n<m && r[n]!='\n')
375			n++;
376		Bprint(b, "c%11d %.*S\n", i, n, r);
377	}
378	for(i=0; i<row->ncol; i++){
379		c = row->col[i];
380		for(j=0; j<c->nw; j++){
381			w = c->w[j];
382			wincommit(w, &w->tag);
383			t = &w->body;
384			/* windows owned by others get special treatment */
385			if(w->nopen[QWevent] > 0)
386				if(w->dumpstr == nil)
387					continue;
388			/* zeroxes of external windows are tossed */
389			if(t->file->ntext > 1)
390				for(n=0; n<t->file->ntext; n++){
391					w1 = t->file->text[n]->w;
392					if(w == w1)
393						continue;
394					if(w1->nopen[QWevent])
395						goto Continue2;
396				}
397			fontname = "";
398			if(t->reffont->f != font)
399				fontname = t->reffont->f->name;
400			if(t->file->nname)
401				a = runetobyte(t->file->name, t->file->nname);
402			else
403				a = emalloc(1);
404			if(t->file->dumpid){
405				dumped = FALSE;
406				Bprint(b, "x%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid,
407					w->body.q0, w->body.q1,
408					100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
409					fontname);
410			}else if(w->dumpstr){
411				dumped = FALSE;
412				Bprint(b, "e%11d %11d %11d %11d %11.7f %s\n", i, t->file->dumpid,
413					0, 0,
414					100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
415					fontname);
416			}else if((w->dirty==FALSE && access(a, 0)==0) || w->isdir){
417				dumped = FALSE;
418				t->file->dumpid = w->id;
419				Bprint(b, "f%11d %11d %11d %11d %11.7f %s\n", i, w->id,
420					w->body.q0, w->body.q1,
421					100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
422					fontname);
423			}else{
424				dumped = TRUE;
425				t->file->dumpid = w->id;
426				Bprint(b, "F%11d %11d %11d %11d %11.7f %11d %s\n", i, j,
427					w->body.q0, w->body.q1,
428					100.0*(w->r.min.y-c->r.min.y)/Dy(c->r),
429					w->body.file->b.nc, fontname);
430			}
431			free(a);
432			winctlprint(w, buf, 0);
433			Bwrite(b, buf, strlen(buf));
434			m = min(RBUFSIZE, w->tag.file->b.nc);
435			bufread(&w->tag.file->b, 0, r, m);
436			n = 0;
437			while(n<m && r[n]!='\n')
438				n++;
439			Bprint(b, "%.*S\n", n, r);
440			if(dumped){
441				q0 = 0;
442				q1 = t->file->b.nc;
443				while(q0 < q1){
444					n = q1 - q0;
445					if(n > BUFSIZE/UTFmax)
446						n = BUFSIZE/UTFmax;
447					bufread(&t->file->b, q0, r, n);
448					Bprint(b, "%.*S", n, r);
449					q0 += n;
450				}
451			}
452			if(w->dumpstr){
453				if(w->dumpdir)
454					Bprint(b, "%s\n%s\n", w->dumpdir, w->dumpstr);
455				else
456					Bprint(b, "\n%s\n", w->dumpstr);
457			}
458    Continue2:;
459		}
460	}
461	Bterm(b);
462	close(fd);
463	free(b);
464	fbuffree(r);
465
466   Rescue:
467	fbuffree(buf);
468}
469
470static
471char*
472rdline(Biobuf *b, int *linep)
473{
474	char *l;
475
476	l = Brdline(b, '\n');
477	if(l)
478		(*linep)++;
479	return l;
480}
481
482/*
483 * Get font names from load file so we don't load fonts we won't use
484 */
485void
486rowloadfonts(char *file)
487{
488	int i;
489	Biobuf *b;
490	char *l;
491
492	b = Bopen(file, OREAD);
493	if(b == nil)
494		return;
495	/* current directory */
496	l = Brdline(b, '\n');
497	if(l == nil)
498		goto Return;
499	/* global fonts */
500	for(i=0; i<2; i++){
501		l = Brdline(b, '\n');
502		if(l == nil)
503			goto Return;
504		l[Blinelen(b)-1] = 0;
505		if(*l && strcmp(l, fontnames[i])!=0){
506			free(fontnames[i]);
507			fontnames[i] = estrdup(l);
508		}
509	}
510    Return:
511	Bterm(b);
512}
513
514int
515rowload(Row *row, char *file, int initing)
516{
517	int i, j, line, y, nr, nfontr, n, ns, ndumped, dumpid, x, fd, done;
518	double percent;
519	Biobuf *b, *bout;
520	char *buf, *l, *t, *fontname;
521	Rune *r, *fontr;
522	int rune;
523	Column *c, *c1, *c2;
524	uint q0, q1;
525	Rectangle r1, r2;
526	Window *w;
527
528	buf = fbufalloc();
529	if(file == nil){
530		if(home == nil){
531			warning(nil, "can't find file for load: $home not defined\n");
532			goto Rescue1;
533		}
534		sprint(buf, "%s/acme.dump", home);
535		file = buf;
536	}
537	b = Bopen(file, OREAD);
538	if(b == nil){
539		warning(nil, "can't open load file %s: %r\n", file);
540		goto Rescue1;
541	}
542	/* current directory */
543	line = 0;
544	l = rdline(b, &line);
545	if(l == nil)
546		goto Rescue2;
547	l[Blinelen(b)-1] = 0;
548	if(chdir(l) < 0){
549		warning(nil, "can't chdir %s\n", l);
550		goto Rescue2;
551	}
552	/* global fonts */
553	for(i=0; i<2; i++){
554		l = rdline(b, &line);
555		if(l == nil)
556			goto Rescue2;
557		l[Blinelen(b)-1] = 0;
558		if(*l && strcmp(l, fontnames[i])!=0)
559			rfget(i, TRUE, i==0 && initing, l);
560	}
561	if(initing && row->ncol==0)
562		rowinit(row, screen->clipr);
563	l = rdline(b, &line);
564	if(l == nil)
565		goto Rescue2;
566	j = Blinelen(b)/12;
567	if(j<=0 || j>10)
568		goto Rescue2;
569	for(i=0; i<j; i++){
570		percent = atof(l+i*12);
571		if(percent<0 || percent>=100)
572			goto Rescue2;
573		x = row->r.min.x+percent*Dx(row->r)/100+0.5;
574		if(i < row->ncol){
575			if(i == 0)
576				continue;
577			c1 = row->col[i-1];
578			c2 = row->col[i];
579			r1 = c1->r;
580			r2 = c2->r;
581			if(x<Border)
582				x = Border;
583			r1.max.x = x-Border;
584			r2.min.x = x;
585			if(Dx(r1) < 50 || Dx(r2) < 50)
586				continue;
587			draw(screen, Rpt(r1.min, r2.max), display->white, nil, ZP);
588			colresize(c1, r1);
589			colresize(c2, r2);
590			r2.min.x = x-Border;
591			r2.max.x = x;
592			draw(screen, r2, display->black, nil, ZP);
593		}
594		if(i >= row->ncol)
595			rowadd(row, nil, x);
596	}
597	done = 0;
598	while(!done){
599		l = rdline(b, &line);
600		if(l == nil)
601			break;
602		switch(l[0]){
603		case 'c':
604			l[Blinelen(b)-1] = 0;
605			i = atoi(l+1+0*12);
606			r = bytetorune(l+1*12, &nr);
607			ns = -1;
608			for(n=0; n<nr; n++){
609				if(r[n] == '/')
610					ns = n;
611				if(r[n] == ' ')
612					break;
613			}
614			textdelete(&row->col[i]->tag, 0, row->col[i]->tag.file->b.nc, TRUE);
615			textinsert(&row->col[i]->tag, 0, r+n+1, nr-(n+1), TRUE);
616			break;
617		case 'w':
618			l[Blinelen(b)-1] = 0;
619			r = bytetorune(l+2, &nr);
620			ns = -1;
621			for(n=0; n<nr; n++){
622				if(r[n] == '/')
623					ns = n;
624				if(r[n] == ' ')
625					break;
626			}
627			textdelete(&row->tag, 0, row->tag.file->b.nc, TRUE);
628			textinsert(&row->tag, 0, r, nr, TRUE);
629			break;
630		default:
631			done = 1;
632			break;
633		}
634	}
635	for(;;){
636		if(l == nil)
637			break;
638		dumpid = 0;
639		switch(l[0]){
640		case 'e':
641			if(Blinelen(b) < 1+5*12+1)
642				goto Rescue2;
643			l = rdline(b, &line);	/* ctl line; ignored */
644			if(l == nil)
645				goto Rescue2;
646			l = rdline(b, &line);	/* directory */
647			if(l == nil)
648				goto Rescue2;
649			l[Blinelen(b)-1] = 0;
650			if(*l == '\0'){
651				if(home == nil)
652					r = bytetorune("./", &nr);
653				else{
654					t = emalloc(strlen(home)+1+1);
655					sprint(t, "%s/", home);
656					r = bytetorune(t, &nr);
657					free(t);
658				}
659			}else
660				r = bytetorune(l, &nr);
661			l = rdline(b, &line);	/* command */
662			if(l == nil)
663				goto Rescue2;
664			t = emalloc(Blinelen(b)+1);
665			memmove(t, l, Blinelen(b));
666			run(nil, t, r, nr, TRUE, nil, nil, FALSE);
667			/* r is freed in run() */
668			goto Nextline;
669		case 'f':
670			if(Blinelen(b) < 1+5*12+1)
671				goto Rescue2;
672			fontname = l+1+5*12;
673			ndumped = -1;
674			break;
675		case 'F':
676			if(Blinelen(b) < 1+6*12+1)
677				goto Rescue2;
678			fontname = l+1+6*12;
679			ndumped = atoi(l+1+5*12+1);
680			break;
681		case 'x':
682			if(Blinelen(b) < 1+5*12+1)
683				goto Rescue2;
684			fontname = l+1+5*12;
685			ndumped = -1;
686			dumpid = atoi(l+1+1*12);
687			break;
688		default:
689			goto Rescue2;
690		}
691		l[Blinelen(b)-1] = 0;
692		fontr = nil;
693		nfontr = 0;
694		if(*fontname)
695			fontr = bytetorune(fontname, &nfontr);
696		i = atoi(l+1+0*12);
697		j = atoi(l+1+1*12);
698		q0 = atoi(l+1+2*12);
699		q1 = atoi(l+1+3*12);
700		percent = atof(l+1+4*12);
701		if(i<0 || i>10)
702			goto Rescue2;
703		if(i > row->ncol)
704			i = row->ncol;
705		c = row->col[i];
706		y = c->r.min.y+(percent*Dy(c->r))/100+0.5;
707		if(y<c->r.min.y || y>=c->r.max.y)
708			y = -1;
709		if(dumpid == 0)
710			w = coladd(c, nil, nil, y);
711		else
712			w = coladd(c, nil, lookid(dumpid, TRUE), y);
713		if(w == nil)
714			goto Nextline;
715		w->dumpid = j;
716		l = rdline(b, &line);
717		if(l == nil)
718			goto Rescue2;
719		l[Blinelen(b)-1] = 0;
720		r = bytetorune(l+5*12, &nr);
721		ns = -1;
722		for(n=0; n<nr; n++){
723			if(r[n] == '/')
724				ns = n;
725			if(r[n] == ' ')
726				break;
727		}
728		if(dumpid == 0)
729			winsetname(w, r, n);
730		for(; n<nr; n++)
731			if(r[n] == '|')
732				break;
733		wincleartag(w);
734		textinsert(&w->tag, w->tag.file->b.nc, r+n+1, nr-(n+1), TRUE);
735		if(ndumped >= 0){
736			/* simplest thing is to put it in a file and load that */
737			sprint(buf, "/tmp/d%d.%.4sacme", getpid(), getuser());
738			fd = create(buf, OWRITE, 0600);
739			if(fd < 0){
740				free(r);
741				warning(nil, "can't create temp file: %r\n");
742				goto Rescue2;
743			}
744			bout = emalloc(sizeof(Biobuf));
745			Binit(bout, fd, OWRITE);
746			for(n=0; n<ndumped; n++){
747				rune = Bgetrune(b);
748				if(rune == '\n')
749					line++;
750				if(rune == Beof){
751					free(r);
752					Bterm(bout);
753					free(bout);
754					close(fd);
755					remove(buf);
756					goto Rescue2;
757				}
758				Bputrune(bout, rune);
759			}
760			Bterm(bout);
761			free(bout);
762			textload(&w->body, 0, buf, 1);
763			remove(buf);
764			close(fd);
765			w->body.file->mod = TRUE;
766			for(n=0; n<w->body.file->ntext; n++)
767				w->body.file->text[n]->w->dirty = TRUE;
768			winsettag(w);
769		}else if(dumpid==0 && r[ns+1]!='+' && r[ns+1]!='-')
770			get(&w->body, nil, nil, FALSE, XXX, nil, 0);
771		if(fontr){
772			fontx(&w->body, nil, nil, 0, 0, fontr, nfontr);
773			free(fontr);
774		}
775		free(r);
776		if(q0>w->body.file->b.nc || q1>w->body.file->b.nc || q0>q1)
777			q0 = q1 = 0;
778		textshow(&w->body, q0, q1, 1);
779		w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
780		xfidlog(w, "new");
781Nextline:
782		l = rdline(b, &line);
783	}
784	Bterm(b);
785	fbuffree(buf);
786	return TRUE;
787
788Rescue2:
789	warning(nil, "bad load file %s:%d\n", file, line);
790	Bterm(b);
791Rescue1:
792	fbuffree(buf);
793	return FALSE;
794}
795
796void
797allwindows(void (*f)(Window*, void*), void *arg)
798{
799	int i, j;
800	Column *c;
801
802	for(i=0; i<row.ncol; i++){
803		c = row.col[i];
804		for(j=0; j<c->nw; j++)
805			(*f)(c->w[j], arg);
806	}
807}