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