1 /* $NetBSD: vi.c,v 1.21 2021/09/16 19:44:01 christos Exp $ */
2
3 /*
4 * vi command editing
5 * written by John Rochester (initially for nsh)
6 * bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
7 *
8 */
9 #include <sys/cdefs.h>
10
11 #ifndef lint
12 __RCSID("$NetBSD: vi.c,v 1.21 2021/09/16 19:44:01 christos Exp $");
13 #endif
14
15 #include "config.h"
16 #ifdef VI
17
18 #include "sh.h"
19 #include <sys/stat.h>
20 #include <ctype.h>
21 #include "edit.h"
22
23 #define CMDLEN 1024
24 #define Ctrl(c) (c&0x1f)
25 #define is_wordch(c) (letnum(c))
26
27 struct edstate {
28 int winleft;
29 char *cbuf;
30 int cbufsize;
31 int linelen;
32 int cursor;
33 };
34
35
36 static int vi_hook ARGS((int));
37 static void vi_reset ARGS((char *, size_t));
38 static int nextstate ARGS((int));
39 static int vi_insert ARGS((int));
40 static int vi_cmd ARGS((int, const char *));
41 static int domove ARGS((int, const char *, int));
42 static int redo_insert ARGS((int));
43 static void yank_range ARGS((int, int));
44 static int bracktype ARGS((int));
45 static void save_cbuf ARGS((void));
46 static void restore_cbuf ARGS((void));
47 static void edit_reset ARGS((char *, size_t));
48 static int putbuf ARGS((const char *, int, int));
49 static void del_range ARGS((int, int));
50 static int findch ARGS((int, int, int, int));
51 static int forwword ARGS((int));
52 static int backword ARGS((int));
53 static int endword ARGS((int));
54 static int Forwword ARGS((int));
55 static int Backword ARGS((int));
56 static int Endword ARGS((int));
57 static int grabhist ARGS((int, int));
58 static int grabsearch ARGS((int, int, int, char *));
59 static void redraw_line ARGS((int));
60 static void refresh ARGS((int));
61 static int outofwin ARGS((void));
62 static void rewindow ARGS((void));
63 static int newcol ARGS((int, int));
64 static void display ARGS((char *, char *, int));
65 static void ed_mov_opt ARGS((int, char *));
66 static int expand_word ARGS((int));
67 static int complete_word ARGS((int, int));
68 static int print_expansions ARGS((struct edstate *, int));
69 static int char_len ARGS((int));
70 static void x_vi_zotc ARGS((int));
71 static void vi_pprompt ARGS((int));
72 static void vi_error ARGS((void));
73 static void vi_macro_reset ARGS((void));
74 static int x_vi_putbuf ARGS((const char *, size_t));
75
76 #define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */
77 #define M_ 0x2 /* movement command (h, l, etc.) */
78 #define E_ 0x4 /* extended command (c, d, y) */
79 #define X_ 0x8 /* long command (@, f, F, t, T, etc.) */
80 #define U_ 0x10 /* an UN-undoable command (that isn't a M_) */
81 #define B_ 0x20 /* bad command (^@) */
82 #define Z_ 0x40 /* repeat count defaults to 0 (not 1) */
83 #define S_ 0x80 /* search (/, ?) */
84
85 #define is_bad(c) (classify[(c)&0x7f]&B_)
86 #define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_))
87 #define is_move(c) (classify[(c)&0x7f]&M_)
88 #define is_extend(c) (classify[(c)&0x7f]&E_)
89 #define is_long(c) (classify[(c)&0x7f]&X_)
90 #define is_undoable(c) (!(classify[(c)&0x7f]&U_))
91 #define is_srch(c) (classify[(c)&0x7f]&S_)
92 #define is_zerocount(c) (classify[(c)&0x7f]&Z_)
93
94 const unsigned char classify[128] = {
95 /* 0 1 2 3 4 5 6 7 */
96 /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */
97 B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0,
98 /* 01 ^H ^I ^J ^K ^L ^M ^N ^O */
99 M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0,
100 /* 02 ^P ^Q ^R ^S ^T ^U ^V ^W */
101 C_, 0, C_|U_, 0, 0, 0, C_, 0,
102 /* 03 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
103 C_, 0, 0, C_|Z_, 0, 0, 0, 0,
104 /* 04 <space> ! " # $ % & ' */
105 M_, 0, 0, C_, M_, M_, 0, 0,
106 /* 05 ( ) * + , - . / */
107 0, 0, C_, C_, M_, C_, 0, C_|S_,
108 /* 06 0 1 2 3 4 5 6 7 */
109 M_, 0, 0, 0, 0, 0, 0, 0,
110 /* 07 8 9 : ; < = > ? */
111 0, 0, 0, M_, 0, C_, 0, C_|S_,
112 /* 010 @ A B C D E F G */
113 C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_,
114 /* 011 H I J K L M N O */
115 0, C_, 0, 0, 0, 0, C_|U_, 0,
116 /* 012 P Q R S T U V W */
117 C_, 0, C_, C_, M_|X_, C_, 0, M_,
118 /* 013 X Y Z [ \ ] ^ _ */
119 C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_,
120 /* 014 ` a b c d e f g */
121 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_,
122 /* 015 h i j k l m n o */
123 M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0,
124 /* 016 p q r s t u v w */
125 C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_,M_,
126 /* 017 x y z { | } ~ ^? */
127 C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0
128 };
129
130 #define MAXVICMD 3
131 #define SRCHLEN 40
132
133 #define INSERT 1
134 #define REPLACE 2
135
136 #define VNORMAL 0 /* command, insert or replace mode */
137 #define VARG1 1 /* digit prefix (first, eg, 5l) */
138 #define VEXTCMD 2 /* cmd + movement (eg, cl) */
139 #define VARG2 3 /* digit prefix (second, eg, 2c3l) */
140 #define VXCH 4 /* f, F, t, T, @ */
141 #define VFAIL 5 /* bad command */
142 #define VCMD 6 /* single char command (eg, X) */
143 #define VREDO 7 /* . */
144 #define VLIT 8 /* ^V */
145 #define VSEARCH 9 /* /, ? */
146 #define VVERSION 10 /* <ESC> ^V */
147
148 static char undocbuf[CMDLEN];
149
150 static struct edstate *save_edstate ARGS((struct edstate *old));
151 static void restore_edstate ARGS((struct edstate *old, struct edstate *new));
152 static void free_edstate ARGS((struct edstate *old));
153
154 static struct edstate ebuf;
155 static struct edstate undobuf = { 0, undocbuf, CMDLEN, 0, 0 };
156
157 static struct edstate *es; /* current editor state */
158 static struct edstate *undo;
159
160 static char ibuf[CMDLEN]; /* input buffer */
161 static int first_insert; /* set when starting in insert mode */
162 static int saved_inslen; /* saved inslen for first insert */
163 static int inslen; /* length of input buffer */
164 static int srchlen; /* length of current search pattern */
165 static char ybuf[CMDLEN]; /* yank buffer */
166 static int yanklen; /* length of yank buffer */
167 static int fsavecmd = ' '; /* last find command */
168 static int fsavech; /* character to find */
169 static char lastcmd[MAXVICMD]; /* last non-move command */
170 static int lastac; /* argcnt for lastcmd */
171 static int lastsearch = ' '; /* last search command */
172 static char srchpat[SRCHLEN]; /* last search pattern */
173 static int insert; /* non-zero in insert mode */
174 static int hnum; /* position in history */
175 static int ohnum; /* history line copied (after mod) */
176 static int hlast; /* 1 past last position in history */
177 static int modified; /* buffer has been "modified" */
178 static int state;
179
180 /* Information for keeping track of macros that are being expanded.
181 * The format of buf is the alias contents followed by a null byte followed
182 * by the name (letter) of the alias. The end of the buffer is marked by
183 * a double null. The name of the alias is stored so recursive macros can
184 * be detected.
185 */
186 struct macro_state {
187 unsigned char *p; /* current position in buf */
188 unsigned char *buf; /* pointer to macro(s) being expanded */
189 int len; /* how much data in buffer */
190 };
191 static struct macro_state macro;
192
193 enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
194 static enum expand_mode expanded = NONE;/* last input was expanded */
195
196 int
x_vi(buf,len)197 x_vi(buf, len)
198 char *buf;
199 size_t len;
200 {
201 int c;
202
203 vi_reset(buf, len > CMDLEN ? CMDLEN : len);
204 vi_pprompt(1);
205 x_flush();
206 while (1) {
207 if (macro.p) {
208 c = *macro.p++;
209 /* end of current macro? */
210 if (!c) {
211 /* more macros left to finish? */
212 if (*macro.p++)
213 continue;
214 /* must be the end of all the macros */
215 vi_macro_reset();
216 c = x_getc();
217 }
218 } else {
219 c = x_getc();
220 }
221 if (c == -1)
222 break;
223 if (state != VLIT) {
224 if (c == edchars.intr || c == edchars.quit) {
225 /* pretend we got an interrupt */
226 x_vi_zotc(c);
227 x_flush();
228 trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
229 x_mode(false);
230 unwind(LSHELL);
231 } else if (c == edchars.eof && state != VVERSION) {
232 if (es->linelen == 0) {
233 x_vi_zotc(edchars.eof);
234 c = -1;
235 break;
236 }
237 continue;
238 }
239 }
240 if (vi_hook(c))
241 break;
242 x_flush();
243 }
244
245 x_putc('\r'); x_putc('\n'); x_flush();
246
247 if (c == -1 || len <= (size_t)es->linelen)
248 return -1;
249
250 if (es->cbuf != buf)
251 memmove(buf, es->cbuf, es->linelen);
252
253 buf[es->linelen++] = '\n';
254
255 return es->linelen;
256 }
257
258 static int
vi_hook(ch)259 vi_hook(ch)
260 int ch;
261 {
262 static char curcmd[MAXVICMD];
263 static char locpat[SRCHLEN];
264 static int cmdlen;
265 static int argc1, argc2;
266
267 switch (state) {
268
269 case VNORMAL:
270 if (insert != 0) {
271 if (ch == Ctrl('v')) {
272 state = VLIT;
273 ch = '^';
274 }
275 switch (vi_insert(ch)) {
276 case -1:
277 vi_error();
278 state = VNORMAL;
279 break;
280 case 0:
281 if (state == VLIT) {
282 es->cursor--;
283 refresh(0);
284 } else
285 refresh(insert != 0);
286 break;
287 case 1:
288 return 1;
289 }
290 } else {
291 if (ch == '\r' || ch == '\n')
292 return 1;
293 cmdlen = 0;
294 argc1 = 0;
295 if (ch >= '1' && ch <= '9') {
296 argc1 = ch - '0';
297 state = VARG1;
298 } else {
299 curcmd[cmdlen++] = ch;
300 state = nextstate(ch);
301 if (state == VSEARCH) {
302 save_cbuf();
303 es->cursor = 0;
304 es->linelen = 0;
305 if (ch == '/') {
306 if (putbuf("/", 1, 0) != 0) {
307 return -1;
308 }
309 } else if (putbuf("?", 1, 0) != 0)
310 return -1;
311 refresh(0);
312 }
313 if (state == VVERSION) {
314 save_cbuf();
315 es->cursor = 0;
316 es->linelen = 0;
317 putbuf(ksh_version + 4,
318 strlen(ksh_version + 4), 0);
319 refresh(0);
320 }
321 }
322 }
323 break;
324
325 case VLIT:
326 if (is_bad(ch)) {
327 del_range(es->cursor, es->cursor + 1);
328 vi_error();
329 } else
330 es->cbuf[es->cursor++] = ch;
331 refresh(1);
332 state = VNORMAL;
333 break;
334
335 case VVERSION:
336 restore_cbuf();
337 state = VNORMAL;
338 refresh(0);
339 break;
340
341 case VARG1:
342 if (isdigit(ch))
343 argc1 = argc1 * 10 + ch - '0';
344 else {
345 curcmd[cmdlen++] = ch;
346 state = nextstate(ch);
347 }
348 break;
349
350 case VEXTCMD:
351 argc2 = 0;
352 if (ch >= '1' && ch <= '9') {
353 argc2 = ch - '0';
354 state = VARG2;
355 return 0;
356 } else {
357 curcmd[cmdlen++] = ch;
358 if (ch == curcmd[0])
359 state = VCMD;
360 else if (is_move(ch))
361 state = nextstate(ch);
362 else
363 state = VFAIL;
364 }
365 break;
366
367 case VARG2:
368 if (isdigit(ch))
369 argc2 = argc2 * 10 + ch - '0';
370 else {
371 if (argc1 == 0)
372 argc1 = argc2;
373 else
374 argc1 *= argc2;
375 curcmd[cmdlen++] = ch;
376 if (ch == curcmd[0])
377 state = VCMD;
378 else if (is_move(ch))
379 state = nextstate(ch);
380 else
381 state = VFAIL;
382 }
383 break;
384
385 case VXCH:
386 if (ch == Ctrl('['))
387 state = VNORMAL;
388 else {
389 curcmd[cmdlen++] = ch;
390 state = VCMD;
391 }
392 break;
393
394 case VSEARCH:
395 if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
396 restore_cbuf();
397 /* Repeat last search? */
398 if (srchlen == 0) {
399 if (!srchpat[0]) {
400 vi_error();
401 state = VNORMAL;
402 refresh(0);
403 return 0;
404 }
405 } else {
406 locpat[srchlen] = '\0';
407 (void) strlcpy(srchpat, locpat, sizeof srchpat);
408 }
409 state = VCMD;
410 } else if (ch == edchars.erase || ch == Ctrl('h')) {
411 if (srchlen != 0) {
412 srchlen--;
413 es->linelen -= char_len((unsigned char) locpat[srchlen]);
414 es->cursor = es->linelen;
415 refresh(0);
416 return 0;
417 }
418 restore_cbuf();
419 state = VNORMAL;
420 refresh(0);
421 } else if (ch == edchars.kill) {
422 srchlen = 0;
423 es->linelen = 1;
424 es->cursor = 1;
425 refresh(0);
426 return 0;
427 } else if (ch == edchars.werase) {
428 int i;
429 int n = srchlen;
430
431 while (n > 0 && isspace((unsigned char)locpat[n - 1]))
432 n--;
433 while (n > 0 && !isspace((unsigned char)locpat[n - 1]))
434 n--;
435 for (i = srchlen; --i >= n; )
436 es->linelen -= char_len((unsigned char) locpat[i]);
437 srchlen = n;
438 es->cursor = es->linelen;
439 refresh(0);
440 return 0;
441 } else {
442 if (srchlen == SRCHLEN - 1)
443 vi_error();
444 else {
445 locpat[srchlen++] = ch;
446 if ((ch & 0x80) && Flag(FVISHOW8)) {
447 if (es->linelen + 2 > es->cbufsize)
448 vi_error();
449 es->cbuf[es->linelen++] = 'M';
450 es->cbuf[es->linelen++] = '-';
451 ch &= 0x7f;
452 }
453 if (ch < ' ' || ch == 0x7f) {
454 if (es->linelen + 2 > es->cbufsize)
455 vi_error();
456 es->cbuf[es->linelen++] = '^';
457 es->cbuf[es->linelen++] = ch ^ '@';
458 } else {
459 if (es->linelen >= es->cbufsize)
460 vi_error();
461 es->cbuf[es->linelen++] = ch;
462 }
463 es->cursor = es->linelen;
464 refresh(0);
465 }
466 return 0;
467 }
468 break;
469 }
470
471 switch (state) {
472 case VCMD:
473 state = VNORMAL;
474 switch (vi_cmd(argc1, curcmd)) {
475 case -1:
476 vi_error();
477 refresh(0);
478 break;
479 case 0:
480 if (insert != 0)
481 inslen = 0;
482 refresh(insert != 0);
483 break;
484 case 1:
485 refresh(0);
486 return 1;
487 case 2:
488 /* back from a 'v' command - don't redraw the screen */
489 return 1;
490 }
491 break;
492
493 case VREDO:
494 state = VNORMAL;
495 if (argc1 != 0)
496 lastac = argc1;
497 switch (vi_cmd(lastac, lastcmd)) {
498 case -1:
499 vi_error();
500 refresh(0);
501 break;
502 case 0:
503 if (insert != 0) {
504 if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
505 lastcmd[0] == 'C') {
506 if (redo_insert(1) != 0)
507 vi_error();
508 } else {
509 if (redo_insert(lastac) != 0)
510 vi_error();
511 }
512 }
513 refresh(0);
514 break;
515 case 1:
516 refresh(0);
517 return 1;
518 case 2:
519 /* back from a 'v' command - can't happen */
520 break;
521 }
522 break;
523
524 case VFAIL:
525 state = VNORMAL;
526 vi_error();
527 break;
528 }
529 return 0;
530 }
531
532 static void
vi_reset(buf,len)533 vi_reset(buf, len)
534 char *buf;
535 size_t len;
536 {
537 state = VNORMAL;
538 ohnum = hnum = hlast = histnum(-1) + 1;
539 insert = INSERT;
540 saved_inslen = inslen;
541 first_insert = 1;
542 inslen = 0;
543 modified = 1;
544 vi_macro_reset();
545 edit_reset(buf, len);
546 }
547
548 static int
nextstate(ch)549 nextstate(ch)
550 int ch;
551 {
552 if (is_extend(ch))
553 return VEXTCMD;
554 else if (is_srch(ch))
555 return VSEARCH;
556 else if (is_long(ch))
557 return VXCH;
558 else if (ch == '.')
559 return VREDO;
560 else if (ch == Ctrl('v'))
561 return VVERSION;
562 else if (is_cmd(ch))
563 return VCMD;
564 else
565 return VFAIL;
566 }
567
568 static int
vi_insert(ch)569 vi_insert(ch)
570 int ch;
571 {
572 int tcursor;
573
574 if (ch == edchars.erase || ch == Ctrl('h')) {
575 if (insert == REPLACE) {
576 if (es->cursor == undo->cursor) {
577 vi_error();
578 return 0;
579 }
580 if (inslen > 0)
581 inslen--;
582 es->cursor--;
583 if (es->cursor >= undo->linelen)
584 es->linelen--;
585 else
586 es->cbuf[es->cursor] = undo->cbuf[es->cursor];
587 } else {
588 if (es->cursor == 0) {
589 /* x_putc(BEL); no annoying bell here */
590 return 0;
591 }
592 if (inslen > 0)
593 inslen--;
594 es->cursor--;
595 es->linelen--;
596 memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1],
597 es->linelen - es->cursor + 1);
598 }
599 expanded = NONE;
600 return 0;
601 }
602 if (ch == edchars.kill) {
603 if (es->cursor != 0) {
604 inslen = 0;
605 memmove(es->cbuf, &es->cbuf[es->cursor],
606 es->linelen - es->cursor);
607 es->linelen -= es->cursor;
608 es->cursor = 0;
609 }
610 expanded = NONE;
611 return 0;
612 }
613 if (ch == edchars.werase) {
614 if (es->cursor != 0) {
615 tcursor = Backword(1);
616 memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
617 es->linelen - es->cursor);
618 es->linelen -= es->cursor - tcursor;
619 if (inslen < es->cursor - tcursor)
620 inslen = 0;
621 else
622 inslen -= es->cursor - tcursor;
623 es->cursor = tcursor;
624 }
625 expanded = NONE;
626 return 0;
627 }
628 /* If any chars are entered before escape, trash the saved insert
629 * buffer (if user inserts & deletes char, ibuf gets trashed and
630 * we don't want to use it)
631 */
632 if (first_insert && ch != Ctrl('['))
633 saved_inslen = 0;
634 switch (ch) {
635
636 case '\0':
637 return -1;
638
639 case '\r':
640 case '\n':
641 return 1;
642
643 case Ctrl('['):
644 expanded = NONE;
645 if (first_insert) {
646 first_insert = 0;
647 if (inslen == 0) {
648 inslen = saved_inslen;
649 return redo_insert(0);
650 }
651 lastcmd[0] = 'a';
652 lastac = 1;
653 }
654 if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
655 lastcmd[0] == 'C')
656 return redo_insert(0);
657 else
658 return redo_insert(lastac - 1);
659
660 /* { Begin nonstandard vi commands */
661 case Ctrl('x'):
662 expand_word(0);
663 break;
664
665 case Ctrl('f'):
666 complete_word(0, 0);
667 break;
668
669 case Ctrl('e'):
670 print_expansions(es, 0);
671 break;
672
673 case Ctrl('i'):
674 if (Flag(FVITABCOMPLETE)) {
675 complete_word(0, 0);
676 break;
677 }
678 /* FALLTHROUGH */
679 /* End nonstandard vi commands } */
680
681 default:
682 if (es->linelen >= es->cbufsize - 1)
683 return -1;
684 ibuf[inslen++] = ch;
685 if (insert == INSERT) {
686 memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor],
687 es->linelen - es->cursor);
688 es->linelen++;
689 }
690 es->cbuf[es->cursor++] = ch;
691 if (insert == REPLACE && es->cursor > es->linelen)
692 es->linelen++;
693 expanded = NONE;
694 }
695 return 0;
696 }
697
698 static int
vi_cmd(argcnt,cmd)699 vi_cmd(argcnt, cmd)
700 int argcnt;
701 const char *cmd;
702 {
703 int ncursor;
704 int cur, c1, c2, c3 = 0;
705 int any;
706 struct edstate *t;
707
708 if (argcnt == 0 && !is_zerocount(*cmd))
709 argcnt = 1;
710
711 if (is_move(*cmd)) {
712 if ((cur = domove(argcnt, cmd, 0)) >= 0) {
713 if (cur == es->linelen && cur != 0)
714 cur--;
715 es->cursor = cur;
716 } else
717 return -1;
718 } else {
719 /* Don't save state in middle of macro.. */
720 if (is_undoable(*cmd) && !macro.p) {
721 undo->winleft = es->winleft;
722 memmove(undo->cbuf, es->cbuf, es->linelen);
723 undo->linelen = es->linelen;
724 undo->cursor = es->cursor;
725 lastac = argcnt;
726 memmove(lastcmd, cmd, MAXVICMD);
727 }
728 switch (*cmd) {
729
730 case Ctrl('l'):
731 case Ctrl('r'):
732 redraw_line(1);
733 break;
734
735 case '@':
736 {
737 static char alias[] = "_\0";
738 struct tbl *ap;
739 int olen, nlen;
740 char *p, *nbuf;
741
742 /* lookup letter in alias list... */
743 alias[1] = cmd[1];
744 ap = mytsearch(&aliases, alias, hash(alias));
745 if (!cmd[1] || !ap || !(ap->flag & ISSET))
746 return -1;
747 /* check if this is a recursive call... */
748 if ((p = (char *) macro.p))
749 while ((p = strchr(p, '\0')) && p[1])
750 if (*++p == cmd[1])
751 return -1;
752 /* insert alias into macro buffer */
753 nlen = strlen(ap->val.s) + 1;
754 olen = !macro.p ? 2
755 : macro.len - (macro.p - macro.buf);
756 nbuf = alloc(nlen + 1 + olen, APERM);
757 memcpy(nbuf, ap->val.s, nlen);
758 nbuf[nlen++] = cmd[1];
759 if (macro.p) {
760 memcpy(nbuf + nlen, macro.p, olen);
761 afree(macro.buf, APERM);
762 nlen += olen;
763 } else {
764 nbuf[nlen++] = '\0';
765 nbuf[nlen++] = '\0';
766 }
767 macro.p = macro.buf = (unsigned char *) nbuf;
768 macro.len = nlen;
769 }
770 break;
771
772 case 'a':
773 modified = 1; hnum = hlast;
774 if (es->linelen != 0)
775 es->cursor++;
776 insert = INSERT;
777 break;
778
779 case 'A':
780 modified = 1; hnum = hlast;
781 del_range(0, 0);
782 es->cursor = es->linelen;
783 insert = INSERT;
784 break;
785
786 case 'S':
787 es->cursor = domove(1, "^", 1);
788 del_range(es->cursor, es->linelen);
789 modified = 1; hnum = hlast;
790 insert = INSERT;
791 break;
792
793 case 'Y':
794 cmd = "y$";
795 /* ahhhhhh... */
796 /*FALLTHROUGH*/
797 case 'c':
798 case 'd':
799 case 'y':
800 if (*cmd == cmd[1]) {
801 c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
802 c2 = es->linelen;
803 } else if (!is_move(cmd[1]))
804 return -1;
805 else {
806 if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
807 return -1;
808 if (*cmd == 'c' &&
809 (cmd[1]=='w' || cmd[1]=='W') &&
810 !isspace((unsigned char)es->cbuf[es->cursor])) {
811 while (isspace((unsigned char)es->cbuf[--ncursor]))
812 continue;
813 ncursor++;
814 }
815 if (ncursor > es->cursor) {
816 c1 = es->cursor;
817 c2 = ncursor;
818 } else {
819 c1 = ncursor;
820 c2 = es->cursor;
821 if (cmd[1] == '%')
822 c2++;
823 }
824 }
825 if (*cmd != 'c' && c1 != c2)
826 yank_range(c1, c2);
827 if (*cmd != 'y') {
828 del_range(c1, c2);
829 es->cursor = c1;
830 }
831 if (*cmd == 'c') {
832 modified = 1; hnum = hlast;
833 insert = INSERT;
834 }
835 break;
836
837 case 'p':
838 modified = 1; hnum = hlast;
839 if (es->linelen != 0)
840 es->cursor++;
841 while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
842 continue;
843 if (es->cursor != 0)
844 es->cursor--;
845 if (argcnt != 0)
846 return -1;
847 break;
848
849 case 'P':
850 modified = 1; hnum = hlast;
851 any = 0;
852 while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
853 any = 1;
854 if (any && es->cursor != 0)
855 es->cursor--;
856 if (argcnt != 0)
857 return -1;
858 break;
859
860 case 'C':
861 modified = 1; hnum = hlast;
862 del_range(es->cursor, es->linelen);
863 insert = INSERT;
864 break;
865
866 case 'D':
867 yank_range(es->cursor, es->linelen);
868 del_range(es->cursor, es->linelen);
869 if (es->cursor != 0)
870 es->cursor--;
871 break;
872
873 case 'g':
874 if (!argcnt)
875 argcnt = hlast + 1;
876 /* fall through */
877 case 'G':
878 if (!argcnt)
879 argcnt = 1;
880 else
881 argcnt = hlast - (source->line - argcnt);
882 if (grabhist(modified, argcnt - 1) < 0)
883 return -1;
884 else {
885 modified = 0;
886 hnum = argcnt - 1;
887 }
888 break;
889
890 case 'i':
891 modified = 1; hnum = hlast;
892 insert = INSERT;
893 break;
894
895 case 'I':
896 modified = 1; hnum = hlast;
897 es->cursor = domove(1, "^", 1);
898 insert = INSERT;
899 break;
900
901 case 'j':
902 case '+':
903 case Ctrl('n'):
904 if (grabhist(modified, hnum + argcnt) < 0)
905 return -1;
906 else {
907 modified = 0;
908 hnum += argcnt;
909 }
910 break;
911
912 case 'k':
913 case '-':
914 case Ctrl('p'):
915 if (grabhist(modified, hnum - argcnt) < 0)
916 return -1;
917 else {
918 modified = 0;
919 hnum -= argcnt;
920 }
921 break;
922
923 case 'r':
924 if (es->linelen == 0)
925 return -1;
926 modified = 1; hnum = hlast;
927 if (cmd[1] == 0)
928 vi_error();
929 else
930 es->cbuf[es->cursor] = cmd[1];
931 break;
932
933 case 'R':
934 modified = 1; hnum = hlast;
935 insert = REPLACE;
936 break;
937
938 case 's':
939 if (es->linelen == 0)
940 return -1;
941 modified = 1; hnum = hlast;
942 if (es->cursor + argcnt > es->linelen)
943 argcnt = es->linelen - es->cursor;
944 del_range(es->cursor, es->cursor + argcnt);
945 insert = INSERT;
946 break;
947
948 case 'v':
949 if (es->linelen == 0)
950 return -1;
951 if (!argcnt) {
952 if (modified) {
953 es->cbuf[es->linelen] = '\0';
954 source->line++;
955 histsave(source->line, es->cbuf, 1);
956 } else
957 argcnt = source->line + 1
958 - (hlast - hnum);
959 }
960 shf_snprintf(es->cbuf, es->cbufsize,
961 argcnt ? "%s %d" : "%s",
962 "fc -e ${VISUAL:-${EDITOR:-vi}} --",
963 argcnt);
964 es->linelen = strlen(es->cbuf);
965 return 2;
966
967 case 'x':
968 if (es->linelen == 0)
969 return -1;
970 modified = 1; hnum = hlast;
971 if (es->cursor + argcnt > es->linelen)
972 argcnt = es->linelen - es->cursor;
973 yank_range(es->cursor, es->cursor + argcnt);
974 del_range(es->cursor, es->cursor + argcnt);
975 break;
976
977 case 'X':
978 if (es->cursor > 0) {
979 modified = 1; hnum = hlast;
980 if (es->cursor < argcnt)
981 argcnt = es->cursor;
982 yank_range(es->cursor - argcnt, es->cursor);
983 del_range(es->cursor - argcnt, es->cursor);
984 es->cursor -= argcnt;
985 } else
986 return -1;
987 break;
988
989 case 'u':
990 t = es;
991 es = undo;
992 undo = t;
993 break;
994
995 case 'U':
996 if (!modified)
997 return -1;
998 if (grabhist(modified, ohnum) < 0)
999 return -1;
1000 modified = 0;
1001 hnum = ohnum;
1002 break;
1003
1004 case '?':
1005 if (hnum == hlast)
1006 hnum = -1;
1007 /* ahhh */
1008 /*FALLTHROUGH*/
1009 case '/':
1010 c3 = 1;
1011 srchlen = 0;
1012 lastsearch = *cmd;
1013 /* fall through */
1014 case 'n':
1015 case 'N':
1016 if (lastsearch == ' ')
1017 return -1;
1018 if (lastsearch == '?')
1019 c1 = 1;
1020 else
1021 c1 = 0;
1022 if (*cmd == 'N')
1023 c1 = !c1;
1024 if ((c2 = grabsearch(modified, hnum,
1025 c1, srchpat)) < 0) {
1026 if (c3) {
1027 restore_cbuf();
1028 refresh(0);
1029 }
1030 return -1;
1031 } else {
1032 modified = 0;
1033 hnum = c2;
1034 ohnum = hnum;
1035 }
1036 break;
1037 case '_': {
1038 int inspace;
1039 char *p, *sp;
1040
1041 if (histnum(-1) < 0)
1042 return -1;
1043 p = *histpos();
1044 #define issp(c) (isspace((unsigned char)(c)) || (c) == '\n')
1045 if (argcnt) {
1046 while (*p && issp(*p))
1047 p++;
1048 while (*p && --argcnt) {
1049 while (*p && !issp(*p))
1050 p++;
1051 while (*p && issp(*p))
1052 p++;
1053 }
1054 if (!*p)
1055 return -1;
1056 sp = p;
1057 } else {
1058 sp = p;
1059 inspace = 0;
1060 while (*p) {
1061 if (issp(*p))
1062 inspace = 1;
1063 else if (inspace) {
1064 inspace = 0;
1065 sp = p;
1066 }
1067 p++;
1068 }
1069 p = sp;
1070 }
1071 modified = 1; hnum = hlast;
1072 if (es->cursor != es->linelen)
1073 es->cursor++;
1074 while (*p && !issp(*p)) {
1075 argcnt++;
1076 p++;
1077 }
1078 if (putbuf(space, 1, 0) != 0)
1079 argcnt = -1;
1080 else if (putbuf(sp, argcnt, 0) != 0)
1081 argcnt = -1;
1082 if (argcnt < 0) {
1083 if (es->cursor != 0)
1084 es->cursor--;
1085 return -1;
1086 }
1087 insert = INSERT;
1088 }
1089 break;
1090
1091 case '~': {
1092 char *p;
1093 int i;
1094
1095 if (es->linelen == 0)
1096 return -1;
1097 for (i = 0; i < argcnt; i++) {
1098 p = &es->cbuf[es->cursor];
1099 if (islower((unsigned char)*p)) {
1100 modified = 1; hnum = hlast;
1101 *p = toupper((unsigned char)*p);
1102 } else if (isupper((unsigned char)*p)) {
1103 modified = 1; hnum = hlast;
1104 *p = tolower((unsigned char)*p);
1105 }
1106 if (es->cursor < es->linelen - 1)
1107 es->cursor++;
1108 }
1109 break;
1110 }
1111
1112 case '#':
1113 {
1114 int ret = x_do_comment(es->cbuf, es->cbufsize,
1115 &es->linelen);
1116 if (ret >= 0)
1117 es->cursor = 0;
1118 return ret;
1119 }
1120
1121 case '=': /* at&t ksh */
1122 case Ctrl('e'): /* Nonstandard vi/ksh */
1123 print_expansions(es, 1);
1124 break;
1125
1126
1127 case Ctrl('i'): /* Nonstandard vi/ksh */
1128 if (!Flag(FVITABCOMPLETE))
1129 return -1;
1130 complete_word(1, argcnt);
1131 break;
1132
1133 case Ctrl('['): /* some annoying at&t ksh's */
1134 if (!Flag(FVIESCCOMPLETE))
1135 return -1;
1136 /*FALLTHROUGH*/
1137 case '\\': /* at&t ksh */
1138 case Ctrl('f'): /* Nonstandard vi/ksh */
1139 complete_word(1, argcnt);
1140 break;
1141
1142
1143 case '*': /* at&t ksh */
1144 case Ctrl('x'): /* Nonstandard vi/ksh */
1145 expand_word(1);
1146 break;
1147 }
1148 if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
1149 es->cursor--;
1150 }
1151 return 0;
1152 }
1153
1154 static int
domove(argcnt,cmd,sub)1155 domove(argcnt, cmd, sub)
1156 int argcnt;
1157 const char *cmd;
1158 int sub;
1159 {
1160 int bcount, UNINITIALIZED(i), t;
1161 int UNINITIALIZED(ncursor);
1162
1163 switch (*cmd) {
1164
1165 case 'b':
1166 if (!sub && es->cursor == 0)
1167 return -1;
1168 ncursor = backword(argcnt);
1169 break;
1170
1171 case 'B':
1172 if (!sub && es->cursor == 0)
1173 return -1;
1174 ncursor = Backword(argcnt);
1175 break;
1176
1177 case 'e':
1178 if (!sub && es->cursor + 1 >= es->linelen)
1179 return -1;
1180 ncursor = endword(argcnt);
1181 if (sub && ncursor < es->linelen)
1182 ncursor++;
1183 break;
1184
1185 case 'E':
1186 if (!sub && es->cursor + 1 >= es->linelen)
1187 return -1;
1188 ncursor = Endword(argcnt);
1189 if (sub && ncursor < es->linelen)
1190 ncursor++;
1191 break;
1192
1193 case 'f':
1194 case 'F':
1195 case 't':
1196 case 'T':
1197 fsavecmd = *cmd;
1198 fsavech = cmd[1];
1199 /* drop through */
1200 /*FALLTHROUGH*/
1201 case ',':
1202 case ';':
1203 if (fsavecmd == ' ')
1204 return -1;
1205 i = fsavecmd == 'f' || fsavecmd == 'F';
1206 t = fsavecmd > 'a';
1207 if (*cmd == ',')
1208 t = !t;
1209 if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
1210 return -1;
1211 if (sub && t)
1212 ncursor++;
1213 break;
1214
1215 case 'h':
1216 case Ctrl('h'):
1217 if (!sub && es->cursor == 0)
1218 return -1;
1219 ncursor = es->cursor - argcnt;
1220 if (ncursor < 0)
1221 ncursor = 0;
1222 break;
1223
1224 case ' ':
1225 case 'l':
1226 if (!sub && es->cursor + 1 >= es->linelen)
1227 return -1;
1228 if (es->linelen != 0) {
1229 ncursor = es->cursor + argcnt;
1230 if (ncursor > es->linelen)
1231 ncursor = es->linelen;
1232 }
1233 break;
1234
1235 case 'w':
1236 if (!sub && es->cursor + 1 >= es->linelen)
1237 return -1;
1238 ncursor = forwword(argcnt);
1239 break;
1240
1241 case 'W':
1242 if (!sub && es->cursor + 1 >= es->linelen)
1243 return -1;
1244 ncursor = Forwword(argcnt);
1245 break;
1246
1247 case '0':
1248 ncursor = 0;
1249 break;
1250
1251 case '^':
1252 ncursor = 0;
1253 while (ncursor < es->linelen - 1 && isspace((unsigned char)es->cbuf[ncursor]))
1254 ncursor++;
1255 break;
1256
1257 case '|':
1258 ncursor = argcnt;
1259 if (ncursor > es->linelen)
1260 ncursor = es->linelen;
1261 if (ncursor)
1262 ncursor--;
1263 break;
1264
1265 case '$':
1266 if (es->linelen != 0)
1267 ncursor = es->linelen;
1268 else
1269 ncursor = 0;
1270 break;
1271
1272 case '%':
1273 ncursor = es->cursor;
1274 while (ncursor < es->linelen &&
1275 (i = bracktype(es->cbuf[ncursor])) == 0)
1276 ncursor++;
1277 if (ncursor == es->linelen)
1278 return -1;
1279 bcount = 1;
1280 do {
1281 if (i > 0) {
1282 if (++ncursor >= es->linelen)
1283 return -1;
1284 } else {
1285 if (--ncursor < 0)
1286 return -1;
1287 }
1288 t = bracktype(es->cbuf[ncursor]);
1289 if (t == i)
1290 bcount++;
1291 else if (t == -i)
1292 bcount--;
1293 } while (bcount != 0);
1294 if (sub && i > 0)
1295 ncursor++;
1296 break;
1297
1298 default:
1299 return -1;
1300 }
1301 return ncursor;
1302 }
1303
1304 static int
redo_insert(count)1305 redo_insert(count)
1306 int count;
1307 {
1308 while (count-- > 0)
1309 if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
1310 return -1;
1311 if (es->cursor > 0)
1312 es->cursor--;
1313 insert = 0;
1314 return 0;
1315 }
1316
1317 static void
yank_range(a,b)1318 yank_range(a, b)
1319 int a, b;
1320 {
1321 yanklen = b - a;
1322 if (yanklen != 0)
1323 memmove(ybuf, &es->cbuf[a], yanklen);
1324 }
1325
1326 static int
bracktype(ch)1327 bracktype(ch)
1328 int ch;
1329 {
1330 switch (ch) {
1331
1332 case '(':
1333 return 1;
1334
1335 case '[':
1336 return 2;
1337
1338 case '{':
1339 return 3;
1340
1341 case ')':
1342 return -1;
1343
1344 case ']':
1345 return -2;
1346
1347 case '}':
1348 return -3;
1349
1350 default:
1351 return 0;
1352 }
1353 }
1354
1355 /*
1356 * Non user interface editor routines below here
1357 */
1358
1359 static int cur_col; /* current column on line */
1360 static int pwidth; /* width of prompt */
1361 static int prompt_trunc; /* how much of prompt to truncate */
1362 static int prompt_skip; /* how much of prompt to skip */
1363 static int winwidth; /* width of window */
1364 static char *wbuf[2]; /* window buffers */
1365 static int wbuf_len; /* length of window buffers (x_cols-3)*/
1366 static int win; /* window buffer in use */
1367 static char morec; /* more character at right of window */
1368 static int lastref; /* argument to last refresh() */
1369 static char holdbuf[CMDLEN]; /* place to hold last edit buffer */
1370 static int holdlen; /* length of holdbuf */
1371
1372 static void
save_cbuf()1373 save_cbuf()
1374 {
1375 memmove(holdbuf, es->cbuf, es->linelen);
1376 holdlen = es->linelen;
1377 holdbuf[holdlen] = '\0';
1378 }
1379
1380 static void
restore_cbuf()1381 restore_cbuf()
1382 {
1383 es->cursor = 0;
1384 es->linelen = holdlen;
1385 memmove(es->cbuf, holdbuf, holdlen);
1386 }
1387
1388 /* return a new edstate */
1389 static struct edstate *
save_edstate(old)1390 save_edstate(old)
1391 struct edstate *old;
1392 {
1393 struct edstate *new;
1394
1395 new = (struct edstate *)alloc(sizeof(struct edstate), APERM);
1396 new->cbuf = alloc(old->cbufsize, APERM);
1397 memcpy(new->cbuf, old->cbuf, old->linelen);
1398 new->cbufsize = old->cbufsize;
1399 new->linelen = old->linelen;
1400 new->cursor = old->cursor;
1401 new->winleft = old->winleft;
1402 return new;
1403 }
1404
1405 static void
restore_edstate(new,old)1406 restore_edstate(new, old)
1407 struct edstate *old, *new;
1408 {
1409 memcpy(new->cbuf, old->cbuf, old->linelen);
1410 new->linelen = old->linelen;
1411 new->cursor = old->cursor;
1412 new->winleft = old->winleft;
1413 free_edstate(old);
1414 }
1415
1416 static void
free_edstate(old)1417 free_edstate(old)
1418 struct edstate *old;
1419 {
1420 afree(old->cbuf, APERM);
1421 afree((char *)old, APERM);
1422 }
1423
1424
1425
1426 static void
edit_reset(buf,len)1427 edit_reset(buf, len)
1428 char *buf;
1429 size_t len;
1430 {
1431 const char *p;
1432
1433 es = &ebuf;
1434 es->cbuf = buf;
1435 es->cbufsize = len;
1436 undo = &undobuf;
1437 undo->cbufsize = len;
1438
1439 es->linelen = undo->linelen = 0;
1440 es->cursor = undo->cursor = 0;
1441 es->winleft = undo->winleft = 0;
1442
1443 cur_col = pwidth = promptlen(prompt, &p);
1444 prompt_skip = p - prompt;
1445 if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
1446 cur_col = x_cols - 3 - MIN_EDIT_SPACE;
1447 prompt_trunc = pwidth - cur_col;
1448 pwidth -= prompt_trunc;
1449 } else
1450 prompt_trunc = 0;
1451 if (!wbuf_len || wbuf_len != x_cols - 3) {
1452 wbuf_len = x_cols - 3;
1453 wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
1454 wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
1455 }
1456 (void) memset(wbuf[0], ' ', wbuf_len);
1457 (void) memset(wbuf[1], ' ', wbuf_len);
1458 winwidth = x_cols - pwidth - 3;
1459 win = 0;
1460 morec = ' ';
1461 lastref = 1;
1462 holdlen = 0;
1463 }
1464
1465 /*
1466 * this is used for calling x_escape() in complete_word()
1467 */
1468 static int
x_vi_putbuf(s,len)1469 x_vi_putbuf(s, len)
1470 const char *s;
1471 size_t len;
1472 {
1473 return putbuf(s, len, 0);
1474 }
1475
1476 static int
putbuf(buf,len,repl)1477 putbuf(buf, len, repl)
1478 const char *buf;
1479 int len;
1480 int repl;
1481 {
1482 if (len == 0)
1483 return 0;
1484 if (repl) {
1485 if (es->cursor + len >= es->cbufsize)
1486 return -1;
1487 if (es->cursor + len > es->linelen)
1488 es->linelen = es->cursor + len;
1489 } else {
1490 if (es->linelen + len >= es->cbufsize)
1491 return -1;
1492 memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
1493 es->linelen - es->cursor);
1494 es->linelen += len;
1495 }
1496 memmove(&es->cbuf[es->cursor], buf, len);
1497 es->cursor += len;
1498 return 0;
1499 }
1500
1501 static void
del_range(a,b)1502 del_range(a, b)
1503 int a, b;
1504 {
1505 if (es->linelen != b)
1506 memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
1507 es->linelen -= b - a;
1508 }
1509
1510 static int
findch(ch,cnt,forw,incl)1511 findch(ch, cnt, forw, incl)
1512 int ch;
1513 int cnt;
1514 int forw;
1515 int incl;
1516 {
1517 int ncursor;
1518
1519 if (es->linelen == 0)
1520 return -1;
1521 ncursor = es->cursor;
1522 while (cnt--) {
1523 do {
1524 if (forw) {
1525 if (++ncursor == es->linelen)
1526 return -1;
1527 } else {
1528 if (--ncursor < 0)
1529 return -1;
1530 }
1531 } while (es->cbuf[ncursor] != ch);
1532 }
1533 if (!incl) {
1534 if (forw)
1535 ncursor--;
1536 else
1537 ncursor++;
1538 }
1539 return ncursor;
1540 }
1541
1542 static int
forwword(argcnt)1543 forwword(argcnt)
1544 int argcnt;
1545 {
1546 int ncursor;
1547
1548 ncursor = es->cursor;
1549 while (ncursor < es->linelen && argcnt--) {
1550 if (is_wordch(es->cbuf[ncursor]))
1551 while (ncursor < es->linelen &&
1552 is_wordch(es->cbuf[ncursor]))
1553 ncursor++;
1554 else if (!isspace((unsigned char)es->cbuf[ncursor]))
1555 while (ncursor < es->linelen &&
1556 !is_wordch(es->cbuf[ncursor]) &&
1557 !isspace((unsigned char)es->cbuf[ncursor]))
1558 ncursor++;
1559 while (ncursor < es->linelen &&
1560 isspace((unsigned char)es->cbuf[ncursor]))
1561 ncursor++;
1562 }
1563 return ncursor;
1564 }
1565
1566 static int
backword(argcnt)1567 backword(argcnt)
1568 int argcnt;
1569 {
1570 int ncursor;
1571
1572 ncursor = es->cursor;
1573 while (ncursor > 0 && argcnt--) {
1574 while (--ncursor > 0 && isspace((unsigned char)es->cbuf[ncursor]))
1575 continue;
1576 if (ncursor > 0) {
1577 if (is_wordch(es->cbuf[ncursor]))
1578 while (--ncursor >= 0 &&
1579 is_wordch(es->cbuf[ncursor]))
1580 continue;
1581 else
1582 while (--ncursor >= 0 &&
1583 !is_wordch(es->cbuf[ncursor]) &&
1584 !isspace((unsigned char)es->cbuf[ncursor]))
1585 continue;
1586 ncursor++;
1587 }
1588 }
1589 return ncursor;
1590 }
1591
1592 static int
endword(argcnt)1593 endword(argcnt)
1594 int argcnt;
1595 {
1596 int ncursor;
1597
1598 ncursor = es->cursor;
1599 while (ncursor < es->linelen && argcnt--) {
1600 while (++ncursor < es->linelen - 1 &&
1601 isspace((unsigned char)es->cbuf[ncursor]))
1602 continue;
1603 if (ncursor < es->linelen - 1) {
1604 if (is_wordch(es->cbuf[ncursor]))
1605 while (++ncursor < es->linelen &&
1606 is_wordch(es->cbuf[ncursor]))
1607 continue;
1608 else
1609 while (++ncursor < es->linelen &&
1610 !is_wordch(es->cbuf[ncursor]) &&
1611 !isspace((unsigned char)es->cbuf[ncursor]))
1612 continue;
1613 ncursor--;
1614 }
1615 }
1616 return ncursor;
1617 }
1618
1619 static int
Forwword(argcnt)1620 Forwword(argcnt)
1621 int argcnt;
1622 {
1623 int ncursor;
1624
1625 ncursor = es->cursor;
1626 while (ncursor < es->linelen && argcnt--) {
1627 while (ncursor < es->linelen &&
1628 !isspace((unsigned char)es->cbuf[ncursor]))
1629 ncursor++;
1630 while (ncursor < es->linelen &&
1631 isspace((unsigned char)es->cbuf[ncursor]))
1632 ncursor++;
1633 }
1634 return ncursor;
1635 }
1636
1637 static int
Backword(argcnt)1638 Backword(argcnt)
1639 int argcnt;
1640 {
1641 int ncursor;
1642
1643 ncursor = es->cursor;
1644 while (ncursor > 0 && argcnt--) {
1645 while (--ncursor >= 0 && isspace((unsigned char)es->cbuf[ncursor]))
1646 continue;
1647 while (ncursor >= 0 && !isspace((unsigned char)es->cbuf[ncursor]))
1648 ncursor--;
1649 ncursor++;
1650 }
1651 return ncursor;
1652 }
1653
1654 static int
Endword(argcnt)1655 Endword(argcnt)
1656 int argcnt;
1657 {
1658 int ncursor;
1659
1660 ncursor = es->cursor;
1661 while (ncursor < es->linelen - 1 && argcnt--) {
1662 while (++ncursor < es->linelen - 1 &&
1663 isspace((unsigned char)es->cbuf[ncursor]))
1664 continue;
1665 if (ncursor < es->linelen - 1) {
1666 while (++ncursor < es->linelen &&
1667 !isspace((unsigned char)es->cbuf[ncursor]))
1668 continue;
1669 ncursor--;
1670 }
1671 }
1672 return ncursor;
1673 }
1674
1675 static int
grabhist(save,n)1676 grabhist(save, n)
1677 int save;
1678 int n;
1679 {
1680 char *hptr;
1681
1682 if (n < 0 || n > hlast)
1683 return -1;
1684 if (n == hlast) {
1685 restore_cbuf();
1686 ohnum = n;
1687 return 0;
1688 }
1689 (void) histnum(n);
1690 if ((hptr = *histpos()) == NULL) {
1691 internal_errorf(0, "grabhist: bad history array");
1692 return -1;
1693 }
1694 if (save)
1695 save_cbuf();
1696 if ((es->linelen = strlen(hptr)) >= es->cbufsize)
1697 es->linelen = es->cbufsize - 1;
1698 memmove(es->cbuf, hptr, es->linelen);
1699 es->cursor = 0;
1700 ohnum = n;
1701 return 0;
1702 }
1703
1704 static int
grabsearch(save,start,fwd,pat)1705 grabsearch(save, start, fwd, pat)
1706 int save, start, fwd;
1707 char *pat;
1708 {
1709 char *hptr;
1710 int hist;
1711 int anchored;
1712
1713 if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
1714 return -1;
1715 if (fwd)
1716 start++;
1717 else
1718 start--;
1719 anchored = *pat == '^' ? (++pat, 1) : 0;
1720 if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
1721 /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
1722 /* XXX should FILECMP be strncmp? */
1723 if (start != 0 && fwd && FILECMP(holdbuf, pat) >= 0) {
1724 restore_cbuf();
1725 return 0;
1726 } else
1727 return -1;
1728 }
1729 if (save)
1730 save_cbuf();
1731 histnum(hist);
1732 hptr = *histpos();
1733 if ((es->linelen = strlen(hptr)) >= es->cbufsize)
1734 es->linelen = es->cbufsize - 1;
1735 memmove(es->cbuf, hptr, es->linelen);
1736 es->cursor = 0;
1737 return hist;
1738 }
1739
1740 static void
redraw_line(newlinex)1741 redraw_line(newlinex)
1742 int newlinex;
1743 {
1744 (void) memset(wbuf[win], ' ', wbuf_len);
1745 if (newlinex) {
1746 x_putc('\r');
1747 x_putc('\n');
1748 }
1749 vi_pprompt(0);
1750 cur_col = pwidth;
1751 morec = ' ';
1752 }
1753
1754 static void
refresh(leftside)1755 refresh(leftside)
1756 int leftside;
1757 {
1758 if (leftside < 0)
1759 leftside = lastref;
1760 else
1761 lastref = leftside;
1762 if (outofwin())
1763 rewindow();
1764 display(wbuf[1 - win], wbuf[win], leftside);
1765 win = 1 - win;
1766 }
1767
1768 static int
outofwin()1769 outofwin()
1770 {
1771 int cur, col;
1772
1773 if (es->cursor < es->winleft)
1774 return 1;
1775 col = 0;
1776 cur = es->winleft;
1777 while (cur < es->cursor)
1778 col = newcol((unsigned char) es->cbuf[cur++], col);
1779 if (col >= winwidth)
1780 return 1;
1781 return 0;
1782 }
1783
1784 static void
rewindow()1785 rewindow()
1786 {
1787 int tcur, tcol;
1788 int holdcur1, holdcol1;
1789 int holdcur2, holdcol2;
1790
1791 holdcur1 = holdcur2 = tcur = 0;
1792 holdcol1 = holdcol2 = tcol = 0;
1793 while (tcur < es->cursor) {
1794 if (tcol - holdcol2 > winwidth / 2) {
1795 holdcur1 = holdcur2;
1796 holdcol1 = holdcol2;
1797 holdcur2 = tcur;
1798 holdcol2 = tcol;
1799 }
1800 tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
1801 }
1802 while (tcol - holdcol1 > winwidth / 2)
1803 holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
1804 holdcol1);
1805 es->winleft = holdcur1;
1806 }
1807
1808 static int
newcol(ch,col)1809 newcol(ch, col)
1810 int ch, col;
1811 {
1812 if (ch == '\t')
1813 return (col | 7) + 1;
1814 return col + char_len(ch);
1815 }
1816
1817 static void
display(wb1,wb2,leftside)1818 display(wb1, wb2, leftside)
1819 char *wb1, *wb2;
1820 int leftside;
1821 {
1822 unsigned char ch;
1823 char *twb1, *twb2, mc;
1824 int cur, col, cnt;
1825 int UNINITIALIZED(ncol);
1826 int moreright;
1827
1828 col = 0;
1829 cur = es->winleft;
1830 moreright = 0;
1831 twb1 = wb1;
1832 while (col < winwidth && cur < es->linelen) {
1833 if (cur == es->cursor && leftside)
1834 ncol = col + pwidth;
1835 if ((ch = es->cbuf[cur]) == '\t') {
1836 do {
1837 *twb1++ = ' ';
1838 } while (++col < winwidth && (col & 7) != 0);
1839 } else {
1840 if ((ch & 0x80) && Flag(FVISHOW8)) {
1841 *twb1++ = 'M';
1842 if (++col < winwidth) {
1843 *twb1++ = '-';
1844 col++;
1845 }
1846 ch &= 0x7f;
1847 }
1848 if (col < winwidth) {
1849 if (ch < ' ' || ch == 0x7f) {
1850 *twb1++ = '^';
1851 if (++col < winwidth) {
1852 *twb1++ = ch ^ '@';
1853 col++;
1854 }
1855 } else {
1856 *twb1++ = ch;
1857 col++;
1858 }
1859 }
1860 }
1861 if (cur == es->cursor && !leftside)
1862 ncol = col + pwidth - 1;
1863 cur++;
1864 }
1865 if (cur == es->cursor)
1866 ncol = col + pwidth;
1867 if (col < winwidth) {
1868 while (col < winwidth) {
1869 *twb1++ = ' ';
1870 col++;
1871 }
1872 } else
1873 moreright++;
1874 *twb1 = ' ';
1875
1876 col = pwidth;
1877 cnt = winwidth;
1878 twb1 = wb1;
1879 twb2 = wb2;
1880 while (cnt--) {
1881 if (*twb1 != *twb2) {
1882 if (cur_col != col)
1883 ed_mov_opt(col, wb1);
1884 x_putc(*twb1);
1885 cur_col++;
1886 }
1887 twb1++;
1888 twb2++;
1889 col++;
1890 }
1891 if (es->winleft > 0 && moreright)
1892 /* POSIX says to use * for this but that is a globbing
1893 * character and may confuse people; + is more innocuous
1894 */
1895 mc = '+';
1896 else if (es->winleft > 0)
1897 mc = '<';
1898 else if (moreright)
1899 mc = '>';
1900 else
1901 mc = ' ';
1902 if (mc != morec) {
1903 ed_mov_opt(pwidth + winwidth + 1, wb1);
1904 x_putc(mc);
1905 cur_col++;
1906 morec = mc;
1907 }
1908 if (cur_col != ncol)
1909 ed_mov_opt(ncol, wb1);
1910 }
1911
1912 static void
ed_mov_opt(col,wb)1913 ed_mov_opt(col, wb)
1914 int col;
1915 char *wb;
1916 {
1917 if (col < cur_col) {
1918 if (col + 1 < cur_col - col) {
1919 x_putc('\r');
1920 vi_pprompt(0);
1921 cur_col = pwidth;
1922 while (cur_col++ < col)
1923 x_putc(*wb++);
1924 } else {
1925 while (cur_col-- > col)
1926 x_putc('\b');
1927 }
1928 } else {
1929 wb = &wb[cur_col - pwidth];
1930 while (cur_col++ < col)
1931 x_putc(*wb++);
1932 }
1933 cur_col = col;
1934 }
1935
1936
1937 /* replace word with all expansions (ie, expand word*) */
1938 static int
expand_word(commandx)1939 expand_word(commandx)
1940 int commandx;
1941 {
1942 static struct edstate *buf;
1943 int rval = 0;
1944 int nwords;
1945 int start, end;
1946 char **words;
1947 int i;
1948
1949 /* Undo previous expansion */
1950 if (commandx == 0 && expanded == EXPAND && buf) {
1951 restore_edstate(es, buf);
1952 buf = 0;
1953 expanded = NONE;
1954 return 0;
1955 }
1956 if (buf) {
1957 free_edstate(buf);
1958 buf = 0;
1959 }
1960
1961 nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
1962 es->cbuf, es->linelen, es->cursor,
1963 &start, &end, &words, (int *) 0);
1964 if (nwords == 0) {
1965 vi_error();
1966 return -1;
1967 }
1968
1969 buf = save_edstate(es);
1970 expanded = EXPAND;
1971 del_range(start, end);
1972 es->cursor = start;
1973 for (i = 0; i < nwords; ) {
1974 if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
1975 rval = -1;
1976 break;
1977 }
1978 if (++i < nwords && putbuf(space, 1, 0) != 0) {
1979 rval = -1;
1980 break;
1981 }
1982 }
1983 i = buf->cursor - end;
1984 if (rval == 0 && i > 0)
1985 es->cursor += i;
1986 modified = 1; hnum = hlast;
1987 insert = INSERT;
1988 lastac = 0;
1989 refresh(0);
1990 return rval;
1991 }
1992
1993 static int
complete_word(commandx,count)1994 complete_word(commandx, count)
1995 int commandx;
1996 int count;
1997 {
1998 static struct edstate *buf;
1999 int rval = 0;
2000 int nwords;
2001 int start, end;
2002 char **words;
2003 char *match;
2004 int match_len;
2005 int is_unique;
2006 int is_command;
2007
2008 /* Undo previous completion */
2009 if (commandx == 0 && expanded == COMPLETE && buf) {
2010 print_expansions(buf, 0);
2011 expanded = PRINT;
2012 return 0;
2013 }
2014 if (commandx == 0 && expanded == PRINT && buf) {
2015 restore_edstate(es, buf);
2016 buf = 0;
2017 expanded = NONE;
2018 return 0;
2019 }
2020 if (buf) {
2021 free_edstate(buf);
2022 buf = 0;
2023 }
2024
2025 /* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
2026 * was done this way.
2027 */
2028 nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
2029 es->cbuf, es->linelen, es->cursor,
2030 &start, &end, &words, &is_command);
2031 if (nwords == 0) {
2032 vi_error();
2033 return -1;
2034 }
2035 if (count) {
2036 int i;
2037
2038 count--;
2039 if (count >= nwords) {
2040 vi_error();
2041 x_print_expansions(nwords, words, is_command);
2042 x_free_words(nwords, words);
2043 redraw_line(0);
2044 return -1;
2045 }
2046 /*
2047 * Expand the count'th word to its basename
2048 */
2049 if (is_command) {
2050 match = words[count]
2051 + x_basename(words[count], (char *) 0);
2052 /* If more than one possible match, use full path */
2053 for (i = 0; i < nwords; i++)
2054 if (i != count &&
2055 FILECMP(words[i]
2056 + x_basename(words[i], (char *) 0),
2057 match) == 0)
2058 {
2059 match = words[count];
2060 break;
2061 }
2062 } else
2063 match = words[count];
2064 match_len = strlen(match);
2065 is_unique = 1;
2066 /* expanded = PRINT; next call undo */
2067 } else {
2068 match = words[0];
2069 match_len = x_longest_prefix(nwords, words);
2070 expanded = COMPLETE; /* next call will list completions */
2071 is_unique = nwords == 1;
2072 }
2073
2074 buf = save_edstate(es);
2075 del_range(start, end);
2076 es->cursor = start;
2077
2078 /* escape all shell-sensitive characters and put the result into
2079 * command buffer */
2080 rval = x_escape(match, match_len, x_vi_putbuf);
2081
2082 if (rval == 0 && is_unique) {
2083 /* If exact match, don't undo. Allows directory completions
2084 * to be used (ie, complete the next portion of the path).
2085 */
2086 expanded = NONE;
2087
2088 /* If not a directory, add a space to the end... */
2089 if (match_len > 0 && !ISDIRSEP(match[match_len - 1]))
2090 rval = putbuf(space, 1, 0);
2091 }
2092 x_free_words(nwords, words);
2093
2094 modified = 1; hnum = hlast;
2095 insert = INSERT;
2096 lastac = 0; /* prevent this from being redone... */
2097 refresh(0);
2098
2099 return rval;
2100 }
2101
2102 static int
print_expansions(ex,commandx)2103 print_expansions(ex, commandx)
2104 struct edstate *ex;
2105 int commandx;
2106 {
2107 int nwords;
2108 int start, end;
2109 char **words;
2110 int is_command;
2111
2112 nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
2113 ex->cbuf, ex->linelen, ex->cursor,
2114 &start, &end, &words, &is_command);
2115 if (nwords == 0) {
2116 vi_error();
2117 return -1;
2118 }
2119 x_print_expansions(nwords, words, is_command);
2120 x_free_words(nwords, words);
2121 redraw_line(0);
2122 return 0;
2123 }
2124
2125 /* How long is char when displayed (not counting tabs) */
2126 static int
char_len(c)2127 char_len(c)
2128 int c;
2129 {
2130 int len = 1;
2131
2132 if ((c & 0x80) && Flag(FVISHOW8)) {
2133 len += 2;
2134 c &= 0x7f;
2135 }
2136 if (c < ' ' || c == 0x7f)
2137 len++;
2138 return len;
2139 }
2140
2141 /* Similar to x_zotc(emacs.c), but no tab weirdness */
2142 static void
x_vi_zotc(c)2143 x_vi_zotc(c)
2144 int c;
2145 {
2146 if (Flag(FVISHOW8) && (c & 0x80)) {
2147 x_puts("M-");
2148 c &= 0x7f;
2149 }
2150 if (c < ' ' || c == 0x7f) {
2151 x_putc('^');
2152 c ^= '@';
2153 }
2154 x_putc(c);
2155 }
2156
2157 static void
vi_pprompt(full)2158 vi_pprompt(full)
2159 int full;
2160 {
2161 pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
2162 }
2163
2164 static void
vi_error()2165 vi_error()
2166 {
2167 /* Beem out of any macros as soon as an error occurs */
2168 vi_macro_reset();
2169 x_putc(BEL);
2170 x_flush();
2171 }
2172
2173 static void
vi_macro_reset()2174 vi_macro_reset()
2175 {
2176 if (macro.p) {
2177 afree(macro.buf, APERM);
2178 memset((char *) ¯o, 0, sizeof(macro));
2179 }
2180 }
2181
2182 #endif /* VI */
2183