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