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