xref: /illumos-gate/usr/src/tools/cscope-fast/command.c (revision 4de2612967d06c4fdbf524a62556a1e8118a006f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1988 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright (c) 1999 by Sun Microsystems, Inc.
28  * All rights reserved.
29  */
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 /*
34  *	cscope - interactive C symbol or text cross-reference
35  *
36  *	command functions
37  */
38 
39 #include <curses.h>	/* KEY_.* */
40 #include <fcntl.h>	/* O_RDONLY */
41 #include <unistd.h>
42 #include <stdio.h>
43 #include "global.h"
44 #include "library.h"
45 
46 BOOL	caseless;		/* ignore letter case when searching */
47 BOOL	*change;		/* change this line */
48 BOOL	changing;		/* changing text */
49 char	newpat[PATLEN + 1];	/* new pattern */
50 char	pattern[PATLEN + 1];	/* symbol or text pattern */
51 
52 static	char	appendprompt[] = "Append to file: ";
53 static	char	pipeprompt[] = "Pipe to shell command: ";
54 static	char	readprompt[] = "Read from file: ";
55 static	char	selectionprompt[] = "Selection: ";
56 static	char	toprompt[] = "To: ";
57 
58 static void scrollbar(MOUSEEVENT *p);
59 
60 /* execute the command */
61 
62 BOOL
63 command(int commandc)
64 {
65 	char	filename[PATHLEN + 1];	/* file path name */
66 	MOUSEEVENT *p;			/* mouse data */
67 	int	c, i;
68 	FILE	*file;
69 	HISTORY *curritem, *item;	/* command history */
70 	char	*s;
71 
72 	switch (commandc) {
73 
74 	case ctrl('C'):	/* toggle caseless mode */
75 		if (caseless == NO) {
76 			caseless = YES;
77 			putmsg2("Caseless mode is now ON");
78 		} else {
79 			caseless = NO;
80 			putmsg2("Caseless mode is now OFF");
81 		}
82 		egrepcaseless(caseless);	/* turn on/off -i flag */
83 		return (NO);
84 
85 	case ctrl('R'):	/* rebuild the cross reference */
86 		if (isuptodate == YES) {
87 			putmsg("The -d option prevents rebuilding the "
88 			    "symbol database");
89 			return (NO);
90 		}
91 		exitcurses();
92 		freefilelist();		/* remake the source file list */
93 		makefilelist();
94 		rebuild();
95 		if (errorsfound == YES) {
96 			errorsfound = NO;
97 			askforreturn();
98 		}
99 		entercurses();
100 		putmsg("");		/* clear any previous message */
101 		totallines = 0;
102 		topline = nextline = 1;
103 		break;
104 
105 	case ctrl('X'):	/* mouse selection */
106 		if ((p = getmouseevent()) == NULL) {
107 			return (NO);	/* unknown control sequence */
108 		}
109 		/* if the button number is a scrollbar tag */
110 		if (p->button == '0') {
111 			scrollbar(p);
112 			break;
113 		}
114 		/* ignore a sweep */
115 		if (p->x2 >= 0) {
116 			return (NO);
117 		}
118 		/* if this is a line selection */
119 		if (p->y1 < FLDLINE) {
120 
121 			/* find the selected line */
122 			/* note: the selection is forced into range */
123 			for (i = disprefs - 1; i > 0; --i) {
124 				if (p->y1 >= displine[i]) {
125 					break;
126 				}
127 			}
128 			/* display it in the file with the editor */
129 			editref(i);
130 		} else {	/* this is an input field selection */
131 			field = mouseselection(p, FLDLINE, FIELDS);
132 			setfield();
133 			resetcmd();
134 			return (NO);
135 		}
136 		break;
137 
138 	case '\t':	/* go to next input field */
139 	case '\n':
140 	case '\r':
141 	case ctrl('N'):
142 	case KEY_DOWN:
143 	case KEY_ENTER:
144 	case KEY_RIGHT:
145 		field = (field + 1) % FIELDS;
146 		setfield();
147 		resetcmd();
148 		return (NO);
149 
150 	case ctrl('P'):	/* go to previous input field */
151 	case KEY_UP:
152 	case KEY_LEFT:
153 		field = (field + (FIELDS - 1)) % FIELDS;
154 		setfield();
155 		resetcmd();
156 		return (NO);
157 	case KEY_HOME:	/* go to first input field */
158 		field = 0;
159 		setfield();
160 		resetcmd();
161 		return (NO);
162 
163 	case KEY_LL:	/* go to last input field */
164 		field = FIELDS - 1;
165 		setfield();
166 		resetcmd();
167 		return (NO);
168 	case ' ':	/* display next page */
169 	case '+':
170 	case ctrl('V'):
171 	case KEY_NPAGE:
172 		/* don't redisplay if there are no lines */
173 		if (totallines == 0) {
174 			return (NO);
175 		}
176 		/*
177 		 * note: seekline() is not used to move to the next
178 		 * page because display() leaves the file pointer at
179 		 * the next page to optimize paging forward
180 		 */
181 		break;
182 
183 	case '-':	/* display previous page */
184 	case KEY_PPAGE:
185 		/* don't redisplay if there are no lines */
186 		if (totallines == 0) {
187 			return (NO);
188 		}
189 		i = topline;		/* save the current top line */
190 		nextline = topline;	/* go back to this page */
191 
192 		/* if on first page but not at beginning, go to beginning */
193 		if (nextline > 1 && nextline <= mdisprefs) {
194 			nextline = 1;
195 		} else {	/* go back the maximum displayable lines */
196 			nextline -= mdisprefs;
197 
198 			/* if this was the first page, go to the last page */
199 			if (nextline < 1) {
200 				nextline = totallines - mdisprefs + 1;
201 				if (nextline < 1) {
202 					nextline = 1;
203 				}
204 				/* old top is past last line */
205 				i = totallines + 1;
206 			}
207 		}
208 		/*
209 		 * move down til the bottom line is just before the
210 		 * previous top line
211 		 */
212 		c = nextline;
213 		for (;;) {
214 			seekline(nextline);
215 			display();
216 			if (i - bottomline <= 0) {
217 				break;
218 			}
219 			nextline = ++c;
220 		}
221 		return (NO);	/* display already up to date */
222 
223 	case '>':	/* write or append the lines to a file */
224 		if (totallines == 0) {
225 			putmsg("There are no lines to write to a file");
226 		} else {	/* get the file name */
227 			(void) move(PRLINE, 0);
228 			(void) addstr("Write to file: ");
229 			s = "w";
230 			if ((c = mygetch()) == '>') {
231 				(void) move(PRLINE, 0);
232 				(void) addstr(appendprompt);
233 				c = '\0';
234 				s = "a";
235 			}
236 			if (c != '\r' && c != '\n' && c != KEY_ENTER &&
237 			    c != KEY_BREAK &&
238 			    getline(newpat, COLS - sizeof (appendprompt), c,
239 			    NO) > 0) {
240 				shellpath(filename, sizeof (filename), newpat);
241 				if ((file = fopen(filename, s)) == NULL) {
242 					cannotopen(filename);
243 				} else {
244 					seekline(1);
245 					while ((c = getc(refsfound)) != EOF) {
246 						(void) putc(c, file);
247 					}
248 					seekline(topline);
249 					(void) fclose(file);
250 				}
251 			}
252 			clearprompt();
253 		}
254 		return (NO);	/* return to the previous field */
255 
256 	case '<':	/* read lines from a file */
257 		(void) move(PRLINE, 0);
258 		(void) addstr(readprompt);
259 		if (getline(newpat, COLS - sizeof (readprompt), '\0',
260 		    NO) > 0) {
261 			clearprompt();
262 			shellpath(filename, sizeof (filename), newpat);
263 			if (readrefs(filename) == NO) {
264 				putmsg2("Ignoring an empty file");
265 				return (NO);
266 			}
267 			return (YES);
268 		}
269 		clearprompt();
270 		return (NO);
271 
272 	case '^':	/* pipe the lines through a shell command */
273 	case '|':	/* pipe the lines to a shell command */
274 		if (totallines == 0) {
275 			putmsg("There are no lines to pipe to a shell command");
276 			return (NO);
277 		}
278 		/* get the shell command */
279 		(void) move(PRLINE, 0);
280 		(void) addstr(pipeprompt);
281 		if (getline(newpat,
282 		    COLS - sizeof (pipeprompt), '\0', NO) == 0) {
283 			clearprompt();
284 			return (NO);
285 		}
286 		/* if the ^ command, redirect output to a temp file */
287 		if (commandc == '^') {
288 			(void) strcat(strcat(newpat, " >"), temp2);
289 		}
290 		exitcurses();
291 		if ((file = mypopen(newpat, "w")) == NULL) {
292 			(void) fprintf(stderr,
293 			    "cscope: cannot open pipe to shell command: %s\n",
294 			    newpat);
295 		} else {
296 			seekline(1);
297 			while ((c = getc(refsfound)) != EOF) {
298 				(void) putc(c, file);
299 			}
300 			seekline(topline);
301 			(void) mypclose(file);
302 		}
303 		if (commandc == '^') {
304 			if (readrefs(temp2) == NO) {
305 				putmsg("Ignoring empty output of ^ command");
306 			}
307 		}
308 		askforreturn();
309 		entercurses();
310 		break;
311 
312 	case ctrl('L'):	/* redraw screen */
313 	case KEY_CLEAR:
314 		(void) clearok(curscr, TRUE);
315 		(void) wrefresh(curscr);
316 		drawscrollbar(topline, bottomline, totallines);
317 		return (NO);
318 
319 	case '!':	/* shell escape */
320 		(void) execute(shell, shell, (char *)NULL);
321 		seekline(topline);
322 		break;
323 
324 	case '?':	/* help */
325 		(void) clear();
326 		help();
327 		(void) clear();
328 		seekline(topline);
329 		break;
330 
331 	case ctrl('E'):	/* edit all lines */
332 		editall();
333 		break;
334 
335 	case ctrl('A'):	/* repeat last pattern */
336 	case ctrl('Y'):	/* (old command) */
337 		if (*pattern != '\0') {
338 			(void) addstr(pattern);
339 			goto repeat;
340 		}
341 		break;
342 
343 	case ctrl('B'):		/* cmd history back */
344 	case ctrl('F'):		/* cmd history fwd */
345 		curritem = currentcmd();
346 		item = (commandc == ctrl('F')) ? nextcmd() : prevcmd();
347 		clearmsg2();
348 		if (curritem == item) {
349 			/* inform user that we're at history end */
350 			putmsg2(
351 			    "End of input field and search pattern history");
352 		}
353 		if (item) {
354 			field = item->field;
355 			setfield();
356 			atfield();
357 			(void) addstr(item->text);
358 			(void) strcpy(pattern, item->text);
359 			switch (c = mygetch()) {
360 			case '\r':
361 			case '\n':
362 			case KEY_ENTER:
363 				goto repeat;
364 			default:
365 				ungetch(c);
366 				atfield();
367 				(void) clrtoeol(); /* clear current field */
368 				break;
369 			}
370 		}
371 		return (NO);
372 
373 	case '\\':	/* next character is not a command */
374 		(void) addch('\\');	/* display the quote character */
375 
376 		/* get a character from the terminal */
377 		if ((commandc = mygetch()) == EOF) {
378 			return (NO);	/* quit */
379 		}
380 		(void) addstr("\b \b");	/* erase the quote character */
381 		goto ispat;
382 
383 	case '.':
384 		atfield();	/* move back to the input field */
385 		/* FALLTHROUGH */
386 	default:
387 		/* edit a selected line */
388 		if (isdigit(commandc) && commandc != '0' && !mouse) {
389 			if (returnrequired == NO) {
390 				editref(commandc - '1');
391 			} else {
392 				(void) move(PRLINE, 0);
393 				(void) addstr(selectionprompt);
394 				if (getline(newpat,
395 				    COLS - sizeof (selectionprompt), commandc,
396 				    NO) > 0 &&
397 				    (i = atoi(newpat)) > 0) {
398 					editref(i - 1);
399 				}
400 				clearprompt();
401 			}
402 		} else if (isprint(commandc)) {
403 			/* this is the start of a pattern */
404 ispat:
405 			if (getline(newpat, COLS - fldcolumn - 1, commandc,
406 			    caseless) > 0) {
407 					(void) strcpy(pattern, newpat);
408 					resetcmd();	/* reset history */
409 repeat:
410 				addcmd(field, pattern);	/* add to history */
411 				if (field == CHANGE) {
412 					/* prompt for the new text */
413 					(void) move(PRLINE, 0);
414 					(void) addstr(toprompt);
415 					(void) getline(newpat,
416 					    COLS - sizeof (toprompt), '\0', NO);
417 				}
418 				/* search for the pattern */
419 				if (search() == YES) {
420 					switch (field) {
421 					case DEFINITION:
422 					case FILENAME:
423 						if (totallines > 1) {
424 							break;
425 						}
426 						topline = 1;
427 						editref(0);
428 						break;
429 					case CHANGE:
430 						return (changestring());
431 					}
432 				} else if (field == FILENAME &&
433 				    access(newpat, READ) == 0) {
434 					/* try to edit the file anyway */
435 					edit(newpat, "1");
436 				}
437 			} else {	/* no pattern--the input was erased */
438 				return (NO);
439 			}
440 		} else {	/* control character */
441 			return (NO);
442 		}
443 	}
444 	return (YES);
445 }
446 
447 /* clear the prompt line */
448 
449 void
450 clearprompt(void)
451 {
452 	(void) move(PRLINE, 0);
453 	(void) clrtoeol();
454 }
455 
456 /* read references from a file */
457 
458 BOOL
459 readrefs(char *filename)
460 {
461 	FILE	*file;
462 	int	c;
463 
464 	if ((file = fopen(filename, "r")) == NULL) {
465 		cannotopen(filename);
466 		return (NO);
467 	}
468 	if ((c = getc(file)) == EOF) {	/* if file is empty */
469 		return (NO);
470 	}
471 	totallines = 0;
472 	nextline = 1;
473 	if (writerefsfound() == YES) {
474 		(void) putc(c, refsfound);
475 		while ((c = getc(file)) != EOF) {
476 			(void) putc(c, refsfound);
477 		}
478 		(void) fclose(file);
479 		(void) freopen(temp1, "r", refsfound);
480 		countrefs();
481 	}
482 	return (YES);
483 }
484 
485 /* change one text string to another */
486 
487 BOOL
488 changestring(void)
489 {
490 	char	buf[PATLEN + 1];	/* input buffer */
491 	char	newfile[PATHLEN + 1];	/* new file name */
492 	char	oldfile[PATHLEN + 1];	/* old file name */
493 	char	linenum[NUMLEN + 1];	/* file line number */
494 	char	msg[MSGLEN + 1];	/* message */
495 	FILE	*script;		/* shell script file */
496 	BOOL	anymarked = NO;		/* any line marked */
497 	MOUSEEVENT *p;			/* mouse data */
498 	int	c, i;
499 	char	*s;
500 
501 	/* open the temporary file */
502 	if ((script = fopen(temp2, "w")) == NULL) {
503 		cannotopen(temp2);
504 		return (NO);
505 	}
506 	/* create the line change indicators */
507 	change = (BOOL *)mycalloc((unsigned)totallines, sizeof (BOOL));
508 	changing = YES;
509 	initmenu();
510 
511 	/* until the quit command is entered */
512 	for (;;) {
513 		/* display the current page of lines */
514 		display();
515 	same:
516 		/* get a character from the terminal */
517 		(void) move(PRLINE, 0);
518 		(void) addstr(
519 		    "Select lines to change (press the ? key for help): ");
520 		if ((c = mygetch()) == EOF || c == ctrl('D') ||
521 		    c == ctrl('Z')) {
522 			break;	/* change lines */
523 		}
524 		/* see if the input character is a command */
525 		switch (c) {
526 		case ' ':	/* display next page */
527 		case '+':
528 		case ctrl('V'):
529 		case KEY_NPAGE:
530 		case '-':	/* display previous page */
531 		case KEY_PPAGE:
532 		case '!':	/* shell escape */
533 		case '?':	/* help */
534 			(void) command(c);
535 			break;
536 
537 		case ctrl('L'):	/* redraw screen */
538 		case KEY_CLEAR:
539 			(void) command(c);
540 			goto same;
541 
542 		case ESC:	/* kept for backwards compatibility */
543 			/* FALLTHROUGH */
544 
545 		case '\r':	/* don't change lines */
546 		case '\n':
547 		case KEY_ENTER:
548 		case KEY_BREAK:
549 		case ctrl('G'):
550 			clearprompt();
551 			goto nochange;
552 
553 		case '*':	/* mark/unmark all displayed lines */
554 			for (i = 0; topline + i < nextline; ++i) {
555 				mark(i);
556 			}
557 			goto same;
558 
559 		case 'a':	/* mark/unmark all lines */
560 			for (i = 0; i < totallines; ++i) {
561 				if (change[i] == NO) {
562 					change[i] = YES;
563 				} else {
564 					change[i] = NO;
565 				}
566 			}
567 			/* show that all have been marked */
568 			seekline(totallines);
569 			break;
570 		case ctrl('X'):	/* mouse selection */
571 			if ((p = getmouseevent()) == NULL) {
572 				goto same;	/* unknown control sequence */
573 			}
574 			/* if the button number is a scrollbar tag */
575 			if (p->button == '0') {
576 				scrollbar(p);
577 				break;
578 			}
579 			/* find the selected line */
580 			/* note: the selection is forced into range */
581 			for (i = disprefs - 1; i > 0; --i) {
582 				if (p->y1 >= displine[i]) {
583 					break;
584 				}
585 			}
586 			mark(i);
587 			goto same;
588 		default:
589 			/* if a line was selected */
590 			if (isdigit(c) && c != '0' && !mouse) {
591 				if (returnrequired == NO) {
592 					mark(c - '1');
593 				} else {
594 					clearprompt();
595 					(void) move(PRLINE, 0);
596 					(void) addstr(selectionprompt);
597 					if (getline(buf,
598 					    COLS - sizeof (selectionprompt), c,
599 					    NO) > 0 &&
600 					    (i = atoi(buf)) > 0) {
601 						mark(i - 1);
602 					}
603 				}
604 			}
605 			goto same;
606 		}
607 	}
608 	/* for each line containing the old text */
609 	(void) fprintf(script, "ed - <<\\!\nH\n");
610 	*oldfile = '\0';
611 	seekline(1);
612 	for (i = 0; fscanf(refsfound, "%s%*s%s%*[^\n]", newfile, linenum) == 2;
613 	    ++i) {
614 		/* see if the line is to be changed */
615 		if (change[i] == YES) {
616 			anymarked = YES;
617 
618 			/* if this is a new file */
619 			if (strcmp(newfile, oldfile) != 0) {
620 
621 				/* make sure it can be changed */
622 				if (access(newfile, WRITE) != 0) {
623 					(void) sprintf(msg,
624 					    "Cannot write to file %s",
625 					    newfile);
626 					putmsg(msg);
627 					anymarked = NO;
628 					break;
629 				}
630 				/* if there was an old file */
631 				if (*oldfile != '\0') {
632 					(void) fprintf(script,
633 					    "w\n");	/* save it */
634 				}
635 				/* edit the new file */
636 				(void) strcpy(oldfile, newfile);
637 				(void) fprintf(script, "e %s\n", oldfile);
638 			}
639 			/* output substitute command */
640 			(void) fprintf(script,
641 			    "%ss/", linenum);	/* change */
642 			for (s = pattern; *s != '\0'; ++s) {	/* old text */
643 				if (*s == '/') {
644 					(void) putc('\\', script);
645 				}
646 				(void) putc(*s, script);
647 			}
648 			(void) putc('/', script);			/* to */
649 			for (s = newpat; *s != '\0'; ++s) {	/* new text */
650 				if (strchr("/\\&", *s) != NULL) {
651 					(void) putc('\\', script);
652 				}
653 				(void) putc(*s, script);
654 			}
655 			(void) fprintf(script, "/gp\n");	/* and print */
656 		}
657 	}
658 	(void) fprintf(script, "w\nq\n!\n");	/* write and quit */
659 	(void) fclose(script);
660 	clearprompt();
661 
662 	/* if any line was marked */
663 	if (anymarked == YES) {
664 		/* edit the files */
665 		(void) refresh();
666 		(void) fprintf(stderr, "Changed lines:\n\r");
667 		(void) execute(shell, shell, temp2, (char *)NULL);
668 		askforreturn();
669 	}
670 nochange:
671 	changing = NO;
672 	initmenu();
673 	free(change);
674 	seekline(topline);
675 	return (YES);	/* clear any marks on exit without change */
676 }
677 
678 /* mark/unmark this displayed line to be changed */
679 
680 void
681 mark(int i)
682 {
683 	int	j;
684 
685 	j = i + topline - 1;
686 	if (j < totallines) {
687 		(void) move(displine[i], selectlen);
688 		if (change[j] == NO) {
689 			change[j] = YES;
690 			(void) addch('>');
691 		} else {
692 			change[j] = NO;
693 			(void) addch(' ');
694 		}
695 	}
696 }
697 
698 /* scrollbar actions */
699 
700 static void
701 scrollbar(MOUSEEVENT *p)
702 {
703 	/* reposition list if it makes sense */
704 	if (totallines == 0) {
705 		return;
706 	}
707 	switch (p->percent) {
708 
709 	case 101: /* scroll down one page */
710 		if (nextline + mdisprefs > totallines) {
711 			nextline = totallines - mdisprefs + 1;
712 		}
713 		break;
714 
715 	case 102: /* scroll up one page */
716 		nextline = topline - mdisprefs;
717 		if (nextline < 1) {
718 			nextline = 1;
719 		}
720 		break;
721 
722 	case 103: /* scroll down one line */
723 		nextline = topline + 1;
724 		break;
725 
726 	case 104: /* scroll up one line */
727 		if (topline > 1) {
728 			nextline = topline - 1;
729 		}
730 		break;
731 	default:
732 		nextline = p->percent * totallines / 100;
733 	}
734 	seekline(nextline);
735 }
736