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