xref: /plan9/sys/src/cmd/acme/wind.c (revision 588d0145e19f8596f2f4442d05dd8a9eda147983)
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 "dat.h"
12 #include "fns.h"
13 
14 int	winid;
15 
16 void
wininit(Window * w,Window * clone,Rectangle r)17 wininit(Window *w, Window *clone, Rectangle r)
18 {
19 	Rectangle r1, br;
20 	File *f;
21 	Reffont *rf;
22 	Rune *rp;
23 	int nc;
24 
25 	w->tag.w = w;
26 	w->body.w = w;
27 	w->id = ++winid;
28 	incref(w);
29 	if(globalincref)
30 		incref(w);
31 	w->ctlfid = ~0;
32 	w->utflastqid = -1;
33 	r1 = r;
34 	r1.max.y = r1.min.y + font->height;
35 	incref(&reffont);
36 	f = fileaddtext(nil, &w->tag);
37 	textinit(&w->tag, f, r1, &reffont, tagcols);
38 	w->tag.what = Tag;
39 	/* tag is a copy of the contents, not a tracked image */
40 	if(clone){
41 		textdelete(&w->tag, 0, w->tag.file->nc, TRUE);
42 		nc = clone->tag.file->nc;
43 		rp = runemalloc(nc);
44 		bufread(clone->tag.file, 0, rp, nc);
45 		textinsert(&w->tag, 0, rp, nc, TRUE);
46 		free(rp);
47 		filereset(w->tag.file);
48 		textsetselect(&w->tag, nc, nc);
49 	}
50 	r1 = r;
51 	r1.min.y += font->height + 1;
52 	if(r1.max.y < r1.min.y)
53 		r1.max.y = r1.min.y;
54 	f = nil;
55 	if(clone){
56 		f = clone->body.file;
57 		w->body.org = clone->body.org;
58 		w->isscratch = clone->isscratch;
59 		rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
60 	}else
61 		rf = rfget(FALSE, FALSE, FALSE, nil);
62 	f = fileaddtext(f, &w->body);
63 	w->body.what = Body;
64 	textinit(&w->body, f, r1, rf, textcols);
65 	r1.min.y -= 1;
66 	r1.max.y = r1.min.y+1;
67 	draw(screen, r1, tagcols[BORD], nil, ZP);
68 	textscrdraw(&w->body);
69 	w->r = r;
70 	w->r.max.y = w->body.r.max.y;
71 	br.min = w->tag.scrollr.min;
72 	br.max.x = br.min.x + Dx(button->r);
73 	br.max.y = br.min.y + Dy(button->r);
74 	draw(screen, br, button, nil, button->r.min);
75 	w->filemenu = TRUE;
76 	w->maxlines = w->body.maxlines;
77 	w->autoindent = globalautoindent;
78 	if(clone){
79 		w->dirty = clone->dirty;
80 		textsetselect(&w->body, clone->body.q0, clone->body.q1);
81 		winsettag(w);
82 		w->autoindent = clone->autoindent;
83 	}
84 }
85 
86 int
delrunepos(Window * w)87 delrunepos(Window *w)
88 {
89 	int n;
90 	Rune rune;
91 
92 	for(n=0; n<w->tag.file->nc; n++) {
93 		bufread(w->tag.file, n, &rune, 1);
94 		if(rune == ' ')
95 			break;
96 	}
97 	n += 2;
98 	if(n >= w->tag.file->nc)
99 		return -1;
100 	return n;
101 }
102 
103 void
movetodel(Window * w)104 movetodel(Window *w)
105 {
106 	int n;
107 
108 	n = delrunepos(w);
109 	if(n < 0)
110 		return;
111 	moveto(mousectl, addpt(frptofchar(&w->tag, n), Pt(4, w->tag.font->height-4)));
112 }
113 
114 int
winresize(Window * w,Rectangle r,int safe)115 winresize(Window *w, Rectangle r, int safe)
116 {
117 	Rectangle r1;
118 	int y;
119 	Image *b;
120 	Rectangle br;
121 
122 	r1 = r;
123 	r1.max.y = r1.min.y + font->height;
124 	y = r1.max.y;
125 	if(!safe || !eqrect(w->tag.r, r1)){
126 		y = textresize(&w->tag, r1);
127 		b = button;
128 		if(w->body.file->mod && !w->isdir && !w->isscratch)
129 			b = modbutton;
130 		br.min = w->tag.scrollr.min;
131 		br.max.x = br.min.x + Dx(b->r);
132 		br.max.y = br.min.y + Dy(b->r);
133 		draw(screen, br, b, nil, b->r.min);
134 	}
135 	if(!safe || !eqrect(w->body.r, r1)){
136 		if(y+1+font->height > r.max.y){		/* no body */
137 			r1.min.y = y;
138 			r1.max.y = y;
139 			textresize(&w->body, r1);
140 			w->r = r;
141 			w->r.max.y = y;
142 			return y;
143 		}
144 		r1 = r;
145 		r1.min.y = y;
146 		r1.max.y = y + 1;
147 		draw(screen, r1, tagcols[BORD], nil, ZP);
148 		r1.min.y = y + 1;
149 		r1.max.y = r.max.y;
150 		y = textresize(&w->body, r1);
151 		w->r = r;
152 		w->r.max.y = y;
153 		textscrdraw(&w->body);
154 	}
155 	w->maxlines = min(w->body.nlines, max(w->maxlines, w->body.maxlines));
156 	return w->r.max.y;
157 }
158 
159 void
winlock1(Window * w,int owner)160 winlock1(Window *w, int owner)
161 {
162 	incref(w);
163 	qlock(w);
164 	w->owner = owner;
165 }
166 
167 void
winlock(Window * w,int owner)168 winlock(Window *w, int owner)
169 {
170 	int i;
171 	File *f;
172 
173 	f = w->body.file;
174 	for(i=0; i<f->ntext; i++)
175 		winlock1(f->text[i]->w, owner);
176 }
177 
178 void
winunlock(Window * w)179 winunlock(Window *w)
180 {
181 	int i;
182 	File *f;
183 
184 	/*
185 	 * subtle: loop runs backwards to avoid tripping over
186 	 * winclose indirectly editing f->text and freeing f
187 	 * on the last iteration of the loop.
188 	 */
189 	f = w->body.file;
190 	for(i=f->ntext-1; i>=0; i--){
191 		w = f->text[i]->w;
192 		w->owner = 0;
193 		qunlock(w);
194 		winclose(w);
195 	}
196 }
197 
198 void
winmousebut(Window * w)199 winmousebut(Window *w)
200 {
201 	moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
202 }
203 
204 void
windirfree(Window * w)205 windirfree(Window *w)
206 {
207 	int i;
208 	Dirlist *dl;
209 
210 	if(w->isdir){
211 		for(i=0; i<w->ndl; i++){
212 			dl = w->dlp[i];
213 			free(dl->r);
214 			free(dl);
215 		}
216 		free(w->dlp);
217 	}
218 	w->dlp = nil;
219 	w->ndl = 0;
220 }
221 
222 void
winclose(Window * w)223 winclose(Window *w)
224 {
225 	int i;
226 
227 	if(decref(w) == 0){
228 		windirfree(w);
229 		textclose(&w->tag);
230 		textclose(&w->body);
231 		if(activewin == w)
232 			activewin = nil;
233 		for(i=0; i<w->nincl; i++)
234 			free(w->incl[i]);
235 		free(w->incl);
236 		free(w->events);
237 		free(w);
238 	}
239 }
240 
241 void
windelete(Window * w)242 windelete(Window *w)
243 {
244 	Xfid *x;
245 
246 	x = w->eventx;
247 	if(x){
248 		w->nevents = 0;
249 		free(w->events);
250 		w->events = nil;
251 		w->eventx = nil;
252 		sendp(x->c, nil);	/* wake him up */
253 	}
254 }
255 
256 void
winundo(Window * w,int isundo)257 winundo(Window *w, int isundo)
258 {
259 	Text *body;
260 	int i;
261 	File *f;
262 	Window *v;
263 
264 	w->utflastqid = -1;
265 	body = &w->body;
266 	fileundo(body->file, isundo, &body->q0, &body->q1);
267 	textshow(body, body->q0, body->q1, 1);
268 	f = body->file;
269 	for(i=0; i<f->ntext; i++){
270 		v = f->text[i]->w;
271 		v->dirty = (f->seq != v->putseq);
272 		if(v != w){
273 			v->body.q0 = v->body.p0+v->body.org;
274 			v->body.q1 = v->body.p1+v->body.org;
275 		}
276 	}
277 	winsettag(w);
278 }
279 
280 void
winsetname(Window * w,Rune * name,int n)281 winsetname(Window *w, Rune *name, int n)
282 {
283 	Text *t;
284 	Window *v;
285 	int i;
286 
287 	t = &w->body;
288 	if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
289 		return;
290 	w->isscratch = FALSE;
291 	if(n>=6 && runeeq(L"/guide", 6, name+(n-6), 6))
292 		w->isscratch = TRUE;
293 	else if(n>=7 && runeeq(L"+Errors", 7, name+(n-7), 7))
294 		w->isscratch = TRUE;
295 	filesetname(t->file, name, n);
296 	for(i=0; i<t->file->ntext; i++){
297 		v = t->file->text[i]->w;
298 		winsettag(v);
299 		v->isscratch = w->isscratch;
300 	}
301 }
302 
303 void
wintype(Window * w,Text * t,Rune r)304 wintype(Window *w, Text *t, Rune r)
305 {
306 	int i;
307 
308 	texttype(t, r);
309 	if(t->what == Body)
310 		for(i=0; i<t->file->ntext; i++)
311 			textscrdraw(t->file->text[i]);
312 	winsettag(w);
313 }
314 
315 void
wincleartag(Window * w)316 wincleartag(Window *w)
317 {
318 	int i, n;
319 	Rune *r;
320 
321 	/* w must be committed */
322 	n = w->tag.file->nc;
323 	r = runemalloc(n);
324 	bufread(w->tag.file, 0, r, n);
325 	for(i=0; i<n; i++)
326 		if(r[i]==' ' || r[i]=='\t')
327 			break;
328 	for(; i<n; i++)
329 		if(r[i] == '|')
330 			break;
331 	if(i == n)
332 		return;
333 	i++;
334 	textdelete(&w->tag, i, n, TRUE);
335 	free(r);
336 	w->tag.file->mod = FALSE;
337 	if(w->tag.q0 > i)
338 		w->tag.q0 = i;
339 	if(w->tag.q1 > i)
340 		w->tag.q1 = i;
341 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
342 }
343 
344 void
winsettag1(Window * w)345 winsettag1(Window *w)
346 {
347 	int i, j, k, n, bar, dirty;
348 	Rune *new, *old, *r;
349 	Image *b;
350 	uint q0, q1;
351 	Rectangle br;
352 
353 	/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
354 	if(w->tag.ncache!=0 || w->tag.file->mod)
355 		wincommit(w, &w->tag);	/* check file name; also guarantees we can modify tag contents */
356 	old = runemalloc(w->tag.file->nc+1);
357 	bufread(w->tag.file, 0, old, w->tag.file->nc);
358 	old[w->tag.file->nc] = '\0';
359 	for(i=0; i<w->tag.file->nc; i++)
360 		if(old[i]==' ' || old[i]=='\t')
361 			break;
362 	if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
363 		textdelete(&w->tag, 0, i, TRUE);
364 		textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
365 		free(old);
366 		old = runemalloc(w->tag.file->nc+1);
367 		bufread(w->tag.file, 0, old, w->tag.file->nc);
368 		old[w->tag.file->nc] = '\0';
369 	}
370 	new = runemalloc(w->body.file->nname+100);
371 	i = 0;
372 	runemove(new+i, w->body.file->name, w->body.file->nname);
373 	i += w->body.file->nname;
374 	runemove(new+i, L" Del Snarf", 10);
375 	i += 10;
376 	if(w->filemenu){
377 		if(w->body.file->delta.nc>0 || w->body.ncache){
378 			runemove(new+i, L" Undo", 5);
379 			i += 5;
380 		}
381 		if(w->body.file->epsilon.nc > 0){
382 			runemove(new+i, L" Redo", 5);
383 			i += 5;
384 		}
385 		dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
386 		if(!w->isdir && dirty){
387 			runemove(new+i, L" Put", 4);
388 			i += 4;
389 		}
390 	}
391 	if(w->isdir){
392 		runemove(new+i, L" Get", 4);
393 		i += 4;
394 	}
395 	runemove(new+i, L" |", 2);
396 	i += 2;
397 	r = runestrchr(old, '|');
398 	if(r)
399 		k = r-old+1;
400 	else{
401 		k = w->tag.file->nc;
402 		if(w->body.file->seq == 0){
403 			runemove(new+i, L" Look ", 6);
404 			i += 6;
405 		}
406 	}
407 	if(runeeq(new, i, old, k) == FALSE){
408 		n = k;
409 		if(n > i)
410 			n = i;
411 		for(j=0; j<n; j++)
412 			if(old[j] != new[j])
413 				break;
414 		q0 = w->tag.q0;
415 		q1 = w->tag.q1;
416 		textdelete(&w->tag, j, k, TRUE);
417 		textinsert(&w->tag, j, new+j, i-j, TRUE);
418 		/* try to preserve user selection */
419 		r = runestrchr(old, '|');
420 		if(r){
421 			bar = r-old;
422 			if(q0 > bar){
423 				bar = (runestrchr(new, '|')-new)-bar;
424 				w->tag.q0 = q0+bar;
425 				w->tag.q1 = q1+bar;
426 			}
427 		}
428 	}
429 	free(old);
430 	free(new);
431 	w->tag.file->mod = FALSE;
432 	n = w->tag.file->nc+w->tag.ncache;
433 	if(w->tag.q0 > n)
434 		w->tag.q0 = n;
435 	if(w->tag.q1 > n)
436 		w->tag.q1 = n;
437 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
438 	b = button;
439 	if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
440 		b = modbutton;
441 	br.min = w->tag.scrollr.min;
442 	br.max.x = br.min.x + Dx(b->r);
443 	br.max.y = br.min.y + Dy(b->r);
444 	draw(screen, br, b, nil, b->r.min);
445 }
446 
447 void
winsettag(Window * w)448 winsettag(Window *w)
449 {
450 	int i;
451 	File *f;
452 	Window *v;
453 
454 	f = w->body.file;
455 	for(i=0; i<f->ntext; i++){
456 		v = f->text[i]->w;
457 		if(v->col->safe || v->body.maxlines>0)
458 			winsettag1(v);
459 	}
460 }
461 
462 void
wincommit(Window * w,Text * t)463 wincommit(Window *w, Text *t)
464 {
465 	Rune *r;
466 	int i;
467 	File *f;
468 
469 	textcommit(t, TRUE);
470 	f = t->file;
471 	if(f->ntext > 1)
472 		for(i=0; i<f->ntext; i++)
473 			textcommit(f->text[i], FALSE);	/* no-op for t */
474 	if(t->what == Body)
475 		return;
476 	r = runemalloc(w->tag.file->nc);
477 	bufread(w->tag.file, 0, r, w->tag.file->nc);
478 	for(i=0; i<w->tag.file->nc; i++)
479 		if(r[i]==' ' || r[i]=='\t')
480 			break;
481 	if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
482 		seq++;
483 		filemark(w->body.file);
484 		w->body.file->mod = TRUE;
485 		w->dirty = TRUE;
486 		winsetname(w, r, i);
487 		winsettag(w);
488 	}
489 	free(r);
490 }
491 
492 void
winaddincl(Window * w,Rune * r,int n)493 winaddincl(Window *w, Rune *r, int n)
494 {
495 	char *a;
496 	Dir *d;
497 	Runestr rs;
498 
499 	a = runetobyte(r, n);
500 	d = dirstat(a);
501 	if(d == nil){
502 		if(a[0] == '/')
503 			goto Rescue;
504 		rs = dirname(&w->body, r, n);
505 		r = rs.r;
506 		n = rs.nr;
507 		free(a);
508 		a = runetobyte(r, n);
509 		d = dirstat(a);
510 		if(d == nil)
511 			goto Rescue;
512 		r = runerealloc(r, n+1);
513 		r[n] = 0;
514 	}
515 	free(a);
516 	if((d->qid.type&QTDIR) == 0){
517 		free(d);
518 		warning(nil, "%s: not a directory\n", a);
519 		free(r);
520 		return;
521 	}
522 	free(d);
523 	w->nincl++;
524 	w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
525 	memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
526 	w->incl[0] = runemalloc(n+1);
527 	runemove(w->incl[0], r, n);
528 	free(r);
529 	return;
530 
531 Rescue:
532 	warning(nil, "%s: %r\n", a);
533 	free(r);
534 	free(a);
535 	return;
536 }
537 
538 int
winclean(Window * w,int conservative)539 winclean(Window *w, int conservative)	/* as it stands, conservative is always TRUE */
540 {
541 	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
542 		return TRUE;
543 	if(!conservative && w->nopen[QWevent]>0)
544 		return TRUE;
545 	if(w->dirty){
546 		if(w->body.file->nname)
547 			warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
548 		else{
549 			if(w->body.file->nc < 100)	/* don't whine if it's too small */
550 				return TRUE;
551 			warning(nil, "unnamed file modified\n");
552 		}
553 		w->dirty = FALSE;
554 		return FALSE;
555 	}
556 	return TRUE;
557 }
558 
559 char*
winctlprint(Window * w,char * buf,int fonts)560 winctlprint(Window *w, char *buf, int fonts)
561 {
562 	sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->nc,
563 		w->body.file->nc, w->isdir, w->dirty);
564 	if(fonts)
565 		return smprint("%s%11d %q %11d " , buf, Dx(w->body.r),
566 			w->body.reffont->f->name, w->body.maxtab);
567 	return buf;
568 }
569 
570 void
winevent(Window * w,char * fmt,...)571 winevent(Window *w, char *fmt, ...)
572 {
573 	int n;
574 	char *b;
575 	Xfid *x;
576 	va_list arg;
577 
578 	if(w->nopen[QWevent] == 0)
579 		return;
580 	if(w->owner == 0)
581 		error("no window owner");
582 	va_start(arg, fmt);
583 	b = vsmprint(fmt, arg);
584 	va_end(arg);
585 	if(b == nil)
586 		error("vsmprint failed");
587 	n = strlen(b);
588 	w->events = realloc(w->events, w->nevents+1+n);
589 	w->events[w->nevents++] = w->owner;
590 	memmove(w->events+w->nevents, b, n);
591 	free(b);
592 	w->nevents += n;
593 	x = w->eventx;
594 	if(x){
595 		w->eventx = nil;
596 		sendp(x->c, nil);
597 	}
598 }
599