xref: /plan9-contrib/sys/src/cmd/sam/sam.c (revision 219b2ee8daee37f4aad58d63f21287faa8e4ffdc)
1 #include "sam.h"
2 
3 Rune	genbuf[BLOCKSIZE];
4 int	io;
5 int	panicking;
6 int	rescuing;
7 Mod	modnum;
8 String	genstr;
9 String	rhs;
10 String	wd;
11 String	cmdstr;
12 Rune	empty[] = { 0 };
13 char	*genc;
14 File	*curfile;
15 File	*flist;
16 File	*cmd;
17 jmp_buf	mainloop;
18 List tempfile;
19 int	quitok = TRUE;
20 int	downloaded;
21 int	dflag;
22 int	Rflag;
23 char	*machine;
24 char	*home;
25 int	bpipeok;
26 int	termlocked;
27 char	*samterm = SAMTERM;
28 char	*rsamname = RSAM;
29 
30 Rune	baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
31 
32 void	usage(void);
33 
34 void
35 main(int argc, char *argv[])
36 {
37 	int i;
38 	String *t;
39 	char **ap, **arg;
40 
41 	arg = argv++;
42 	ap = argv;
43 	while(argc>1 && argv[0] && argv[0][0]=='-'){
44 		switch(argv[0][1]){
45 		case 'd':
46 			dflag++;
47 			break;
48 
49 		case 'r':
50 			--argc, argv++;
51 			if(argc == 1)
52 				usage();
53 			machine = *argv;
54 			break;
55 
56 		case 'R':
57 			Rflag++;
58 			break;
59 
60 		case 't':
61 			--argc, argv++;
62 			if(argc == 1)
63 				usage();
64 			samterm = *argv;
65 			break;
66 
67 		case 's':
68 			--argc, argv++;
69 			if(argc == 1)
70 				usage();
71 			rsamname = *argv;
72 			break;
73 
74 		default:
75 			dprint("sam: unknown flag %c\n", argv[0][1]);
76 			exits("usage");
77 		}
78 		--argc, argv++;
79 	}
80 	Strinit(&cmdstr);
81 	Strinit0(&lastpat);
82 	Strinit0(&lastregexp);
83 	Strinit0(&genstr);
84 	Strinit0(&rhs);
85 	Strinit0(&wd);
86 	tempfile.listptr = emalloc(0);
87 	Strinit0(&plan9cmd);
88 	home = getenv(HOME);
89 	if(home == 0)
90 		home = "/";
91 	if(!dflag)
92 		startup(machine, Rflag, arg, ap);
93 	Fstart();
94 	notify(notifyf);
95 	if(argc>1){
96 		for(i=0; i<argc-1; i++)
97 			if(!setjmp(mainloop)){
98 				t = tmpcstr(argv[i]);
99 				Straddc(t, '\0');
100 				Strduplstr(&genstr, t);
101 				freetmpstr(t);
102 				Fsetname(newfile(), &genstr);
103 			}
104 	}else if(!downloaded)
105 		newfile()->state = Clean;
106 	modnum++;
107 	if(file.nused)
108 		current(file.filepptr[0]);
109 	setjmp(mainloop);
110 	cmdloop();
111 	trytoquit();	/* if we already q'ed, quitok will be TRUE */
112 	exits(0);
113 }
114 
115 void
116 usage(void)
117 {
118 	dprint("usage: sam [-d] [-t samterm] [-s sam name] -r machine\n");
119 	exits("usage");
120 }
121 
122 void
123 rescue(void)
124 {
125 	int i, nblank = 0;
126 	File *f;
127 	char *c;
128 	char buf[256];
129 
130 	if(rescuing++)
131 		return;
132 	io = -1;
133 	for(i=0; i<file.nused; i++){
134 		f = file.filepptr[i];
135 		if(f==cmd || f->nrunes==0 || f->state!=Dirty)
136 			continue;
137 		if(io == -1){
138 			sprint(buf, "%s/sam.save", home);
139 			io = create(buf, 1, 0777);
140 			if(io<0)
141 				return;
142 		}
143 		if(f->name.s[0]){
144 			c = Strtoc(&f->name);
145 			strncpy(buf, c, sizeof buf-1);
146 			buf[sizeof buf-1] = 0;
147 			free(c);
148 		}else
149 			sprint(buf, "nameless.%d", nblank++);
150 		fprint(io, "#!%s '%s' $* <<'---%s'\n", SAMSAVECMD, buf, buf);
151 		addr.r.p1 = 0, addr.r.p2 = f->nrunes;
152 		writeio(f);
153 		fprint(io, "\n---%s\n", (char *)buf);
154 	}
155 }
156 
157 void
158 panic(char *s)
159 {
160 	int wasd;
161 
162 	if(!panicking++ && !setjmp(mainloop)){
163 		wasd = downloaded;
164 		downloaded = 0;
165 		dprint("sam: panic: %s: %r\n", s);
166 		if(wasd)
167 			fprint(2, "sam: panic: %s: %r\n", s);
168 		rescue();
169 		abort();
170 	}
171 }
172 
173 void
174 hiccough(char *s)
175 {
176 	if(rescuing)
177 		exits("rescue");
178 	if(s)
179 		dprint("%s\n", s);
180 	resetcmd();
181 	resetxec();
182 	resetsys();
183 	if(io > 0)
184 		close(io);
185 	if(undobuf->nrunes)
186 		Bdelete(undobuf, (Posn)0, undobuf->nrunes);
187 	update();
188 	if (curfile) {
189 		if (curfile->state==Unread)
190 			curfile->state = Clean;
191 		else if (downloaded)
192 			outTs(Hcurrent, curfile->tag);
193 	}
194 	longjmp(mainloop, 1);
195 }
196 
197 void
198 intr(void)
199 {
200 	error(Eintr);
201 }
202 
203 void
204 trytoclose(File *f)
205 {
206 	char *t;
207 	char buf[256];
208 
209 	if(f == cmd)	/* possible? */
210 		return;
211 	if(f->deleted)
212 		return;
213 	if(f->state==Dirty && !f->closeok){
214 		f->closeok = TRUE;
215 		if(f->name.s[0]){
216 			t = Strtoc(&f->name);
217 			strncpy(buf, t, sizeof buf-1);
218 			free(t);
219 		}else
220 			strcpy(buf, "nameless file");
221 		error_s(Emodified, buf);
222 	}
223 	f->deleted = TRUE;
224 }
225 
226 void
227 trytoquit(void)
228 {
229 	int c;
230 	File *f;
231 
232 	if(!quitok)
233 {
234 		for(c = 0; c<file.nused; c++){
235 			f = file.filepptr[c];
236 			if(f!=cmd && f->state==Dirty){
237 				quitok = TRUE;
238 				eof = FALSE;
239 				error(Echanges);
240 			}
241 		}
242 }
243 }
244 
245 void
246 load(File *f)
247 {
248 	Address saveaddr;
249 
250 	Strduplstr(&genstr, &f->name);
251 	filename(f);
252 	if(f->name.s[0]){
253 		saveaddr = addr;
254 		edit(f, 'I');
255 		addr = saveaddr;
256 	}else
257 		f->state = Clean;
258 	Fupdate(f, TRUE, TRUE);
259 }
260 
261 void
262 cmdupdate(void)
263 {
264 	if(cmd && cmd->mod!=0){
265 		Fupdate(cmd, FALSE, downloaded);
266 		cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->nrunes;
267 		telldot(cmd);
268 	}
269 }
270 
271 void
272 delete(File *f)
273 {
274 	if(downloaded && f->rasp)
275 		outTs(Hclose, f->tag);
276 	delfile(f);
277 	if(f == curfile)
278 		current(0);
279 }
280 
281 void
282 update(void)
283 {
284 	int i, anymod;
285 	File *f;
286 
287 	settempfile();
288 	for(anymod = i=0; i<tempfile.nused; i++){
289 		f = tempfile.filepptr[i];
290 		if(f==cmd)	/* cmd gets done in main() */
291 			continue;
292 		if(f->deleted)
293 			delete(f);
294 		if(f->mod==modnum && Fupdate(f, FALSE, downloaded))
295 			anymod++;
296 		if(f->rasp)
297 			telldot(f);
298 	}
299 	if(anymod)
300 		modnum++;
301 }
302 
303 File *
304 current(File *f)
305 {
306 	return curfile = f;
307 }
308 
309 void
310 edit(File *f, int cmd)
311 {
312 	int empty = TRUE;
313 	Posn p;
314 	int nulls;
315 
316 	if(cmd == 'r')
317 		Fdelete(f, addr.r.p1, addr.r.p2);
318 	if(cmd=='e' || cmd=='I'){
319 		Fdelete(f, (Posn)0, f->nrunes);
320 		addr.r.p2 = f->nrunes;
321 	}else if(f->nrunes!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
322 		empty = FALSE;
323 	if((io = open(genc, OREAD))<0) {
324 		if (curfile && curfile->state == Unread)
325 			curfile->state = Clean;
326 		error_s(Eopen, genc);
327 	}
328 	p = readio(f, &nulls, empty);
329 	closeio((cmd=='e' || cmd=='I')? -1 : p);
330 	if(cmd == 'r')
331 		f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
332 	else
333 		f->ndot.r.p1 = f->ndot.r.p2 = 0;
334 	f->closeok = empty;
335 	if (quitok)
336 		quitok = empty;
337 	else
338 		quitok = FALSE;
339 	state(f, empty && !nulls? Clean : Dirty);
340 	if(cmd == 'e')
341 		filename(f);
342 }
343 
344 int
345 getname(File *f, String *s, int save)
346 {
347 	int c, i;
348 
349 	Strzero(&genstr);
350 	if(genc){
351 		free(genc);
352 		genc = 0;
353 	}
354 	if(s==0 || (c = s->s[0])==0){		/* no name provided */
355 		if(f)
356 			Strduplstr(&genstr, &f->name);
357 		else
358 			Straddc(&genstr, '\0');
359 		goto Return;
360 	}
361 	if(c!=' ' && c!='\t')
362 		error(Eblank);
363 	for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
364 		;
365 	while(s->s[i] > ' ')
366 		Straddc(&genstr, s->s[i++]);
367 	if(s->s[i])
368 		error(Enewline);
369 	Straddc(&genstr, '\0');
370 	if(f && (save || f->name.s[0]==0)){
371 		Fsetname(f, &genstr);
372 		if(Strcmp(&f->name, &genstr)){
373 			quitok = f->closeok = FALSE;
374 			f->qid = 0;
375 			f->date = 0;
376 			state(f, Dirty); /* if it's 'e', fix later */
377 		}
378 	}
379     Return:
380 	genc = Strtoc(&genstr);
381 	return genstr.n-1;	/* strlen(name) */
382 }
383 
384 void
385 filename(File *f)
386 {
387 	if(genc)
388 		free(genc);
389 	genc = Strtoc(&genstr);
390 	dprint("%c%c%c %s\n", " '"[f->state==Dirty],
391 		"-+"[f->rasp!=0], " ."[f==curfile], genc);
392 }
393 
394 void
395 undostep(File *f)
396 {
397 	Buffer *t;
398 	int changes;
399 	Mark mark;
400 
401 	t = f->transcript;
402 	changes = Fupdate(f, TRUE, TRUE);
403 	Bread(t, (Rune*)&mark, (sizeof mark)/RUNESIZE, f->markp);
404 	Bdelete(t, f->markp, t->nrunes);
405 	f->markp = mark.p;
406 	f->dot.r = mark.dot;
407 	f->ndot.r = mark.dot;
408 	f->mark = mark.mark;
409 	f->mod = mark.m;
410 	f->closeok = mark.s1!=Dirty;
411 	if(mark.s1==Dirty)
412 		quitok = FALSE;
413 	if(f->state==Clean && mark.s1==Clean && changes)
414 		state(f, Dirty);
415 	else
416 		state(f, mark.s1);
417 }
418 
419 int
420 undo(void)
421 {
422 	File *f;
423 	int i;
424 	Mod max;
425 	if((max = curfile->mod)==0)
426 		return 0;
427 	settempfile();
428 	for(i = 0; i<tempfile.nused; i++){
429 		f = tempfile.filepptr[i];
430 		if(f!=cmd && f->mod==max)
431 			undostep(f);
432 	}
433 	return 1;
434 }
435 
436 int
437 readcmd(String *s)
438 {
439 	int retcode;
440 
441 	if(flist == 0)
442 		(flist = Fopen())->state = Clean;
443 	addr.r.p1 = 0, addr.r.p2 = flist->nrunes;
444 	retcode = plan9(flist, '<', s, FALSE);
445 	Fupdate(flist, FALSE, FALSE);
446 	flist->mod = 0;
447 	if (flist->nrunes > BLOCKSIZE)
448 		error(Etoolong);
449 	Strzero(&genstr);
450 	Strinsure(&genstr, flist->nrunes);
451 	Fchars(flist, genbuf, (Posn)0, flist->nrunes);
452 	memmove(genstr.s, genbuf, flist->nrunes*RUNESIZE);
453 	genstr.n = flist->nrunes;
454 	Straddc(&genstr, '\0');
455 	return retcode;
456 }
457 
458 void
459 cd(String *str)
460 {
461 	int i;
462 	File *f;
463 	String *t;
464 
465 	t = tmpcstr("/bin/pwd");
466 	Straddc(t, '\0');
467 	if (flist) {
468 		Fclose(flist);
469 		flist = 0;
470 	}
471 	if (readcmd(t) != 0) {
472 		Strduplstr(&genstr, tmprstr(baddir, sizeof(baddir)/sizeof(Rune)));
473 		Straddc(&genstr, '\0');
474 	}
475 	freetmpstr(t);
476 	Strduplstr(&wd, &genstr);
477 	if(wd.s[0] == 0){
478 		wd.n = 0;
479 		warn(Wpwd);
480 	}else if(wd.s[wd.n-2] == '\n'){
481 		--wd.n;
482 		wd.s[wd.n-1]='/';
483 	}
484 	if(chdir(getname((File *)0, str, FALSE)? genc : home))
485 		syserror("chdir");
486 	settempfile();
487 	for(i=0; i<tempfile.nused; i++){
488 		f = tempfile.filepptr[i];
489 		if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
490 			Strinsert(&f->name, &wd, (Posn)0);
491 			sortname(f);
492 		}
493 	}
494 }
495 
496 int
497 loadflist(String *s)
498 {
499 	int c, i;
500 
501 	c = s->s[0];
502 	for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
503 		;
504 	if((c==' ' || c=='\t') && s->s[i]!='\n'){
505 		if(s->s[i]=='<'){
506 			Strdelete(s, 0L, (long)i+1);
507 			readcmd(s);
508 		}else{
509 			Strzero(&genstr);
510 			while((c = s->s[i++]) && c!='\n')
511 				Straddc(&genstr, c);
512 			Straddc(&genstr, '\0');
513 		}
514 	}else{
515 		if(c != '\n')
516 			error(Eblank);
517 		Strdupl(&genstr, empty);
518 	}
519 	if(genc)
520 		free(genc);
521 	genc = Strtoc(&genstr);
522 	return genstr.s[0];
523 }
524 
525 File *
526 readflist(int readall, int delete)
527 {
528 	Posn i;
529 	int c;
530 	File *f;
531 	String *t;
532 
533 	for(i=0,f=0; f==0 || readall || delete; i++){	/* ++ skips blank */
534 		Strdelete(&genstr, (Posn)0, i);
535 		for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
536 			;
537 		if(i >= genstr.n)
538 			break;
539 		Strdelete(&genstr, (Posn)0, i);
540 		for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
541 			;
542 
543 		if(i == 0)
544 			break;
545 		genstr.s[i] = 0;
546 		t = tmprstr(genstr.s, i+1);
547 		f = lookfile(t);
548 		if(delete){
549 			if(f == 0)
550 				warn_S(Wfile, t);
551 			else
552 				trytoclose(f);
553 		}else if(f==0 && readall)
554 			Fsetname(f = newfile(), t);
555 	}
556 	return f;
557 }
558 
559 File *
560 tofile(String *s)
561 {
562 	File *f;
563 
564 	if(s->s[0] != ' ')
565 		error(Eblank);
566 	if(loadflist(s) == 0){
567 		f = lookfile(&genstr);	/* empty string ==> nameless file */
568 		if(f == 0)
569 			error_s(Emenu, genc);
570 	}else if((f=readflist(FALSE, FALSE)) == 0)
571 		error_s(Emenu, genc);
572 	return current(f);
573 }
574 
575 File *
576 getfile(String *s)
577 {
578 	File *f;
579 
580 	if(loadflist(s) == 0)
581 		Fsetname(f = newfile(), &genstr);
582 	else if((f=readflist(TRUE, FALSE)) == 0)
583 		error(Eblank);
584 	return current(f);
585 }
586 
587 void
588 closefiles(File *f, String *s)
589 {
590 	if(s->s[0] == 0){
591 		if(f == 0)
592 			error(Enofile);
593 		trytoclose(f);
594 		return;
595 	}
596 	if(s->s[0] != ' ')
597 		error(Eblank);
598 	if(loadflist(s) == 0)
599 		error(Enewline);
600 	readflist(FALSE, TRUE);
601 }
602 
603 void
604 copy(File *f, Address addr2)
605 {
606 	Posn p;
607 	int ni;
608 	for(p=addr.r.p1; p<addr.r.p2; p+=ni){
609 		ni = addr.r.p2-p;
610 		if(ni > BLOCKSIZE)
611 			ni = BLOCKSIZE;
612 		Fchars(f, genbuf, p, p+ni);
613 		Finsert(addr2.f, tmprstr(genbuf, ni), addr2.r.p2);
614 	}
615 	addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
616 	addr2.f->ndot.r.p1 = addr2.r.p2;
617 }
618 
619 void
620 move(File *f, Address addr2)
621 {
622 	if(addr.r.p2 <= addr2.r.p2){
623 		Fdelete(f, addr.r.p1, addr.r.p2);
624 		copy(f, addr2);
625 	}else if(addr.r.p1 >= addr2.r.p2){
626 		copy(f, addr2);
627 		Fdelete(f, addr.r.p1, addr.r.p2);
628 	}else
629 		error(Eoverlap);
630 }
631 
632 Posn
633 nlcount(File *f, Posn p0, Posn p1)
634 {
635 	Posn nl = 0;
636 
637 	Fgetcset(f, p0);
638 	while(p0++<p1)
639 		if(Fgetc(f)=='\n')
640 			nl++;
641 	return nl;
642 }
643 
644 void
645 printposn(File *f, int charsonly)
646 {
647 	Posn l1, l2;
648 
649 	if(!charsonly){
650 		l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
651 		l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
652 		/* check if addr ends with '\n' */
653 		if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && (Fgetcset(f, addr.r.p2-1),Fgetc(f)=='\n'))
654 			--l2;
655 		dprint("%lud", l1);
656 		if(l2 != l1)
657 			dprint(",%lud", l2);
658 		dprint("; ");
659 	}
660 	dprint("#%lud", addr.r.p1);
661 	if(addr.r.p2 != addr.r.p1)
662 		dprint(",#%lud", addr.r.p2);
663 	dprint("\n");
664 }
665 
666 void
667 settempfile(void)
668 {
669 	if(tempfile.nalloc < file.nused){
670 		free(tempfile.listptr);
671 		tempfile.listptr = emalloc(sizeof(*tempfile.filepptr)*file.nused);
672 		tempfile.nalloc = file.nused;
673 	}
674 	tempfile.nused = file.nused;
675 	memmove(&tempfile.filepptr[0], &file.filepptr[0], file.nused*sizeof(File*));
676 }
677