xref: /386bsd/usr/src/usr.bin/elvis/vcmd.c (revision a2142627)
1 /* vcmd.c */
2 
3 /* Author:
4  *	Steve Kirkendall
5  *	14407 SW Teal Blvd. #C
6  *	Beaverton, OR 97005
7  *	kirkenda@cs.pdx.edu
8  */
9 
10 
11 /* This file contains the functions that handle VI commands */
12 
13 
14 #include "config.h"
15 #include "ctype.h"
16 #include "vi.h"
17 #if MSDOS
18 # include <process.h>
19 # include <string.h>
20 #endif
21 #if TOS
22 # include <osbind.h>
23 # include <string.h>
24 #endif
25 #if OSK
26 # include <stdio.h>
27 #endif
28 
29 
30 /* This function puts the editor in EX mode */
v_quit()31 MARK v_quit()
32 {
33 	move(LINES - 1, 0);
34 	mode = MODE_EX;
35 	return cursor;
36 }
37 
38 /* This function causes the screen to be redrawn */
v_redraw()39 MARK v_redraw()
40 {
41 	redraw(MARK_UNSET, FALSE);
42 	return cursor;
43 }
44 
45 /* This function executes a string of EX commands, and waits for a user keystroke
46  * before returning to the VI screen.  If that keystroke is another ':', then
47  * another EX command is read and executed.
48  */
49 /*ARGSUSED*/
v_1ex(m,text)50 MARK v_1ex(m, text)
51 	MARK	m;	/* the current line */
52 	char	*text;	/* the first command to execute */
53 {
54 	/* run the command.  be careful about modes & output */
55 	exwrote = (mode == MODE_COLON);
56 	doexcmd(text);
57 	exrefresh();
58 
59 	/* if mode is no longer MODE_VI, then we should quit right away! */
60 	if (mode != MODE_VI && mode != MODE_COLON)
61 		return cursor;
62 
63 	/* The command did some output.  Wait for a keystoke. */
64 	if (exwrote)
65 	{
66 		mode = MODE_VI;
67 		msg("[Hit <RETURN> to continue]");
68 		if (getkey(0) == ':')
69 		{	mode = MODE_COLON;
70 			addch('\n');
71 		}
72 		else
73 			redraw(MARK_UNSET, FALSE);
74 	}
75 
76 	return cursor;
77 }
78 
79 /* This function undoes the last change */
80 /*ARGSUSED*/
v_undo(m)81 MARK v_undo(m)
82 	MARK	m;	/* (ignored) */
83 {
84 	if (undo())
85 	{
86 		redraw(MARK_UNSET, FALSE);
87 	}
88 	return cursor;
89 }
90 
91 /* This function deletes the character(s) that the cursor is on */
v_xchar(m,cnt,cmd)92 MARK v_xchar(m, cnt, cmd)
93 	MARK	m;	/* where to start deletions */
94 	long	cnt;	/* number of chars to delete */
95 	int	cmd;	/* either 'x' or 'X' */
96 {
97 	DEFAULT(1);
98 
99 	/* for 'X', adjust so chars are deleted *BEFORE* cursor */
100 	if (cmd == 'X')
101 	{
102 		if (markidx(m) < cnt)
103 			return MARK_UNSET;
104 		m -= cnt;
105 	}
106 
107 	/* make sure we don't try to delete more thars than there are */
108 	pfetch(markline(m));
109 	if (markidx(m + cnt) > plen)
110 	{
111 		cnt = plen - markidx(m);
112 	}
113 	if (cnt == 0L)
114 	{
115 		return MARK_UNSET;
116 	}
117 
118 	/* do it */
119 	ChangeText
120 	{
121 		cut(m, m + cnt);
122 		delete(m, m + cnt);
123 	}
124 	return m;
125 }
126 
127 /* This function defines a mark */
128 /*ARGSUSED*/
v_mark(m,count,key)129 MARK v_mark(m, count, key)
130 	MARK	m;	/* where the mark will be */
131 	long	count;	/* (ignored) */
132 	int	key;	/* the ASCII label of the mark */
133 {
134 	if (key < 'a' || key > 'z')
135 	{
136 		msg("Marks must be from a to z");
137 	}
138 	else
139 	{
140 		mark[key - 'a'] = m;
141 	}
142 	return m;
143 }
144 
145 /* This function toggles upper & lower case letters */
v_ulcase(m,cnt)146 MARK v_ulcase(m, cnt)
147 	MARK	m;	/* where to make the change */
148 	long	cnt;	/* number of chars to flip */
149 {
150 	REG char 	*pos;
151 	REG int		j;
152 
153 	DEFAULT(1);
154 
155 	/* fetch the current version of the line */
156 	pfetch(markline(m));
157 
158 	/* for each position in the line */
159 	for (j = 0, pos = &ptext[markidx(m)]; j < cnt && *pos; j++, pos++)
160 	{
161 		if (isupper(*pos))
162 		{
163 			tmpblk.c[j] = tolower(*pos);
164 		}
165 		else
166 		{
167 			tmpblk.c[j] = toupper(*pos);
168 		}
169 	}
170 
171 	/* if the new text is different from the old, then change it */
172 	if (strncmp(tmpblk.c, &ptext[markidx(m)], j))
173 	{
174 		ChangeText
175 		{
176 			tmpblk.c[j] = '\0';
177 			change(m, m + j, tmpblk.c);
178 		}
179 	}
180 
181 	return m + j;
182 }
183 
184 
v_replace(m,cnt,key)185 MARK v_replace(m, cnt, key)
186 	MARK	m;	/* first char to be replaced */
187 	long	cnt;	/* number of chars to replace */
188 	int	key;	/* what to replace them with */
189 {
190 	REG char	*text;
191 	REG int		i;
192 
193 	DEFAULT(1);
194 
195 	/* map ^M to '\n' */
196 	if (key == '\r')
197 	{
198 		key = '\n';
199 	}
200 
201 	/* make sure the resulting line isn't too long */
202 	if (cnt > BLKSIZE - 2 - markidx(m))
203 	{
204 		cnt = BLKSIZE - 2 - markidx(m);
205 	}
206 
207 	/* build a string of the desired character with the desired length */
208 	for (text = tmpblk.c, i = cnt; i > 0; i--)
209 	{
210 		*text++ = key;
211 	}
212 	*text = '\0';
213 
214 	/* make sure cnt doesn't extend past EOL */
215 	pfetch(markline(m));
216 	key = markidx(m);
217 	if (key + cnt > plen)
218 	{
219 		cnt = plen - key;
220 	}
221 
222 	/* do the replacement */
223 	ChangeText
224 	{
225 		change(m, m + cnt, tmpblk.c);
226 	}
227 
228 	if (*tmpblk.c == '\n')
229 	{
230 		return (m & ~(BLKSIZE - 1)) + cnt * BLKSIZE;
231 	}
232 	else
233 	{
234 		return m + cnt - 1;
235 	}
236 }
237 
v_overtype(m)238 MARK v_overtype(m)
239 	MARK		m;	/* where to start overtyping */
240 {
241 	MARK		end;	/* end of a substitution */
242 	static long	width;	/* width of a single-line replace */
243 
244 	/* the "doingdot" version of replace is really a substitution */
245 	if (doingdot)
246 	{
247 		/* was the last one really repeatable? */
248 		if (width < 0)
249 		{
250 			msg("Can't repeat a multi-line overtype command");
251 			return MARK_UNSET;
252 		}
253 
254 		/* replacing nothing by nothing?  Don't bother */
255 		if (width == 0)
256 		{
257 			return m;
258 		}
259 
260 		/* replace some chars by repeated text */
261 		return v_subst(m, width);
262 	}
263 
264 	/* Normally, we input starting here, in replace mode */
265 	ChangeText
266 	{
267 		end = input(m, m, WHEN_VIREP, FALSE);
268 	}
269 
270 	/* if we ended on the same line we started on, then this
271 	 * overtype is repeatable via the dot key.
272 	 */
273 	if (markline(end) == markline(m) && end >= m - 1L)
274 	{
275 		width = end - m + 1L;
276 	}
277 	else /* it isn't repeatable */
278 	{
279 		width = -1L;
280 	}
281 
282 	return end;
283 }
284 
285 
286 /* This function selects which cut buffer to use */
287 /*ARGSUSED*/
v_selcut(m,cnt,key)288 MARK v_selcut(m, cnt, key)
289 	MARK	m;
290 	long	cnt;
291 	int	key;
292 {
293 	cutname(key);
294 	return m;
295 }
296 
297 /* This function pastes text from a cut buffer */
298 /*ARGSUSED*/
v_paste(m,cnt,cmd)299 MARK v_paste(m, cnt, cmd)
300 	MARK	m;	/* where to paste the text */
301 	long	cnt;	/* (ignored) */
302 	int	cmd;	/* either 'p' or 'P' */
303 {
304 	MARK	dest;
305 
306 	ChangeText
307 	{
308 		/* paste the text, and find out where it ends */
309 		dest = paste(m, cmd == 'p', TRUE);
310 
311 		/* was that a line-mode paste? */
312 		if (dest && markline(dest) != markline(m))
313 		{
314 			/* line-mode pastes leave the cursor at the front
315 			 * of the first pasted line.
316 			 */
317 			dest = m;
318 			if (cmd == 'p')
319 			{
320 				dest += BLKSIZE;
321 			}
322 			force_flags |= FRNT;
323 		}
324 	}
325 	return dest;
326 }
327 
328 /* This function yanks text into a cut buffer */
v_yank(m,n)329 MARK v_yank(m, n)
330 	MARK	m, n;	/* range of text to yank */
331 {
332 	cut(m, n);
333 	return m;
334 }
335 
336 /* This function deletes a range of text */
v_delete(m,n)337 MARK v_delete(m, n)
338 	MARK	m, n;	/* range of text to delete */
339 {
340 	/* illegal to try and delete nothing */
341 	if (n <= m)
342 	{
343 		return MARK_UNSET;
344 	}
345 
346 	/* Do it */
347 	ChangeText
348 	{
349 		cut(m, n);
350 		delete(m, n);
351 	}
352 	return m;
353 }
354 
355 
356 /* This starts input mode without deleting anything */
v_insert(m,cnt,key)357 MARK v_insert(m, cnt, key)
358 	MARK	m;	/* where to start (sort of) */
359 	long	cnt;	/* repeat how many times? */
360 	int	key;	/* what command is this for? {a,A,i,I,o,O} */
361 {
362 	int	wasdot;
363 	long	reps;
364 	int	above;	/* boolean: new line going above old line? */
365 
366 	DEFAULT(1);
367 
368 	ChangeText
369 	{
370 		/* tweak the insertion point, based on command key */
371 		above = FALSE;
372 		switch (key)
373 		{
374 		  case 'i':
375 			break;
376 
377 		  case 'a':
378 			pfetch(markline(m));
379 			if (plen > 0)
380 			{
381 				m++;
382 			}
383 			break;
384 
385 		  case 'I':
386 			m = m_front(m, 1L);
387 			break;
388 
389 		  case 'A':
390 			pfetch(markline(m));
391 			m = (m & ~(BLKSIZE - 1)) + plen;
392 			break;
393 
394 		  case 'O':
395 			m &= ~(BLKSIZE - 1);
396 			add(m, "\n");
397 			above = TRUE;
398 			break;
399 
400 		  case 'o':
401 			m = (m & ~(BLKSIZE - 1)) + BLKSIZE;
402 			add(m, "\n");
403 			break;
404 		}
405 
406 		/* insert the same text once or more */
407 		for (reps = cnt, wasdot = doingdot; reps > 0; reps--, doingdot = TRUE)
408 		{
409 			m = input(m, m, WHEN_VIINP, above) + 1;
410 		}
411 		if (markidx(m) > 0)
412 		{
413 			m--;
414 		}
415 
416 		doingdot = wasdot;
417 	}
418 
419 #ifndef CRUNCH
420 # ifndef NO_EXTENSIONS
421 	if (key == 'i' && *o_inputmode && mode == MODE_VI)
422 	{
423 		msg("Now in command mode!  To return to input mode, hit <i>");
424 	}
425 # endif
426 #endif
427 
428 	return m;
429 }
430 
431 /* This starts input mode with some text deleted */
v_change(m,n)432 MARK v_change(m, n)
433 	MARK	m, n;	/* the range of text to change */
434 {
435 	int	lnmode;	/* is this a line-mode change? */
436 
437 	/* swap them if they're in reverse order */
438 	if (m > n)
439 	{
440 		MARK	tmp;
441 		tmp = m;
442 		m = n;
443 		n = tmp;
444 	}
445 
446 	/* for line mode, retain the last newline char */
447 	lnmode = (markidx(m) == 0 && markidx(n) == 0 && m != n);
448 	if (lnmode)
449 	{
450 		n -= BLKSIZE;
451 		pfetch(markline(n));
452 		n = (n & ~(BLKSIZE - 1)) + plen;
453 	}
454 
455 	ChangeText
456 	{
457 		cut(m, n);
458 		m = input(m, n, WHEN_VIINP, FALSE);
459 	}
460 
461 	return m;
462 }
463 
464 /* This function replaces a given number of characters with input */
v_subst(m,cnt)465 MARK v_subst(m, cnt)
466 	MARK	m;	/* where substitutions start */
467 	long	cnt;	/* number of chars to replace */
468 {
469 	DEFAULT(1);
470 
471 	/* make sure we don't try replacing past EOL */
472 	pfetch(markline(m));
473 	if (markidx(m) + cnt > plen)
474 	{
475 		cnt = plen - markidx(m);
476 	}
477 
478 	/* Go for it! */
479 	ChangeText
480 	{
481 		cut(m, m + cnt);
482 		m = input(m, m + cnt, WHEN_VIINP, FALSE);
483 	}
484 	return m;
485 }
486 
487 /* This calls the ex "join" command to join some lines together */
v_join(m,cnt)488 MARK v_join(m, cnt)
489 	MARK	m;	/* the first line to be joined */
490 	long	cnt;	/* number of other lines to join */
491 {
492 	MARK	joint;	/* where the lines were joined */
493 
494 	DEFAULT(1);
495 
496 	/* figure out where the joint will be */
497 	pfetch(markline(m));
498 	joint = (m & ~(BLKSIZE - 1)) + plen;
499 
500 	/* join the lines */
501 	cmd_join(m, m + MARK_AT_LINE(cnt), CMD_JOIN, 0, "");
502 
503 	/* the cursor should be left at the joint */
504 	return joint;
505 }
506 
507 
508 /* This calls the ex "<" command to shift some lines left */
v_lshift(m,n)509 MARK v_lshift(m, n)
510 	MARK	m, n;	/* range of lines to shift */
511 {
512 	/* adjust for inclusive endmarks in ex */
513 	n -= BLKSIZE;
514 
515 	cmd_shift(m, n, CMD_SHIFTL, FALSE, (char *)0);
516 
517 	return m;
518 }
519 
520 /* This calls the ex ">" command to shift some lines right */
v_rshift(m,n)521 MARK v_rshift(m, n)
522 	MARK	m, n;	/* range of lines to shift */
523 {
524 	/* adjust for inclusive endmarks in ex */
525 	n -= BLKSIZE;
526 
527 	cmd_shift(m, n, CMD_SHIFTR, FALSE, (char *)0);
528 
529 	return m;
530 }
531 
532 /* This filters some lines through a preset program, to reformat them */
v_reformat(m,n)533 MARK v_reformat(m, n)
534 	MARK	m, n;	/* range of lines to shift */
535 {
536 	/* adjust for inclusive endmarks in ex */
537 	n -= BLKSIZE;
538 
539 	/* run the filter command */
540 	filter(m, n, o_equalprg, TRUE);
541 
542 	redraw(MARK_UNSET, FALSE);
543 	return m;
544 }
545 
546 
547 /* This runs some lines through a filter program */
v_filter(m,n)548 MARK v_filter(m, n)
549 	MARK	m, n;	/* range of lines to shift */
550 {
551 	char	cmdln[150];	/* a shell command line */
552 
553 	/* adjust for inclusive endmarks in ex */
554 	n -= BLKSIZE;
555 
556 	if (vgets('!', cmdln, sizeof(cmdln)) > 0)
557 	{
558 		filter(m, n, cmdln, TRUE);
559 	}
560 
561 	redraw(MARK_UNSET, FALSE);
562 	return m;
563 }
564 
565 
566 /* This function runs the ex "file" command to show the file's status */
v_status()567 MARK v_status()
568 {
569 	cmd_file(cursor, cursor, CMD_FILE, 0, "");
570 	return cursor;
571 }
572 
573 
574 /* This function runs the ":&" command to repeat the previous :s// */
v_again(m,n)575 MARK v_again(m, n)
576 	MARK	m, n;
577 {
578 	cmd_substitute(m, n - BLKSIZE, CMD_SUBAGAIN, TRUE, "");
579 	return cursor;
580 }
581 
582 
583 
584 /* This function switches to the previous file, if possible */
v_switch()585 MARK v_switch()
586 {
587 	if (!*prevorig)
588 		msg("No previous file");
589 	else
590 	{	strcpy(tmpblk.c, prevorig);
591 		cmd_edit(cursor, cursor, CMD_EDIT, 0, tmpblk.c);
592 	}
593 	return cursor;
594 }
595 
596 /* This function does a tag search on a keyword */
597 /*ARGSUSED*/
v_tag(keyword,m,cnt)598 MARK v_tag(keyword, m, cnt)
599 	char	*keyword;
600 	MARK	m;
601 	long	cnt;
602 {
603 	/* move the cursor to the start of the tag name, where m is */
604 	cursor = m;
605 
606 	/* perform the tag search */
607 	cmd_tag(cursor, cursor, CMD_TAG, 0, keyword);
608 
609 	return cursor;
610 }
611 
612 #ifndef NO_EXTENSIONS
613 /* This function looks up a keyword by calling the helpprog program */
614 /*ARGSUSED*/
v_keyword(keyword,m,cnt)615 MARK v_keyword(keyword, m, cnt)
616 	char	*keyword;
617 	MARK	m;
618 	long	cnt;
619 {
620 	int	waswarn;
621 	char	cmdline[130];
622 
623 	move(LINES - 1, 0);
624 	addstr("---------------------------------------------------------\n");
625 	clrtoeol();
626 	refresh();
627 	sprintf(cmdline, "%s %s", o_keywordprg, keyword);
628 	waswarn = *o_warn;
629 	*o_warn = FALSE;
630 	suspend_curses();
631 	if (system(cmdline))
632 	{
633 		addstr("<<< failed >>>\n");
634 	}
635 	resume_curses(FALSE);
636 	mode = MODE_VI;
637 	redraw(MARK_UNSET, FALSE);
638 	*o_warn = waswarn;
639 
640 	return m;
641 }
642 
643 
644 
v_increment(keyword,m,cnt)645 MARK v_increment(keyword, m, cnt)
646 	char	*keyword;
647 	MARK	m;
648 	long	cnt;
649 {
650 	static	sign;
651 	char	newval[12];
652 	long	atol();
653 
654 	DEFAULT(1);
655 
656 	/* get one more keystroke, unless doingdot */
657 	if (!doingdot)
658 	{
659 		sign = getkey(0);
660 	}
661 
662 	/* adjust the number, based on that second keystroke */
663 	switch (sign)
664 	{
665 	  case '+':
666 	  case '#':
667 		cnt = atol(keyword) + cnt;
668 		break;
669 
670 	  case '-':
671 		cnt = atol(keyword) - cnt;
672 		break;
673 
674 	  case '=':
675 		break;
676 
677 	  default:
678 		return MARK_UNSET;
679 	}
680 	sprintf(newval, "%ld", cnt);
681 
682 	ChangeText
683 	{
684 		change(m, m + strlen(keyword), newval);
685 	}
686 
687 	return m;
688 }
689 #endif
690 
691 
692 /* This function acts like the EX command "xit" */
693 /*ARGSUSED*/
v_xit(m,cnt,key)694 MARK v_xit(m, cnt, key)
695 	MARK	m;	/* ignored */
696 	long	cnt;	/* ignored */
697 	int	key;	/* must be a second 'Z' */
698 {
699 	/* if second char wasn't 'Z', fail */
700 	if (key != 'Z')
701 	{
702 		return MARK_UNSET;
703 	}
704 
705 	/* move the cursor to the bottom of the screen */
706 	move(LINES - 1, 0);
707 	clrtoeol();
708 
709 	/* do the xit command */
710 	cmd_xit(m, m, CMD_XIT, FALSE, "");
711 
712 	/* return the cursor */
713 	return m;
714 }
715 
716 
717 /* This function undoes changes to a single line, if possible */
v_undoline(m)718 MARK v_undoline(m)
719 	MARK	m;	/* where we hope to undo the change */
720 {
721 	/* make sure we have the right line in the buffer */
722 	if (markline(m) != U_line)
723 	{
724 		return MARK_UNSET;
725 	}
726 
727 	/* fix it */
728 	ChangeText
729 	{
730 		strcat(U_text, "\n");
731 		change(MARK_AT_LINE(U_line), MARK_AT_LINE(U_line + 1), U_text);
732 	}
733 
734 	/* nothing in the buffer anymore */
735 	U_line = -1L;
736 
737 	/* return, with the cursor at the front of the line */
738 	return m & ~(BLKSIZE - 1);
739 }
740 
741 
742 #ifndef NO_ERRLIST
v_errlist(m)743 MARK v_errlist(m)
744 	MARK	m;
745 {
746 	cmd_errlist(m, m, CMD_ERRLIST, FALSE, "");
747 	return cursor;
748 }
749 #endif
750 
751 
752 #ifndef NO_AT
753 /*ARGSUSED*/
v_at(m,cnt,key)754 MARK v_at(m, cnt, key)
755 	MARK	m;
756 	long	cnt;
757 	int	key;
758 {
759 	int	size;
760 
761 	size = cb2str(key, tmpblk.c, BLKSIZE);
762 	if (size <= 0 || size == BLKSIZE)
763 	{
764 		return MARK_UNSET;
765 	}
766 
767 	execmap(0, tmpblk.c, FALSE);
768 	return cursor;
769 }
770 #endif
771 
772 
773 #ifdef SIGTSTP
v_suspend()774 MARK v_suspend()
775 {
776 	cmd_suspend(MARK_UNSET, MARK_UNSET, CMD_SUSPEND, FALSE, "");
777 	return MARK_UNSET;
778 }
779 #endif
780 
781 
782 #ifndef NO_VISIBLE
783 /*ARGSUSED*/
v_start(m,cnt,cmd)784 MARK v_start(m, cnt, cmd)
785 	MARK	m;	/* starting point for a v or V command */
786 	long	cnt;	/* ignored */
787 	int	cmd;	/* either 'v' or 'V' */
788 {
789 	if (V_from)
790 	{
791 		V_from = MARK_UNSET;
792 	}
793 	else
794 	{
795 		V_from = m;
796 		V_linemd = isupper(cmd);
797 	}
798 	return m;
799 }
800 #endif
801 
802 #ifndef NO_POPUP
803 # define MENU_HEIGHT 11
804 # define MENU_WIDTH  23
v_popup(m,n)805 MARK v_popup(m, n)
806 	MARK		m, n;	/* the range of text to change */
807 {
808 	int		i;
809 	int		y, x;	/* position where the window will pop up at */
810 	int		key;	/* keystroke from the user */
811 	int		sel;	/* index of the selected operation */
812 	static int	dfltsel;/* default value of sel */
813 	static char	*labels[11] =
814 	{
815 		"ESC cancel!         ",
816 		" d  delete (cut)    ",
817 		" y  yank (copy)     ",
818 		" p  paste after     ",
819 		" P  paste before    ",
820 		" >  more indented   ",
821 		" <  less indented   ",
822 		" =  reformat        ",
823 		" !  external filter ",
824 		" ZZ save & exit     ",
825 		" u  undo previous   "
826 	};
827 
828 	/* try to position the menu near the cursor */
829 	x = physcol - (MENU_WIDTH / 2);
830 	if (x < 0)
831 		x = 0;
832 	else if (x + MENU_WIDTH + 2 > COLS)
833 		x = COLS - MENU_WIDTH - 2;
834 	if (markline(cursor) < topline || markline(cursor) > botline)
835 		y = 0;
836 	else if (markline(cursor) + 1L + MENU_HEIGHT > botline)
837 		y = (int)(markline(cursor) - topline) - MENU_HEIGHT;
838 	else
839 		y = (int)(markline(cursor) - topline) + 1L;
840 
841 	/* draw the menu */
842 	for (sel = 0; sel < MENU_HEIGHT; sel++)
843 	{
844 		move(y + sel, x);
845 		do_POPUP();
846 		if (sel == dfltsel)
847 			qaddstr("-->");
848 		else
849 			qaddstr("   ");
850 		qaddstr(labels[sel]);
851 		do_SE();
852 	}
853 
854 	/* get a selection */
855 	move(y + dfltsel, x + 4);
856 	for (sel = dfltsel; (key = getkey(WHEN_POPUP)) != '\\' && key != '\r'; )
857 	{
858 		/* erase the selection arrow */
859 		move(y + sel, x);
860 		do_POPUP();
861 		qaddstr("   ");
862 		qaddstr(labels[sel]);
863 		do_SE();
864 
865 		/* process the user's keystroke */
866 		if (key == 'j' && sel < MENU_HEIGHT - 1)
867 		{
868 			sel++;
869 		}
870 		else if (key == 'k' && sel > 0)
871 		{
872 			sel--;
873 		}
874 		else if (key == '\033')
875 		{
876 			sel = 0;
877 			break;
878 		}
879 		else
880 		{
881 			for (i = 1; i < MENU_HEIGHT && labels[i][1] != key; i++)
882 			{
883 			}
884 			if (i < MENU_HEIGHT)
885 			{
886 				sel = i;
887 				break;
888 			}
889 		}
890 
891 		/* redraw the arrow, possibly in a new place */
892 		move(y + sel, x);
893 		do_POPUP();
894 		qaddstr("-->");
895 		qaddstr(labels[sel]);
896 		do_SE();
897 		move(y + sel, x + 4);
898 	}
899 
900 	/* in most cases, the default selection is "paste after" */
901 	dfltsel = 3;
902 
903 	/* perform the appropriate action */
904 	switch (sel)
905 	{
906 	  case 0:
907 		m = cursor;
908 		dfltsel = 0;
909 		break;
910 
911 	  case 1: /* delete (cut) */
912 		m = v_delete(m, n);
913 		break;
914 
915 	  case 2: /* yank (copy) */
916 		m = v_yank(m, n);
917 		break;
918 
919 	  case 3: /* paste after */
920 		m = v_paste(n, 1L, 'P');
921 		break;
922 
923 	  case 4: /* paste before */
924 		m = v_paste(m, 1L, 'P');
925 		dfltsel = 4;
926 		break;
927 
928 	  case 5: /* more indented */
929 		m = v_rshift(m, n);
930 		dfltsel = 5;
931 		break;
932 
933 	  case 6: /* less indented */
934 		m = v_lshift(m, n);
935 		dfltsel = 6;
936 		break;
937 
938 	  case 7: /* reformat */
939 		m = v_reformat(m, n);
940 		dfltsel = 7;
941 		break;
942 
943 	  case 8: /* external filter */
944 		m = v_filter(m, n);
945 		dfltsel = 0;
946 		break;
947 
948 	  case 9: /* save & exit */
949 		/* get confirmation first */
950 		do
951 		{
952 			key = getkey(0);
953 		} while (key != '\\' && key != 'Z' && key != '\r'    /* good */
954 		      && key != '\033');			     /* bad  */
955 		if (key != '\033')
956 		{
957 			m = v_xit(m, 0L, 'Z');
958 		}
959 		break;
960 
961 	  case 10: /* undo previous */
962 		m = v_undo(m);
963 		dfltsel = 9;
964 		break;
965 	}
966 
967 	/* arrange for the menu to be erased (except that "chg from kbd"
968 	 * already erased it, and "save & exit" doesn't care)
969 	 */
970 	if (sel != 5 && sel != 9)
971 		redraw(MARK_UNSET, FALSE);
972 
973 	return m;
974 }
975 #endif /* undef NO_POPUP */
976