xref: /plan9/sys/src/cmd/samterm/main.c (revision 0b459c2cb92b7c9d88818e9a2f72e678e5bc4553)
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 "flayer.h"
10 #include "samterm.h"
11 
12 Text	cmd;
13 Rune	*scratch;
14 long	nscralloc;
15 Cursor	*cursor;
16 Flayer	*which = 0;
17 Flayer	*work = 0;
18 long	snarflen;
19 long	typestart = -1;
20 long	typeend = -1;
21 long	typeesc = -1;
22 long	modified = 0;		/* strange lookahead for menus */
23 char	hostlock = 1;
24 char	hasunlocked = 0;
25 int	maxtab = 8;
26 
27 void
28 threadmain(int argc, char *argv[])
29 {
30 	int i, got, scr;
31 	Text *t;
32 	Rectangle r;
33 	Flayer *nwhich;
34 
35 	getscreen(argc, argv);
36 	iconinit();
37 	initio();
38 	scratch = alloc(100*RUNESIZE);
39 	nscralloc = 100;
40 	r = screen->r;
41 	r.max.y = r.min.y+Dy(r)/5;
42 	flstart(screen->clipr);
43 	rinit(&cmd.rasp);
44 	flnew(&cmd.l[0], gettext, 1, &cmd);
45 	flinit(&cmd.l[0], r, font, cmdcols);
46 	cmd.nwin = 1;
47 	which = &cmd.l[0];
48 	cmd.tag = Untagged;
49 	outTs(Tversion, VERSION);
50 	startnewfile(Tstartcmdfile, &cmd);
51 
52 	got = 0;
53 	for(;;got = waitforio()){
54 		if(hasunlocked && RESIZED())
55 			resize();
56 		if(got&(1<<RHost))
57 			rcv();
58 		if(got&(1<<RPlumb)){
59 			for(i=0; cmd.l[i].textfn==0; i++)
60 				;
61 			current(&cmd.l[i]);
62 			flsetselect(which, cmd.rasp.nrunes, cmd.rasp.nrunes);
63 			type(which, RPlumb);
64 		}
65 		if(got&(1<<RKeyboard))
66 			if(which)
67 				type(which, RKeyboard);
68 			else
69 				kbdblock();
70 		if(got&(1<<RMouse)){
71 			if(hostlock==2 || !ptinrect(mousep->xy, screen->r)){
72 				mouseunblock();
73 				continue;
74 			}
75 			nwhich = flwhich(mousep->xy);
76 			scr = which && ptinrect(mousep->xy, which->scroll);
77 			if(mousep->buttons)
78 				flushtyping(1);
79 			if(mousep->buttons&1){
80 				if(nwhich){
81 					if(nwhich!=which)
82 						current(nwhich);
83 					else if(scr)
84 						scroll(which, 1);
85 					else{
86 						t=(Text *)which->user1;
87 						if(flselect(which)){
88 							outTsl(Tdclick, t->tag, which->p0);
89 							t->lock++;
90 						}else if(t!=&cmd)
91 							outcmd();
92 					}
93 				}
94 			}else if((mousep->buttons&2) && which){
95 				if(scr)
96 					scroll(which, 2);
97 				else
98 					menu2hit();
99 			}else if((mousep->buttons&4)){
100 				if(scr)
101 					scroll(which, 3);
102 				else
103 					menu3hit();
104 			}
105 			mouseunblock();
106 		}
107 	}
108 }
109 
110 
111 void
112 resize(void){
113 	int i;
114 
115 	flresize(screen->clipr);
116 	for(i = 0; i<nname; i++)
117 		if(text[i])
118 			hcheck(text[i]->tag);
119 }
120 
121 void
122 current(Flayer *nw)
123 {
124 	Text *t;
125 
126 	if(which)
127 		flborder(which, 0);
128 	if(nw){
129 		flushtyping(1);
130 		flupfront(nw);
131 		flborder(nw, 1);
132 		buttons(Up);
133 		t = (Text *)nw->user1;
134 		t->front = nw-&t->l[0];
135 		if(t != &cmd)
136 			work = nw;
137 	}
138 	which = nw;
139 }
140 
141 void
142 closeup(Flayer *l)
143 {
144 	Text *t=(Text *)l->user1;
145 	int m;
146 
147 	m = whichmenu(t->tag);
148 	if(m < 0)
149 		return;
150 	flclose(l);
151 	if(l == which){
152 		which = 0;
153 		current(flwhich(Pt(0, 0)));
154 	}
155 	if(l == work)
156 		work = 0;
157 	if(--t->nwin == 0){
158 		rclear(&t->rasp);
159 		free((uchar *)t);
160 		text[m] = 0;
161 	}else if(l == &t->l[t->front]){
162 		for(m=0; m<NL; m++)	/* find one; any one will do */
163 			if(t->l[m].textfn){
164 				t->front = m;
165 				return;
166 			}
167 		panic("close");
168 	}
169 }
170 
171 Flayer *
172 findl(Text *t)
173 {
174 	int i;
175 	for(i = 0; i<NL; i++)
176 		if(t->l[i].textfn==0)
177 			return &t->l[i];
178 	return 0;
179 }
180 
181 void
182 duplicate(Flayer *l, Rectangle r, Font *f, int close)
183 {
184 	Text *t=(Text *)l->user1;
185 	Flayer *nl = findl(t);
186 	Rune *rp;
187 	ulong n;
188 
189 	if(nl){
190 		flnew(nl, gettext, l->user0, (char *)t);
191 		flinit(nl, r, f, l->f.cols);
192 		nl->origin = l->origin;
193 		rp = (*l->textfn)(l, l->f.nchars, &n);
194 		flinsert(nl, rp, rp+n, l->origin);
195 		flsetselect(nl, l->p0, l->p1);
196 		if(close){
197 			flclose(l);
198 			if(l==which)
199 				which = 0;
200 		}else
201 			t->nwin++;
202 		current(nl);
203 		hcheck(t->tag);
204 	}
205 	setcursor(mousectl, cursor);
206 }
207 
208 void
209 buttons(int updown)
210 {
211 	while(((mousep->buttons&7)!=0) != updown)
212 		getmouse();
213 }
214 
215 int
216 getr(Rectangle *rp)
217 {
218 	Point p;
219 	Rectangle r;
220 
221 	*rp = getrect(3, mousectl);
222 	if(rp->max.x && rp->max.x-rp->min.x<=5 && rp->max.y-rp->min.y<=5){
223 		p = rp->min;
224 		r = cmd.l[cmd.front].entire;
225 		*rp = screen->r;
226 		if(cmd.nwin==1){
227 			if (p.y <= r.min.y)
228 				rp->max.y = r.min.y;
229 			else if (p.y >= r.max.y)
230 				rp->min.y = r.max.y;
231 			if (p.x <= r.min.x)
232 				rp->max.x = r.min.x;
233 			else if (p.x >= r.max.x)
234 				rp->min.x = r.max.x;
235 		}
236 	}
237 	return rectclip(rp, screen->r) &&
238 	   rp->max.x-rp->min.x>100 && rp->max.y-rp->min.y>40;
239 }
240 
241 void
242 snarf(Text *t, int w)
243 {
244 	Flayer *l = &t->l[w];
245 
246 	if(l->p1>l->p0){
247 		snarflen = l->p1-l->p0;
248 		outTsll(Tsnarf, t->tag, l->p0, l->p1);
249 	}
250 }
251 
252 void
253 cut(Text *t, int w, int save, int check)
254 {
255 	long p0, p1;
256 	Flayer *l;
257 
258 	l = &t->l[w];
259 	p0 = l->p0;
260 	p1 = l->p1;
261 	if(p0 == p1)
262 		return;
263 	if(p0 < 0)
264 		panic("cut");
265 	if(save)
266 		snarf(t, w);
267 	outTsll(Tcut, t->tag, p0, p1);
268 	flsetselect(l, p0, p0);
269 	t->lock++;
270 	hcut(t->tag, p0, p1-p0);
271 	if(check)
272 		hcheck(t->tag);
273 }
274 
275 void
276 paste(Text *t, int w)
277 {
278 	if(snarflen){
279 		cut(t, w, 0, 0);
280 		t->lock++;
281 		outTsl(Tpaste, t->tag, t->l[w].p0);
282 	}
283 }
284 
285 void
286 scrorigin(Flayer *l, int but, long p0)
287 {
288 	Text *t=(Text *)l->user1;
289 
290 	switch(but){
291 	case 1:
292 		outTsll(Torigin, t->tag, l->origin, p0);
293 		break;
294 	case 2:
295 		outTsll(Torigin, t->tag, p0, 1L);
296 		break;
297 	case 3:
298 		horigin(t->tag,p0);
299 	}
300 }
301 
302 int
303 alnum(int c)
304 {
305 	/*
306 	 * Hard to get absolutely right.  Use what we know about ASCII
307 	 * and assume anything above the Latin control characters is
308 	 * potentially an alphanumeric.
309 	 */
310 	if(c<=' ')
311 		return 0;
312 	if(0x7F<=c && c<=0xA0)
313 		return 0;
314 	if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
315 		return 0;
316 	return 1;
317 }
318 
319 int
320 raspc(Rasp *r, long p)
321 {
322 	ulong n;
323 	rload(r, p, p+1, &n);
324 	if(n)
325 		return scratch[0];
326 	return 0;
327 }
328 
329 long
330 ctlw(Rasp *r, long o, long p)
331 {
332 	int c;
333 
334 	if(--p < o)
335 		return o;
336 	if(raspc(r, p)=='\n')
337 		return p;
338 	for(; p>=o && !alnum(c=raspc(r, p)); --p)
339 		if(c=='\n')
340 			return p+1;
341 	for(; p>o && alnum(raspc(r, p-1)); --p)
342 		;
343 	return p>=o? p : o;
344 }
345 
346 long
347 ctlu(Rasp *r, long o, long p)
348 {
349 	for(; p-1>=o && raspc(r, p-1)!='\n'; --p)
350 		;
351 	return p>=o? p : o;
352 }
353 
354 int
355 center(Flayer *l, long a)
356 {
357 	Text *t;
358 
359 	t = l->user1;
360 	if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
361 		if(a > t->rasp.nrunes)
362 			a = t->rasp.nrunes;
363 		outTsll(Torigin, t->tag, a, 2L);
364 		return 1;
365 	}
366 	return 0;
367 }
368 
369 int
370 onethird(Flayer *l, long a)
371 {
372 	Text *t;
373 	Rectangle s;
374 	long lines;
375 
376 	t = l->user1;
377 	if(!t->lock && (a<l->origin || l->origin+l->f.nchars<a)){
378 		if(a > t->rasp.nrunes)
379 			a = t->rasp.nrunes;
380 		s = insetrect(l->scroll, 1);
381 		lines = ((s.max.y-s.min.y)/l->f.font->height+1)/3;
382 		if (lines < 2)
383 			lines = 2;
384 		outTsll(Torigin, t->tag, a, lines);
385 		return 1;
386 	}
387 	return 0;
388 }
389 
390 void
391 flushtyping(int clearesc)
392 {
393 	Text *t;
394 	ulong n;
395 
396 	if(clearesc)
397 		typeesc = -1;
398 	if(typestart == typeend) {
399 		modified = 0;
400 		return;
401 	}
402 	t = which->user1;
403 	if(t != &cmd)
404 		modified = 1;
405 	rload(&t->rasp, typestart, typeend, &n);
406 	scratch[n] = 0;
407 	if(t==&cmd && typeend==t->rasp.nrunes && scratch[typeend-typestart-1]=='\n'){
408 		setlock();
409 		outcmd();
410 	}
411 	outTslS(Ttype, t->tag, typestart, scratch);
412 	typestart = -1;
413 	typeend = -1;
414 }
415 
416 #define	SCROLLKEY	Kdown
417 #define	BACKSCROLLKEY	Kup
418 #define	ESC		0x1B
419 
420 void
421 type(Flayer *l, int res)	/* what a bloody mess this is */
422 {
423 	Text *t = (Text *)l->user1;
424 	Rune buf[100];
425 	Rune *p = buf;
426 	int c, backspacing;
427 	long a, a0;
428 	int scrollkey;
429 
430 	scrollkey = 0;
431 	if(res == RKeyboard)
432 		scrollkey = (qpeekc()==SCROLLKEY || qpeekc()==BACKSCROLLKEY);	/* ICK */
433 
434 	if(hostlock || t->lock){
435 		kbdblock();
436 		return;
437 	}
438 	a = l->p0;
439 	if(a!=l->p1 && !scrollkey){
440 		flushtyping(1);
441 		cut(t, t->front, 1, 1);
442 		return;	/* it may now be locked */
443 	}
444 	backspacing = 0;
445 	while((c = kbdchar())>0){
446 		if(res == RKeyboard){
447 			if(c==SCROLLKEY || c==BACKSCROLLKEY || c==ESC)
448 				break;
449 			/* backspace, ctrl-u, ctrl-w, del */
450 			if(c=='\b' || c==0x15 || c==0x17 || c==0x7F){
451 				backspacing = 1;
452 				break;
453 			}
454 		}
455 		*p++ = c;
456 		if(c == '\n' || p >= buf+sizeof(buf)/sizeof(buf[0]))
457 			break;
458 	}
459 	if(p > buf){
460 		if(typestart < 0)
461 			typestart = a;
462 		if(typeesc < 0)
463 			typeesc = a;
464 		hgrow(t->tag, a, p-buf, 0);
465 		t->lock++;	/* pretend we Trequest'ed for hdatarune*/
466 		hdatarune(t->tag, a, buf, p-buf);
467 		a += p-buf;
468 		l->p0 = a;
469 		l->p1 = a;
470 		typeend = a;
471 		if(c=='\n' || typeend-typestart>100)
472 			flushtyping(0);
473 		onethird(l, a);
474 	}
475 	if(c == SCROLLKEY){
476 		flushtyping(0);
477 		center(l, l->origin+l->f.nchars+1);
478 		/* backspacing immediately after outcmd(): sorry */
479 	}else if(c == BACKSCROLLKEY){
480 		flushtyping(0);
481 		a0 = l->origin-l->f.nchars;
482 		if(a0 < 0)
483 			a0 = 0;
484 		center(l, a0);
485 	}else if(backspacing && !hostlock){
486 		if(l->f.p0>0 && a>0){
487 			switch(c){
488 			case '\b':
489 			case 0x7F:	/* del */
490 				l->p0 = a-1;
491 				break;
492 			case 0x15:	/* ctrl-u */
493 				l->p0 = ctlu(&t->rasp, l->origin, a);
494 				break;
495 			case 0x17:	/* ctrl-w */
496 				l->p0 = ctlw(&t->rasp, l->origin, a);
497 				break;
498 			}
499 			l->p1 = a;
500 			if(l->p1 != l->p0){
501 				/* cut locally if possible */
502 				if(typestart<=l->p0 && l->p1<=typeend){
503 					t->lock++;	/* to call hcut */
504 					hcut(t->tag, l->p0, l->p1-l->p0);
505 					/* hcheck is local because we know rasp is contiguous */
506 					hcheck(t->tag);
507 				}else{
508 					flushtyping(0);
509 					cut(t, t->front, 0, 1);
510 				}
511 			}
512 			if(typeesc >= l->p0)
513 				typeesc = l->p0;
514 			if(typestart >= 0){
515 				if(typestart >= l->p0)
516 					typestart = l->p0;
517 				typeend = l->p0;
518 				if(typestart == typeend){
519 					typestart = -1;
520 					typeend = -1;
521 					modified = 0;
522 				}
523 			}
524 		}
525 	}else{
526 		if(c==ESC && typeesc>=0){
527 			l->p0 = typeesc;
528 			l->p1 = a;
529 			flushtyping(1);
530 		}
531 		for(l=t->l; l<&t->l[NL]; l++)
532 			if(l->textfn)
533 				flsetselect(l, l->p0, l->p1);
534 	}
535 }
536 
537 
538 void
539 outcmd(void){
540 	if(work)
541 		outTsll(Tworkfile, ((Text *)work->user1)->tag, work->p0, work->p1);
542 }
543 
544 void
545 panic(char *s)
546 {
547 	panic1(display, s);
548 }
549 
550 void
551 panic1(Display*, char *s)
552 {
553 	fprint(2, "samterm:panic: ");
554 	perror(s);
555 	abort();
556 }
557 
558 Rune*
559 gettext(Flayer *l, long n, ulong *np)
560 {
561 	Text *t;
562 
563 	t = l->user1;
564 	rload(&t->rasp, l->origin, l->origin+n, np);
565 	return scratch;
566 }
567 
568 long
569 scrtotal(Flayer *l)
570 {
571 	return ((Text *)l->user1)->rasp.nrunes;
572 }
573 
574 void*
575 alloc(ulong n)
576 {
577 	void *p;
578 
579 	p = malloc(n);
580 	if(p == 0)
581 		panic("alloc");
582 	memset(p, 0, n);
583 	return p;
584 }
585