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