Magellan Linux

Contents of /trunk/mkinitrd-magellan/busybox/editors/vi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (show annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 9 months ago) by niro
File MIME type: text/plain
File size: 108803 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

1 /* vi: set sw=4 ts=4: */
2 /*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
6 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
7 */
8
9 /*
10 * Things To Do:
11 * EXINIT
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
14 * add :help command
15 * :map macros
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
22 */
23
24 #include "busybox.h"
25
26 #define ENABLE_FEATURE_VI_CRASHME 0
27
28 #if ENABLE_LOCALE_SUPPORT
29 #define Isprint(c) isprint((c))
30 #else
31 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
32 #endif
33
34 #define MAX_SCR_COLS BUFSIZ
35
36 // Misc. non-Ascii keys that report an escape sequence
37 #define VI_K_UP 128 // cursor key Up
38 #define VI_K_DOWN 129 // cursor key Down
39 #define VI_K_RIGHT 130 // Cursor Key Right
40 #define VI_K_LEFT 131 // cursor key Left
41 #define VI_K_HOME 132 // Cursor Key Home
42 #define VI_K_END 133 // Cursor Key End
43 #define VI_K_INSERT 134 // Cursor Key Insert
44 #define VI_K_PAGEUP 135 // Cursor Key Page Up
45 #define VI_K_PAGEDOWN 136 // Cursor Key Page Down
46 #define VI_K_FUN1 137 // Function Key F1
47 #define VI_K_FUN2 138 // Function Key F2
48 #define VI_K_FUN3 139 // Function Key F3
49 #define VI_K_FUN4 140 // Function Key F4
50 #define VI_K_FUN5 141 // Function Key F5
51 #define VI_K_FUN6 142 // Function Key F6
52 #define VI_K_FUN7 143 // Function Key F7
53 #define VI_K_FUN8 144 // Function Key F8
54 #define VI_K_FUN9 145 // Function Key F9
55 #define VI_K_FUN10 146 // Function Key F10
56 #define VI_K_FUN11 147 // Function Key F11
57 #define VI_K_FUN12 148 // Function Key F12
58
59 /* vt102 typical ESC sequence */
60 /* terminal standout start/normal ESC sequence */
61 static const char SOs[] = "\033[7m";
62 static const char SOn[] = "\033[0m";
63 /* terminal bell sequence */
64 static const char bell[] = "\007";
65 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
66 static const char Ceol[] = "\033[0K";
67 static const char Ceos [] = "\033[0J";
68 /* Cursor motion arbitrary destination ESC sequence */
69 static const char CMrc[] = "\033[%d;%dH";
70 /* Cursor motion up and down ESC sequence */
71 static const char CMup[] = "\033[A";
72 static const char CMdown[] = "\n";
73
74
75 enum {
76 YANKONLY = FALSE,
77 YANKDEL = TRUE,
78 FORWARD = 1, // code depends on "1" for array index
79 BACK = -1, // code depends on "-1" for array index
80 LIMITED = 0, // how much of text[] in char_search
81 FULL = 1, // how much of text[] in char_search
82
83 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
84 S_TO_WS = 2, // used in skip_thing() for moving "dot"
85 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
86 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
87 S_END_ALNUM = 5 // used in skip_thing() for moving "dot"
88 };
89
90 typedef unsigned char Byte;
91
92 static int vi_setops;
93 #define VI_AUTOINDENT 1
94 #define VI_SHOWMATCH 2
95 #define VI_IGNORECASE 4
96 #define VI_ERR_METHOD 8
97 #define autoindent (vi_setops & VI_AUTOINDENT)
98 #define showmatch (vi_setops & VI_SHOWMATCH )
99 #define ignorecase (vi_setops & VI_IGNORECASE)
100 /* indicate error with beep or flash */
101 #define err_method (vi_setops & VI_ERR_METHOD)
102
103
104 static int editing; // >0 while we are editing a file
105 static int cmd_mode; // 0=command 1=insert 2=replace
106 static int file_modified; // buffer contents changed
107 static int last_file_modified = -1;
108 static int fn_start; // index of first cmd line file name
109 static int save_argc; // how many file names on cmd line
110 static int cmdcnt; // repetition count
111 static fd_set rfds; // use select() for small sleeps
112 static struct timeval tv; // use select() for small sleeps
113 static int rows, columns; // the terminal screen is this size
114 static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
115 static Byte *status_buffer; // mesages to the user
116 #define STATUS_BUFFER_LEN 200
117 static int have_status_msg; // is default edit status needed?
118 static int last_status_cksum; // hash of current status line
119 static Byte *cfn; // previous, current, and next file name
120 static Byte *text, *end; // pointers to the user data in memory
121 static Byte *screen; // pointer to the virtual screen buffer
122 static int screensize; // and its size
123 static Byte *screenbegin; // index into text[], of top line on the screen
124 static Byte *dot; // where all the action takes place
125 static int tabstop;
126 static struct termios term_orig, term_vi; // remember what the cooked mode was
127 static Byte erase_char; // the users erase character
128 static Byte last_input_char; // last char read from user
129 static Byte last_forward_char; // last char searched for with 'f'
130
131 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
132 static int last_row; // where the cursor was last moved to
133 #endif
134 #if ENABLE_FEATURE_VI_USE_SIGNALS
135 static jmp_buf restart; // catch_sig()
136 #endif
137 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
138 static int my_pid;
139 #endif
140 #if ENABLE_FEATURE_VI_DOT_CMD
141 static int adding2q; // are we currently adding user input to q
142 static Byte *last_modifying_cmd; // last modifying cmd for "."
143 static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
144 #endif
145 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
146 static Byte *modifying_cmds; // cmds that modify text[]
147 #endif
148 #if ENABLE_FEATURE_VI_READONLY
149 static int vi_readonly, readonly;
150 #endif
151 #if ENABLE_FEATURE_VI_YANKMARK
152 static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
153 static int YDreg, Ureg; // default delete register and orig line for "U"
154 static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
155 static Byte *context_start, *context_end;
156 #endif
157 #if ENABLE_FEATURE_VI_SEARCH
158 static Byte *last_search_pattern; // last pattern from a '/' or '?' search
159 #endif
160
161
162 static void edit_file(Byte *); // edit one file
163 static void do_cmd(Byte); // execute a command
164 static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
165 static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
166 static Byte *end_line(Byte *); // return pointer to cur line E-o-l
167 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
168 static Byte *next_line(Byte *); // return pointer to next line B-o-l
169 static Byte *end_screen(void); // get pointer to last char on screen
170 static int count_lines(Byte *, Byte *); // count line from start to stop
171 static Byte *find_line(int); // find begining of line #li
172 static Byte *move_to_col(Byte *, int); // move "p" to column l
173 static int isblnk(Byte); // is the char a blank or tab
174 static void dot_left(void); // move dot left- dont leave line
175 static void dot_right(void); // move dot right- dont leave line
176 static void dot_begin(void); // move dot to B-o-l
177 static void dot_end(void); // move dot to E-o-l
178 static void dot_next(void); // move dot to next line B-o-l
179 static void dot_prev(void); // move dot to prev line B-o-l
180 static void dot_scroll(int, int); // move the screen up or down
181 static void dot_skip_over_ws(void); // move dot pat WS
182 static void dot_delete(void); // delete the char at 'dot'
183 static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
184 static Byte *new_screen(int, int); // malloc virtual screen memory
185 static Byte *new_text(int); // malloc memory for text[] buffer
186 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
187 static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
188 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
189 static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
190 static Byte *skip_thing(Byte *, int, int, int); // skip some object
191 static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
192 static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
193 static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
194 static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
195 static void show_help(void); // display some help info
196 static void rawmode(void); // set "raw" mode on tty
197 static void cookmode(void); // return to "cooked" mode on tty
198 static int mysleep(int); // sleep for 'h' 1/100 seconds
199 static Byte readit(void); // read (maybe cursor) key from stdin
200 static Byte get_one_char(void); // read 1 char from stdin
201 static int file_size(const Byte *); // what is the byte size of "fn"
202 static int file_insert(Byte *, Byte *, int);
203 static int file_write(Byte *, Byte *, Byte *);
204 static void place_cursor(int, int, int);
205 static void screen_erase(void);
206 static void clear_to_eol(void);
207 static void clear_to_eos(void);
208 static void standout_start(void); // send "start reverse video" sequence
209 static void standout_end(void); // send "end reverse video" sequence
210 static void flash(int); // flash the terminal screen
211 static void show_status_line(void); // put a message on the bottom line
212 static void psb(const char *, ...); // Print Status Buf
213 static void psbs(const char *, ...); // Print Status Buf in standout mode
214 static void ni(Byte *); // display messages
215 static int format_edit_status(void); // format file status on status line
216 static void redraw(int); // force a full screen refresh
217 static void format_line(Byte*, Byte*, int);
218 static void refresh(int); // update the terminal from screen[]
219
220 static void Indicate_Error(void); // use flash or beep to indicate error
221 #define indicate_error(c) Indicate_Error()
222 static void Hit_Return(void);
223
224 #if ENABLE_FEATURE_VI_SEARCH
225 static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
226 static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
227 #endif
228 #if ENABLE_FEATURE_VI_COLON
229 static Byte *get_one_address(Byte *, int *); // get colon addr, if present
230 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
231 static void colon(Byte *); // execute the "colon" mode cmds
232 #endif
233 #if ENABLE_FEATURE_VI_USE_SIGNALS
234 static void winch_sig(int); // catch window size changes
235 static void suspend_sig(int); // catch ctrl-Z
236 static void catch_sig(int); // catch ctrl-C and alarm time-outs
237 #endif
238 #if ENABLE_FEATURE_VI_DOT_CMD
239 static void start_new_cmd_q(Byte); // new queue for command
240 static void end_cmd_q(void); // stop saving input chars
241 #else
242 #define end_cmd_q() ((void)0)
243 #endif
244 #if ENABLE_FEATURE_VI_SETOPTS
245 static void showmatching(Byte *); // show the matching pair () [] {}
246 #endif
247 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
248 static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
249 #endif
250 #if ENABLE_FEATURE_VI_YANKMARK
251 static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
252 static Byte what_reg(void); // what is letter of current YDreg
253 static void check_context(Byte); // remember context for '' command
254 #endif
255 #if ENABLE_FEATURE_VI_CRASHME
256 static void crash_dummy();
257 static void crash_test();
258 static int crashme = 0;
259 #endif
260
261
262 static void write1(const char *out)
263 {
264 fputs(out, stdout);
265 }
266
267 int vi_main(int argc, char **argv)
268 {
269 int c;
270 RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
271
272 #if ENABLE_FEATURE_VI_YANKMARK
273 int i;
274 #endif
275 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
276 my_pid = getpid();
277 #endif
278 #if ENABLE_FEATURE_VI_CRASHME
279 (void) srand((long) my_pid);
280 #endif
281
282 status_buffer = (Byte *)STATUS_BUFFER;
283 last_status_cksum = 0;
284
285 #if ENABLE_FEATURE_VI_READONLY
286 vi_readonly = readonly = FALSE;
287 if (strncmp(argv[0], "view", 4) == 0) {
288 readonly = TRUE;
289 vi_readonly = TRUE;
290 }
291 #endif
292 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
293 #if ENABLE_FEATURE_VI_YANKMARK
294 for (i = 0; i < 28; i++) {
295 reg[i] = 0;
296 } // init the yank regs
297 #endif
298 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
299 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
300 #endif
301
302 // 1- process $HOME/.exrc file
303 // 2- process EXINIT variable from environment
304 // 3- process command line args
305 while ((c = getopt(argc, argv, "hCR")) != -1) {
306 switch (c) {
307 #if ENABLE_FEATURE_VI_CRASHME
308 case 'C':
309 crashme = 1;
310 break;
311 #endif
312 #if ENABLE_FEATURE_VI_READONLY
313 case 'R': // Read-only flag
314 readonly = TRUE;
315 vi_readonly = TRUE;
316 break;
317 #endif
318 //case 'r': // recover flag- ignore- we don't use tmp file
319 //case 'x': // encryption flag- ignore
320 //case 'c': // execute command first
321 //case 'h': // help -- just use default
322 default:
323 show_help();
324 return 1;
325 }
326 }
327
328 // The argv array can be used by the ":next" and ":rewind" commands
329 // save optind.
330 fn_start = optind; // remember first file name for :next and :rew
331 save_argc = argc;
332
333 //----- This is the main file handling loop --------------
334 if (optind >= argc) {
335 editing = 1; // 0= exit, 1= one file, 2= multiple files
336 edit_file(0);
337 } else {
338 for (; optind < argc; optind++) {
339 editing = 1; // 0=exit, 1=one file, 2+ =many files
340 free(cfn);
341 cfn = (Byte *) xstrdup(argv[optind]);
342 edit_file(cfn);
343 }
344 }
345 //-----------------------------------------------------------
346
347 return 0;
348 }
349
350 static void edit_file(Byte * fn)
351 {
352 Byte c;
353 int cnt, size, ch;
354
355 #if ENABLE_FEATURE_VI_USE_SIGNALS
356 int sig;
357 #endif
358 #if ENABLE_FEATURE_VI_YANKMARK
359 static Byte *cur_line;
360 #endif
361
362 rawmode();
363 rows = 24;
364 columns = 80;
365 ch= -1;
366 if (ENABLE_FEATURE_VI_WIN_RESIZE)
367 get_terminal_width_height(0, &columns, &rows);
368 new_screen(rows, columns); // get memory for virtual screen
369
370 cnt = file_size(fn); // file size
371 size = 2 * cnt; // 200% of file size
372 new_text(size); // get a text[] buffer
373 screenbegin = dot = end = text;
374 if (fn != 0) {
375 ch= file_insert(fn, text, cnt);
376 }
377 if (ch < 1) {
378 (void) char_insert(text, '\n'); // start empty buf with dummy line
379 }
380 file_modified = 0;
381 last_file_modified = -1;
382 #if ENABLE_FEATURE_VI_YANKMARK
383 YDreg = 26; // default Yank/Delete reg
384 Ureg = 27; // hold orig line for "U" cmd
385 for (cnt = 0; cnt < 28; cnt++) {
386 mark[cnt] = 0;
387 } // init the marks
388 mark[26] = mark[27] = text; // init "previous context"
389 #endif
390
391 last_forward_char = last_input_char = '\0';
392 crow = 0;
393 ccol = 0;
394
395 #if ENABLE_FEATURE_VI_USE_SIGNALS
396 catch_sig(0);
397 signal(SIGWINCH, winch_sig);
398 signal(SIGTSTP, suspend_sig);
399 sig = setjmp(restart);
400 if (sig != 0) {
401 screenbegin = dot = text;
402 }
403 #endif
404
405 editing = 1;
406 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
407 cmdcnt = 0;
408 tabstop = 8;
409 offset = 0; // no horizontal offset
410 c = '\0';
411 #if ENABLE_FEATURE_VI_DOT_CMD
412 free(last_modifying_cmd);
413 free(ioq_start);
414 ioq = ioq_start = last_modifying_cmd = 0;
415 adding2q = 0;
416 #endif
417 redraw(FALSE); // dont force every col re-draw
418 show_status_line();
419
420 //------This is the main Vi cmd handling loop -----------------------
421 while (editing > 0) {
422 #if ENABLE_FEATURE_VI_CRASHME
423 if (crashme > 0) {
424 if ((end - text) > 1) {
425 crash_dummy(); // generate a random command
426 } else {
427 crashme = 0;
428 dot =
429 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
430 refresh(FALSE);
431 }
432 }
433 #endif
434 last_input_char = c = get_one_char(); // get a cmd from user
435 #if ENABLE_FEATURE_VI_YANKMARK
436 // save a copy of the current line- for the 'U" command
437 if (begin_line(dot) != cur_line) {
438 cur_line = begin_line(dot);
439 text_yank(begin_line(dot), end_line(dot), Ureg);
440 }
441 #endif
442 #if ENABLE_FEATURE_VI_DOT_CMD
443 // These are commands that change text[].
444 // Remember the input for the "." command
445 if (!adding2q && ioq_start == 0
446 && strchr((char *) modifying_cmds, c) != NULL) {
447 start_new_cmd_q(c);
448 }
449 #endif
450 do_cmd(c); // execute the user command
451 //
452 // poll to see if there is input already waiting. if we are
453 // not able to display output fast enough to keep up, skip
454 // the display update until we catch up with input.
455 if (mysleep(0) == 0) {
456 // no input pending- so update output
457 refresh(FALSE);
458 show_status_line();
459 }
460 #if ENABLE_FEATURE_VI_CRASHME
461 if (crashme > 0)
462 crash_test(); // test editor variables
463 #endif
464 }
465 //-------------------------------------------------------------------
466
467 place_cursor(rows, 0, FALSE); // go to bottom of screen
468 clear_to_eol(); // Erase to end of line
469 cookmode();
470 }
471
472 //----- The Colon commands -------------------------------------
473 #if ENABLE_FEATURE_VI_COLON
474 static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present
475 {
476 int st;
477 Byte *q;
478
479 #if ENABLE_FEATURE_VI_YANKMARK
480 Byte c;
481 #endif
482 #if ENABLE_FEATURE_VI_SEARCH
483 Byte *pat, buf[BUFSIZ];
484 #endif
485
486 *addr = -1; // assume no addr
487 if (*p == '.') { // the current line
488 p++;
489 q = begin_line(dot);
490 *addr = count_lines(text, q);
491 #if ENABLE_FEATURE_VI_YANKMARK
492 } else if (*p == '\'') { // is this a mark addr
493 p++;
494 c = tolower(*p);
495 p++;
496 if (c >= 'a' && c <= 'z') {
497 // we have a mark
498 c = c - 'a';
499 q = mark[(int) c];
500 if (q != NULL) { // is mark valid
501 *addr = count_lines(text, q); // count lines
502 }
503 }
504 #endif
505 #if ENABLE_FEATURE_VI_SEARCH
506 } else if (*p == '/') { // a search pattern
507 q = buf;
508 for (p++; *p; p++) {
509 if (*p == '/')
510 break;
511 *q++ = *p;
512 *q = '\0';
513 }
514 pat = (Byte *) xstrdup((char *) buf); // save copy of pattern
515 if (*p == '/')
516 p++;
517 q = char_search(dot, pat, FORWARD, FULL);
518 if (q != NULL) {
519 *addr = count_lines(text, q);
520 }
521 free(pat);
522 #endif
523 } else if (*p == '$') { // the last line in file
524 p++;
525 q = begin_line(end - 1);
526 *addr = count_lines(text, q);
527 } else if (isdigit(*p)) { // specific line number
528 sscanf((char *) p, "%d%n", addr, &st);
529 p += st;
530 } else { // I don't reconise this
531 // unrecognised address- assume -1
532 *addr = -1;
533 }
534 return p;
535 }
536
537 static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present
538 {
539 //----- get the address' i.e., 1,3 'a,'b -----
540 // get FIRST addr, if present
541 while (isblnk(*p))
542 p++; // skip over leading spaces
543 if (*p == '%') { // alias for 1,$
544 p++;
545 *b = 1;
546 *e = count_lines(text, end-1);
547 goto ga0;
548 }
549 p = get_one_address(p, b);
550 while (isblnk(*p))
551 p++;
552 if (*p == ',') { // is there a address separator
553 p++;
554 while (isblnk(*p))
555 p++;
556 // get SECOND addr, if present
557 p = get_one_address(p, e);
558 }
559 ga0:
560 while (isblnk(*p))
561 p++; // skip over trailing spaces
562 return p;
563 }
564
565 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
566 static void setops(const Byte *args, const char *opname, int flg_no,
567 const char *short_opname, int opt)
568 {
569 const char *a = (char *) args + flg_no;
570 int l = strlen(opname) - 1; /* opname have + ' ' */
571
572 if (strncasecmp(a, opname, l) == 0 ||
573 strncasecmp(a, short_opname, 2) == 0) {
574 if(flg_no)
575 vi_setops &= ~opt;
576 else
577 vi_setops |= opt;
578 }
579 }
580 #endif
581
582 static void colon(Byte * buf)
583 {
584 Byte c, *orig_buf, *buf1, *q, *r;
585 Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
586 int i, l, li, ch, b, e;
587 int useforce = FALSE, forced = FALSE;
588 struct stat st_buf;
589
590 // :3154 // if (-e line 3154) goto it else stay put
591 // :4,33w! foo // write a portion of buffer to file "foo"
592 // :w // write all of buffer to current file
593 // :q // quit
594 // :q! // quit- dont care about modified file
595 // :'a,'z!sort -u // filter block through sort
596 // :'f // goto mark "f"
597 // :'fl // list literal the mark "f" line
598 // :.r bar // read file "bar" into buffer before dot
599 // :/123/,/abc/d // delete lines from "123" line to "abc" line
600 // :/xyz/ // goto the "xyz" line
601 // :s/find/replace/ // substitute pattern "find" with "replace"
602 // :!<cmd> // run <cmd> then return
603 //
604
605 if (strlen((char *) buf) <= 0)
606 goto vc1;
607 if (*buf == ':')
608 buf++; // move past the ':'
609
610 li = ch = i = 0;
611 b = e = -1;
612 q = text; // assume 1,$ for the range
613 r = end - 1;
614 li = count_lines(text, end - 1);
615 fn = cfn; // default to current file
616 memset(cmd, '\0', BUFSIZ); // clear cmd[]
617 memset(args, '\0', BUFSIZ); // clear args[]
618
619 // look for optional address(es) :. :1 :1,9 :'q,'a :%
620 buf = get_address(buf, &b, &e);
621
622 // remember orig command line
623 orig_buf = buf;
624
625 // get the COMMAND into cmd[]
626 buf1 = cmd;
627 while (*buf != '\0') {
628 if (isspace(*buf))
629 break;
630 *buf1++ = *buf++;
631 }
632 // get any ARGuments
633 while (isblnk(*buf))
634 buf++;
635 strcpy((char *) args, (char *) buf);
636 buf1 = (Byte*)last_char_is((char *)cmd, '!');
637 if (buf1) {
638 useforce = TRUE;
639 *buf1 = '\0'; // get rid of !
640 }
641 if (b >= 0) {
642 // if there is only one addr, then the addr
643 // is the line number of the single line the
644 // user wants. So, reset the end
645 // pointer to point at end of the "b" line
646 q = find_line(b); // what line is #b
647 r = end_line(q);
648 li = 1;
649 }
650 if (e >= 0) {
651 // we were given two addrs. change the
652 // end pointer to the addr given by user.
653 r = find_line(e); // what line is #e
654 r = end_line(r);
655 li = e - b + 1;
656 }
657 // ------------ now look for the command ------------
658 i = strlen((char *) cmd);
659 if (i == 0) { // :123CR goto line #123
660 if (b >= 0) {
661 dot = find_line(b); // what line is #b
662 dot_skip_over_ws();
663 }
664 }
665 #if ENABLE_FEATURE_ALLOW_EXEC
666 else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd
667 // :!ls run the <cmd>
668 (void) alarm(0); // wait for input- no alarms
669 place_cursor(rows - 1, 0, FALSE); // go to Status line
670 clear_to_eol(); // clear the line
671 cookmode();
672 system((char*)(orig_buf+1)); // run the cmd
673 rawmode();
674 Hit_Return(); // let user see results
675 (void) alarm(3); // done waiting for input
676 }
677 #endif
678 else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
679 if (b < 0) { // no addr given- use defaults
680 b = e = count_lines(text, dot);
681 }
682 psb("%d", b);
683 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
684 if (b < 0) { // no addr given- use defaults
685 q = begin_line(dot); // assume .,. for the range
686 r = end_line(dot);
687 }
688 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
689 dot_skip_over_ws();
690 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
691 int sr;
692 sr= 0;
693 // don't edit, if the current file has been modified
694 if (file_modified && ! useforce) {
695 psbs("No write since last change (:edit! overrides)");
696 goto vc1;
697 }
698 if (strlen((char*)args) > 0) {
699 // the user supplied a file name
700 fn= args;
701 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
702 // no user supplied name- use the current filename
703 fn= cfn;
704 goto vc5;
705 } else {
706 // no user file name, no current name- punt
707 psbs("No current filename");
708 goto vc1;
709 }
710
711 // see if file exists- if not, its just a new file request
712 if ((sr=stat((char*)fn, &st_buf)) < 0) {
713 // This is just a request for a new file creation.
714 // The file_insert below will fail but we get
715 // an empty buffer with a file name. Then the "write"
716 // command can do the create.
717 } else {
718 if ((st_buf.st_mode & (S_IFREG)) == 0) {
719 // This is not a regular file
720 psbs("\"%s\" is not a regular file", fn);
721 goto vc1;
722 }
723 if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
724 // dont have any read permissions
725 psbs("\"%s\" is not readable", fn);
726 goto vc1;
727 }
728 }
729
730 // There is a read-able regular file
731 // make this the current file
732 q = (Byte *) xstrdup((char *) fn); // save the cfn
733 free(cfn); // free the old name
734 cfn = q; // remember new cfn
735
736 vc5:
737 // delete all the contents of text[]
738 new_text(2 * file_size(fn));
739 screenbegin = dot = end = text;
740
741 // insert new file
742 ch = file_insert(fn, text, file_size(fn));
743
744 if (ch < 1) {
745 // start empty buf with dummy line
746 (void) char_insert(text, '\n');
747 ch= 1;
748 }
749 file_modified = 0;
750 last_file_modified = -1;
751 #if ENABLE_FEATURE_VI_YANKMARK
752 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
753 free(reg[Ureg]); // free orig line reg- for 'U'
754 reg[Ureg]= 0;
755 }
756 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
757 free(reg[YDreg]); // free default yank/delete register
758 reg[YDreg]= 0;
759 }
760 for (li = 0; li < 28; li++) {
761 mark[li] = 0;
762 } // init the marks
763 #endif
764 // how many lines in text[]?
765 li = count_lines(text, end - 1);
766 psb("\"%s\"%s"
767 #if ENABLE_FEATURE_VI_READONLY
768 "%s"
769 #endif
770 " %dL, %dC", cfn,
771 (sr < 0 ? " [New file]" : ""),
772 #if ENABLE_FEATURE_VI_READONLY
773 ((vi_readonly || readonly) ? " [Read only]" : ""),
774 #endif
775 li, ch);
776 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
777 if (b != -1 || e != -1) {
778 ni((Byte *) "No address allowed on this command");
779 goto vc1;
780 }
781 if (strlen((char *) args) > 0) {
782 // user wants a new filename
783 free(cfn);
784 cfn = (Byte *) xstrdup((char *) args);
785 } else {
786 // user wants file status info
787 last_status_cksum = 0; // force status update
788 }
789 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
790 // print out values of all features
791 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
792 clear_to_eol(); // clear the line
793 cookmode();
794 show_help();
795 rawmode();
796 Hit_Return();
797 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
798 if (b < 0) { // no addr given- use defaults
799 q = begin_line(dot); // assume .,. for the range
800 r = end_line(dot);
801 }
802 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
803 clear_to_eol(); // clear the line
804 puts("\r");
805 for (; q <= r; q++) {
806 int c_is_no_print;
807
808 c = *q;
809 c_is_no_print = c > 127 && !Isprint(c);
810 if (c_is_no_print) {
811 c = '.';
812 standout_start();
813 }
814 if (c == '\n') {
815 write1("$\r");
816 } else if (c < ' ' || c == 127) {
817 putchar('^');
818 if(c == 127)
819 c = '?';
820 else
821 c += '@';
822 }
823 putchar(c);
824 if (c_is_no_print)
825 standout_end();
826 }
827 #if ENABLE_FEATURE_VI_SET
828 vc2:
829 #endif
830 Hit_Return();
831 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
832 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
833 if (useforce) {
834 // force end of argv list
835 if (*cmd == 'q') {
836 optind = save_argc;
837 }
838 editing = 0;
839 goto vc1;
840 }
841 // don't exit if the file been modified
842 if (file_modified) {
843 psbs("No write since last change (:%s! overrides)",
844 (*cmd == 'q' ? "quit" : "next"));
845 goto vc1;
846 }
847 // are there other file to edit
848 if (*cmd == 'q' && optind < save_argc - 1) {
849 psbs("%d more file to edit", (save_argc - optind - 1));
850 goto vc1;
851 }
852 if (*cmd == 'n' && optind >= save_argc - 1) {
853 psbs("No more files to edit");
854 goto vc1;
855 }
856 editing = 0;
857 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
858 fn = args;
859 if (strlen((char *) fn) <= 0) {
860 psbs("No filename given");
861 goto vc1;
862 }
863 if (b < 0) { // no addr given- use defaults
864 q = begin_line(dot); // assume "dot"
865 }
866 // read after current line- unless user said ":0r foo"
867 if (b != 0)
868 q = next_line(q);
869 #if ENABLE_FEATURE_VI_READONLY
870 l= readonly; // remember current files' status
871 #endif
872 ch = file_insert(fn, q, file_size(fn));
873 #if ENABLE_FEATURE_VI_READONLY
874 readonly= l;
875 #endif
876 if (ch < 0)
877 goto vc1; // nothing was inserted
878 // how many lines in text[]?
879 li = count_lines(q, q + ch - 1);
880 psb("\"%s\""
881 #if ENABLE_FEATURE_VI_READONLY
882 "%s"
883 #endif
884 " %dL, %dC", fn,
885 #if ENABLE_FEATURE_VI_READONLY
886 ((vi_readonly || readonly) ? " [Read only]" : ""),
887 #endif
888 li, ch);
889 if (ch > 0) {
890 // if the insert is before "dot" then we need to update
891 if (q <= dot)
892 dot += ch;
893 file_modified++;
894 }
895 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
896 if (file_modified && ! useforce) {
897 psbs("No write since last change (:rewind! overrides)");
898 } else {
899 // reset the filenames to edit
900 optind = fn_start - 1;
901 editing = 0;
902 }
903 #if ENABLE_FEATURE_VI_SET
904 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
905 i = 0; // offset into args
906 if (strlen((char *) args) == 0) {
907 // print out values of all options
908 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
909 clear_to_eol(); // clear the line
910 printf("----------------------------------------\r\n");
911 #if ENABLE_FEATURE_VI_SETOPTS
912 if (!autoindent)
913 printf("no");
914 printf("autoindent ");
915 if (!err_method)
916 printf("no");
917 printf("flash ");
918 if (!ignorecase)
919 printf("no");
920 printf("ignorecase ");
921 if (!showmatch)
922 printf("no");
923 printf("showmatch ");
924 printf("tabstop=%d ", tabstop);
925 #endif
926 printf("\r\n");
927 goto vc2;
928 }
929 if (strncasecmp((char *) args, "no", 2) == 0)
930 i = 2; // ":set noautoindent"
931 #if ENABLE_FEATURE_VI_SETOPTS
932 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
933 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
934 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
935 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
936 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
937 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
938 if (ch > 0 && ch < columns - 1)
939 tabstop = ch;
940 }
941 #endif /* FEATURE_VI_SETOPTS */
942 #endif /* FEATURE_VI_SET */
943 #if ENABLE_FEATURE_VI_SEARCH
944 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
945 Byte *ls, *F, *R;
946 int gflag;
947
948 // F points to the "find" pattern
949 // R points to the "replace" pattern
950 // replace the cmd line delimiters "/" with NULLs
951 gflag = 0; // global replace flag
952 c = orig_buf[1]; // what is the delimiter
953 F = orig_buf + 2; // start of "find"
954 R = (Byte *) strchr((char *) F, c); // middle delimiter
955 if (!R) goto colon_s_fail;
956 *R++ = '\0'; // terminate "find"
957 buf1 = (Byte *) strchr((char *) R, c);
958 if (!buf1) goto colon_s_fail;
959 *buf1++ = '\0'; // terminate "replace"
960 if (*buf1 == 'g') { // :s/foo/bar/g
961 buf1++;
962 gflag++; // turn on gflag
963 }
964 q = begin_line(q);
965 if (b < 0) { // maybe :s/foo/bar/
966 q = begin_line(dot); // start with cur line
967 b = count_lines(text, q); // cur line number
968 }
969 if (e < 0)
970 e = b; // maybe :.s/foo/bar/
971 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
972 ls = q; // orig line start
973 vc4:
974 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
975 if (buf1 != NULL) {
976 // we found the "find" pattern- delete it
977 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
978 // inset the "replace" patern
979 (void) string_insert(buf1, R); // insert the string
980 // check for "global" :s/foo/bar/g
981 if (gflag == 1) {
982 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
983 q = buf1 + strlen((char *) R);
984 goto vc4; // don't let q move past cur line
985 }
986 }
987 }
988 q = next_line(ls);
989 }
990 #endif /* FEATURE_VI_SEARCH */
991 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
992 psb("%s", BB_VER " " BB_BT);
993 } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file
994 || strncasecmp((char *) cmd, "wq", i) == 0
995 || strncasecmp((char *) cmd, "wn", i) == 0
996 || strncasecmp((char *) cmd, "x", i) == 0) {
997 // is there a file name to write to?
998 if (strlen((char *) args) > 0) {
999 fn = args;
1000 }
1001 #if ENABLE_FEATURE_VI_READONLY
1002 if ((vi_readonly || readonly) && ! useforce) {
1003 psbs("\"%s\" File is read only", fn);
1004 goto vc3;
1005 }
1006 #endif
1007 // how many lines in text[]?
1008 li = count_lines(q, r);
1009 ch = r - q + 1;
1010 // see if file exists- if not, its just a new file request
1011 if (useforce) {
1012 // if "fn" is not write-able, chmod u+w
1013 // sprintf(syscmd, "chmod u+w %s", fn);
1014 // system(syscmd);
1015 forced = TRUE;
1016 }
1017 l = file_write(fn, q, r);
1018 if (useforce && forced) {
1019 // chmod u-w
1020 // sprintf(syscmd, "chmod u-w %s", fn);
1021 // system(syscmd);
1022 forced = FALSE;
1023 }
1024 if (l < 0) {
1025 if (l == -1)
1026 psbs("Write error: %s", strerror(errno));
1027 } else {
1028 psb("\"%s\" %dL, %dC", fn, li, l);
1029 if (q == text && r == end - 1 && l == ch) {
1030 file_modified = 0;
1031 last_file_modified = -1;
1032 }
1033 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1034 cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1035 && l == ch) {
1036 editing = 0;
1037 }
1038 }
1039 #if ENABLE_FEATURE_VI_READONLY
1040 vc3:;
1041 #endif
1042 #if ENABLE_FEATURE_VI_YANKMARK
1043 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1044 if (b < 0) { // no addr given- use defaults
1045 q = begin_line(dot); // assume .,. for the range
1046 r = end_line(dot);
1047 }
1048 text_yank(q, r, YDreg);
1049 li = count_lines(q, r);
1050 psb("Yank %d lines (%d chars) into [%c]",
1051 li, strlen((char *) reg[YDreg]), what_reg());
1052 #endif
1053 } else {
1054 // cmd unknown
1055 ni((Byte *) cmd);
1056 }
1057 vc1:
1058 dot = bound_dot(dot); // make sure "dot" is valid
1059 return;
1060 #if ENABLE_FEATURE_VI_SEARCH
1061 colon_s_fail:
1062 psb(":s expression missing delimiters");
1063 #endif
1064 }
1065
1066 #endif /* FEATURE_VI_COLON */
1067
1068 static void Hit_Return(void)
1069 {
1070 char c;
1071
1072 standout_start(); // start reverse video
1073 write1("[Hit return to continue]");
1074 standout_end(); // end reverse video
1075 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
1076 ;
1077 redraw(TRUE); // force redraw all
1078 }
1079
1080 //----- Synchronize the cursor to Dot --------------------------
1081 static void sync_cursor(Byte * d, int *row, int *col)
1082 {
1083 Byte *beg_cur; // begin and end of "d" line
1084 Byte *end_scr; // begin and end of screen
1085 Byte *tp;
1086 int cnt, ro, co;
1087
1088 beg_cur = begin_line(d); // first char of cur line
1089
1090 end_scr = end_screen(); // last char of screen
1091
1092 if (beg_cur < screenbegin) {
1093 // "d" is before top line on screen
1094 // how many lines do we have to move
1095 cnt = count_lines(beg_cur, screenbegin);
1096 sc1:
1097 screenbegin = beg_cur;
1098 if (cnt > (rows - 1) / 2) {
1099 // we moved too many lines. put "dot" in middle of screen
1100 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1101 screenbegin = prev_line(screenbegin);
1102 }
1103 }
1104 } else if (beg_cur > end_scr) {
1105 // "d" is after bottom line on screen
1106 // how many lines do we have to move
1107 cnt = count_lines(end_scr, beg_cur);
1108 if (cnt > (rows - 1) / 2)
1109 goto sc1; // too many lines
1110 for (ro = 0; ro < cnt - 1; ro++) {
1111 // move screen begin the same amount
1112 screenbegin = next_line(screenbegin);
1113 // now, move the end of screen
1114 end_scr = next_line(end_scr);
1115 end_scr = end_line(end_scr);
1116 }
1117 }
1118 // "d" is on screen- find out which row
1119 tp = screenbegin;
1120 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1121 if (tp == beg_cur)
1122 break;
1123 tp = next_line(tp);
1124 }
1125
1126 // find out what col "d" is on
1127 co = 0;
1128 do { // drive "co" to correct column
1129 if (*tp == '\n' || *tp == '\0')
1130 break;
1131 if (*tp == '\t') {
1132 // 7 - (co % 8 )
1133 co += ((tabstop - 1) - (co % tabstop));
1134 } else if (*tp < ' ' || *tp == 127) {
1135 co++; // display as ^X, use 2 columns
1136 }
1137 } while (tp++ < d && ++co);
1138
1139 // "co" is the column where "dot" is.
1140 // The screen has "columns" columns.
1141 // The currently displayed columns are 0+offset -- columns+ofset
1142 // |-------------------------------------------------------------|
1143 // ^ ^ ^
1144 // offset | |------- columns ----------------|
1145 //
1146 // If "co" is already in this range then we do not have to adjust offset
1147 // but, we do have to subtract the "offset" bias from "co".
1148 // If "co" is outside this range then we have to change "offset".
1149 // If the first char of a line is a tab the cursor will try to stay
1150 // in column 7, but we have to set offset to 0.
1151
1152 if (co < 0 + offset) {
1153 offset = co;
1154 }
1155 if (co >= columns + offset) {
1156 offset = co - columns + 1;
1157 }
1158 // if the first char of the line is a tab, and "dot" is sitting on it
1159 // force offset to 0.
1160 if (d == beg_cur && *d == '\t') {
1161 offset = 0;
1162 }
1163 co -= offset;
1164
1165 *row = ro;
1166 *col = co;
1167 }
1168
1169 //----- Text Movement Routines ---------------------------------
1170 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1171 {
1172 while (p > text && p[-1] != '\n')
1173 p--; // go to cur line B-o-l
1174 return p;
1175 }
1176
1177 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1178 {
1179 while (p < end - 1 && *p != '\n')
1180 p++; // go to cur line E-o-l
1181 return p;
1182 }
1183
1184 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1185 {
1186 while (p < end - 1 && *p != '\n')
1187 p++; // go to cur line E-o-l
1188 // Try to stay off of the Newline
1189 if (*p == '\n' && (p - begin_line(p)) > 0)
1190 p--;
1191 return p;
1192 }
1193
1194 static Byte *prev_line(Byte * p) // return pointer first char prev line
1195 {
1196 p = begin_line(p); // goto begining of cur line
1197 if (p[-1] == '\n' && p > text)
1198 p--; // step to prev line
1199 p = begin_line(p); // goto begining of prev line
1200 return p;
1201 }
1202
1203 static Byte *next_line(Byte * p) // return pointer first char next line
1204 {
1205 p = end_line(p);
1206 if (*p == '\n' && p < end - 1)
1207 p++; // step to next line
1208 return p;
1209 }
1210
1211 //----- Text Information Routines ------------------------------
1212 static Byte *end_screen(void)
1213 {
1214 Byte *q;
1215 int cnt;
1216
1217 // find new bottom line
1218 q = screenbegin;
1219 for (cnt = 0; cnt < rows - 2; cnt++)
1220 q = next_line(q);
1221 q = end_line(q);
1222 return q;
1223 }
1224
1225 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1226 {
1227 Byte *q;
1228 int cnt;
1229
1230 if (stop < start) { // start and stop are backwards- reverse them
1231 q = start;
1232 start = stop;
1233 stop = q;
1234 }
1235 cnt = 0;
1236 stop = end_line(stop); // get to end of this line
1237 for (q = start; q <= stop && q <= end - 1; q++) {
1238 if (*q == '\n')
1239 cnt++;
1240 }
1241 return cnt;
1242 }
1243
1244 static Byte *find_line(int li) // find begining of line #li
1245 {
1246 Byte *q;
1247
1248 for (q = text; li > 1; li--) {
1249 q = next_line(q);
1250 }
1251 return q;
1252 }
1253
1254 //----- Dot Movement Routines ----------------------------------
1255 static void dot_left(void)
1256 {
1257 if (dot > text && dot[-1] != '\n')
1258 dot--;
1259 }
1260
1261 static void dot_right(void)
1262 {
1263 if (dot < end - 1 && *dot != '\n')
1264 dot++;
1265 }
1266
1267 static void dot_begin(void)
1268 {
1269 dot = begin_line(dot); // return pointer to first char cur line
1270 }
1271
1272 static void dot_end(void)
1273 {
1274 dot = end_line(dot); // return pointer to last char cur line
1275 }
1276
1277 static Byte *move_to_col(Byte * p, int l)
1278 {
1279 int co;
1280
1281 p = begin_line(p);
1282 co = 0;
1283 do {
1284 if (*p == '\n' || *p == '\0')
1285 break;
1286 if (*p == '\t') {
1287 // 7 - (co % 8 )
1288 co += ((tabstop - 1) - (co % tabstop));
1289 } else if (*p < ' ' || *p == 127) {
1290 co++; // display as ^X, use 2 columns
1291 }
1292 } while (++co <= l && p++ < end);
1293 return p;
1294 }
1295
1296 static void dot_next(void)
1297 {
1298 dot = next_line(dot);
1299 }
1300
1301 static void dot_prev(void)
1302 {
1303 dot = prev_line(dot);
1304 }
1305
1306 static void dot_scroll(int cnt, int dir)
1307 {
1308 Byte *q;
1309
1310 for (; cnt > 0; cnt--) {
1311 if (dir < 0) {
1312 // scroll Backwards
1313 // ctrl-Y scroll up one line
1314 screenbegin = prev_line(screenbegin);
1315 } else {
1316 // scroll Forwards
1317 // ctrl-E scroll down one line
1318 screenbegin = next_line(screenbegin);
1319 }
1320 }
1321 // make sure "dot" stays on the screen so we dont scroll off
1322 if (dot < screenbegin)
1323 dot = screenbegin;
1324 q = end_screen(); // find new bottom line
1325 if (dot > q)
1326 dot = begin_line(q); // is dot is below bottom line?
1327 dot_skip_over_ws();
1328 }
1329
1330 static void dot_skip_over_ws(void)
1331 {
1332 // skip WS
1333 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1334 dot++;
1335 }
1336
1337 static void dot_delete(void) // delete the char at 'dot'
1338 {
1339 (void) text_hole_delete(dot, dot);
1340 }
1341
1342 static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
1343 {
1344 if (p >= end && end > text) {
1345 p = end - 1;
1346 indicate_error('1');
1347 }
1348 if (p < text) {
1349 p = text;
1350 indicate_error('2');
1351 }
1352 return p;
1353 }
1354
1355 //----- Helper Utility Routines --------------------------------
1356
1357 //----------------------------------------------------------------
1358 //----- Char Routines --------------------------------------------
1359 /* Chars that are part of a word-
1360 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1361 * Chars that are Not part of a word (stoppers)
1362 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1363 * Chars that are WhiteSpace
1364 * TAB NEWLINE VT FF RETURN SPACE
1365 * DO NOT COUNT NEWLINE AS WHITESPACE
1366 */
1367
1368 static Byte *new_screen(int ro, int co)
1369 {
1370 int li;
1371
1372 free(screen);
1373 screensize = ro * co + 8;
1374 screen = xmalloc(screensize);
1375 // initialize the new screen. assume this will be a empty file.
1376 screen_erase();
1377 // non-existent text[] lines start with a tilde (~).
1378 for (li = 1; li < ro - 1; li++) {
1379 screen[(li * co) + 0] = '~';
1380 }
1381 return screen;
1382 }
1383
1384 static Byte *new_text(int size)
1385 {
1386 if (size < 10240)
1387 size = 10240; // have a minimum size for new files
1388 free(text);
1389 text = xmalloc(size + 8);
1390 memset(text, '\0', size); // clear new text[]
1391 //text += 4; // leave some room for "oops"
1392 return text;
1393 }
1394
1395 #if ENABLE_FEATURE_VI_SEARCH
1396 static int mycmp(Byte * s1, Byte * s2, int len)
1397 {
1398 int i;
1399
1400 i = strncmp((char *) s1, (char *) s2, len);
1401 #if ENABLE_FEATURE_VI_SETOPTS
1402 if (ignorecase) {
1403 i = strncasecmp((char *) s1, (char *) s2, len);
1404 }
1405 #endif
1406 return i;
1407 }
1408
1409 static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
1410 {
1411 #ifndef REGEX_SEARCH
1412 Byte *start, *stop;
1413 int len;
1414
1415 len = strlen((char *) pat);
1416 if (dir == FORWARD) {
1417 stop = end - 1; // assume range is p - end-1
1418 if (range == LIMITED)
1419 stop = next_line(p); // range is to next line
1420 for (start = p; start < stop; start++) {
1421 if (mycmp(start, pat, len) == 0) {
1422 return start;
1423 }
1424 }
1425 } else if (dir == BACK) {
1426 stop = text; // assume range is text - p
1427 if (range == LIMITED)
1428 stop = prev_line(p); // range is to prev line
1429 for (start = p - len; start >= stop; start--) {
1430 if (mycmp(start, pat, len) == 0) {
1431 return start;
1432 }
1433 }
1434 }
1435 // pattern not found
1436 return NULL;
1437 #else /*REGEX_SEARCH */
1438 char *q;
1439 struct re_pattern_buffer preg;
1440 int i;
1441 int size, range;
1442
1443 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1444 preg.translate = 0;
1445 preg.fastmap = 0;
1446 preg.buffer = 0;
1447 preg.allocated = 0;
1448
1449 // assume a LIMITED forward search
1450 q = next_line(p);
1451 q = end_line(q);
1452 q = end - 1;
1453 if (dir == BACK) {
1454 q = prev_line(p);
1455 q = text;
1456 }
1457 // count the number of chars to search over, forward or backward
1458 size = q - p;
1459 if (size < 0)
1460 size = p - q;
1461 // RANGE could be negative if we are searching backwards
1462 range = q - p;
1463
1464 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1465 if (q != 0) {
1466 // The pattern was not compiled
1467 psbs("bad search pattern: \"%s\": %s", pat, q);
1468 i = 0; // return p if pattern not compiled
1469 goto cs1;
1470 }
1471
1472 q = p;
1473 if (range < 0) {
1474 q = p - size;
1475 if (q < text)
1476 q = text;
1477 }
1478 // search for the compiled pattern, preg, in p[]
1479 // range < 0- search backward
1480 // range > 0- search forward
1481 // 0 < start < size
1482 // re_search() < 0 not found or error
1483 // re_search() > 0 index of found pattern
1484 // struct pattern char int int int struct reg
1485 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1486 i = re_search(&preg, q, size, 0, range, 0);
1487 if (i == -1) {
1488 p = 0;
1489 i = 0; // return NULL if pattern not found
1490 }
1491 cs1:
1492 if (dir == FORWARD) {
1493 p = p + i;
1494 } else {
1495 p = p - i;
1496 }
1497 return p;
1498 #endif /* REGEX_SEARCH */
1499 }
1500 #endif /* FEATURE_VI_SEARCH */
1501
1502 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1503 {
1504 if (c == 22) { // Is this an ctrl-V?
1505 p = stupid_insert(p, '^'); // use ^ to indicate literal next
1506 p--; // backup onto ^
1507 refresh(FALSE); // show the ^
1508 c = get_one_char();
1509 *p = c;
1510 p++;
1511 file_modified++; // has the file been modified
1512 } else if (c == 27) { // Is this an ESC?
1513 cmd_mode = 0;
1514 cmdcnt = 0;
1515 end_cmd_q(); // stop adding to q
1516 last_status_cksum = 0; // force status update
1517 if ((p[-1] != '\n') && (dot>text)) {
1518 p--;
1519 }
1520 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1521 // 123456789
1522 if ((p[-1] != '\n') && (dot>text)) {
1523 p--;
1524 p = text_hole_delete(p, p); // shrink buffer 1 char
1525 }
1526 } else {
1527 // insert a char into text[]
1528 Byte *sp; // "save p"
1529
1530 if (c == 13)
1531 c = '\n'; // translate \r to \n
1532 sp = p; // remember addr of insert
1533 p = stupid_insert(p, c); // insert the char
1534 #if ENABLE_FEATURE_VI_SETOPTS
1535 if (showmatch && strchr(")]}", *sp) != NULL) {
1536 showmatching(sp);
1537 }
1538 if (autoindent && c == '\n') { // auto indent the new line
1539 Byte *q;
1540
1541 q = prev_line(p); // use prev line as templet
1542 for (; isblnk(*q); q++) {
1543 p = stupid_insert(p, *q); // insert the char
1544 }
1545 }
1546 #endif
1547 }
1548 return p;
1549 }
1550
1551 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1552 {
1553 p = text_hole_make(p, 1);
1554 if (p != 0) {
1555 *p = c;
1556 file_modified++; // has the file been modified
1557 p++;
1558 }
1559 return p;
1560 }
1561
1562 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1563 {
1564 Byte *save_dot, *p, *q;
1565 int cnt;
1566
1567 save_dot = dot;
1568 p = q = dot;
1569
1570 if (strchr("cdy><", c)) {
1571 // these cmds operate on whole lines
1572 p = q = begin_line(p);
1573 for (cnt = 1; cnt < cmdcnt; cnt++) {
1574 q = next_line(q);
1575 }
1576 q = end_line(q);
1577 } else if (strchr("^%$0bBeEft", c)) {
1578 // These cmds operate on char positions
1579 do_cmd(c); // execute movement cmd
1580 q = dot;
1581 } else if (strchr("wW", c)) {
1582 do_cmd(c); // execute movement cmd
1583 // if we are at the next word's first char
1584 // step back one char
1585 // but check the possibilities when it is true
1586 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1587 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1588 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1589 dot--; // move back off of next word
1590 if (dot > text && *dot == '\n')
1591 dot--; // stay off NL
1592 q = dot;
1593 } else if (strchr("H-k{", c)) {
1594 // these operate on multi-lines backwards
1595 q = end_line(dot); // find NL
1596 do_cmd(c); // execute movement cmd
1597 dot_begin();
1598 p = dot;
1599 } else if (strchr("L+j}\r\n", c)) {
1600 // these operate on multi-lines forwards
1601 p = begin_line(dot);
1602 do_cmd(c); // execute movement cmd
1603 dot_end(); // find NL
1604 q = dot;
1605 } else {
1606 c = 27; // error- return an ESC char
1607 //break;
1608 }
1609 *start = p;
1610 *stop = q;
1611 if (q < p) {
1612 *start = q;
1613 *stop = p;
1614 }
1615 dot = save_dot;
1616 return c;
1617 }
1618
1619 static int st_test(Byte * p, int type, int dir, Byte * tested)
1620 {
1621 Byte c, c0, ci;
1622 int test, inc;
1623
1624 inc = dir;
1625 c = c0 = p[0];
1626 ci = p[inc];
1627 test = 0;
1628
1629 if (type == S_BEFORE_WS) {
1630 c = ci;
1631 test = ((!isspace(c)) || c == '\n');
1632 }
1633 if (type == S_TO_WS) {
1634 c = c0;
1635 test = ((!isspace(c)) || c == '\n');
1636 }
1637 if (type == S_OVER_WS) {
1638 c = c0;
1639 test = ((isspace(c)));
1640 }
1641 if (type == S_END_PUNCT) {
1642 c = ci;
1643 test = ((ispunct(c)));
1644 }
1645 if (type == S_END_ALNUM) {
1646 c = ci;
1647 test = ((isalnum(c)) || c == '_');
1648 }
1649 *tested = c;
1650 return test;
1651 }
1652
1653 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1654 {
1655 Byte c;
1656
1657 while (st_test(p, type, dir, &c)) {
1658 // make sure we limit search to correct number of lines
1659 if (c == '\n' && --linecnt < 1)
1660 break;
1661 if (dir >= 0 && p >= end - 1)
1662 break;
1663 if (dir < 0 && p <= text)
1664 break;
1665 p += dir; // move to next char
1666 }
1667 return p;
1668 }
1669
1670 // find matching char of pair () [] {}
1671 static Byte *find_pair(Byte * p, Byte c)
1672 {
1673 Byte match, *q;
1674 int dir, level;
1675
1676 match = ')';
1677 level = 1;
1678 dir = 1; // assume forward
1679 switch (c) {
1680 case '(':
1681 match = ')';
1682 break;
1683 case '[':
1684 match = ']';
1685 break;
1686 case '{':
1687 match = '}';
1688 break;
1689 case ')':
1690 match = '(';
1691 dir = -1;
1692 break;
1693 case ']':
1694 match = '[';
1695 dir = -1;
1696 break;
1697 case '}':
1698 match = '{';
1699 dir = -1;
1700 break;
1701 }
1702 for (q = p + dir; text <= q && q < end; q += dir) {
1703 // look for match, count levels of pairs (( ))
1704 if (*q == c)
1705 level++; // increase pair levels
1706 if (*q == match)
1707 level--; // reduce pair level
1708 if (level == 0)
1709 break; // found matching pair
1710 }
1711 if (level != 0)
1712 q = NULL; // indicate no match
1713 return q;
1714 }
1715
1716 #if ENABLE_FEATURE_VI_SETOPTS
1717 // show the matching char of a pair, () [] {}
1718 static void showmatching(Byte * p)
1719 {
1720 Byte *q, *save_dot;
1721
1722 // we found half of a pair
1723 q = find_pair(p, *p); // get loc of matching char
1724 if (q == NULL) {
1725 indicate_error('3'); // no matching char
1726 } else {
1727 // "q" now points to matching pair
1728 save_dot = dot; // remember where we are
1729 dot = q; // go to new loc
1730 refresh(FALSE); // let the user see it
1731 (void) mysleep(40); // give user some time
1732 dot = save_dot; // go back to old loc
1733 refresh(FALSE);
1734 }
1735 }
1736 #endif /* FEATURE_VI_SETOPTS */
1737
1738 // open a hole in text[]
1739 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1740 {
1741 Byte *src, *dest;
1742 int cnt;
1743
1744 if (size <= 0)
1745 goto thm0;
1746 src = p;
1747 dest = p + size;
1748 cnt = end - src; // the rest of buffer
1749 if (memmove(dest, src, cnt) != dest) {
1750 psbs("can't create room for new characters");
1751 }
1752 memset(p, ' ', size); // clear new hole
1753 end = end + size; // adjust the new END
1754 file_modified++; // has the file been modified
1755 thm0:
1756 return p;
1757 }
1758
1759 // close a hole in text[]
1760 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1761 {
1762 Byte *src, *dest;
1763 int cnt, hole_size;
1764
1765 // move forwards, from beginning
1766 // assume p <= q
1767 src = q + 1;
1768 dest = p;
1769 if (q < p) { // they are backward- swap them
1770 src = p + 1;
1771 dest = q;
1772 }
1773 hole_size = q - p + 1;
1774 cnt = end - src;
1775 if (src < text || src > end)
1776 goto thd0;
1777 if (dest < text || dest >= end)
1778 goto thd0;
1779 if (src >= end)
1780 goto thd_atend; // just delete the end of the buffer
1781 if (memmove(dest, src, cnt) != dest) {
1782 psbs("can't delete the character");
1783 }
1784 thd_atend:
1785 end = end - hole_size; // adjust the new END
1786 if (dest >= end)
1787 dest = end - 1; // make sure dest in below end-1
1788 if (end <= text)
1789 dest = end = text; // keep pointers valid
1790 file_modified++; // has the file been modified
1791 thd0:
1792 return dest;
1793 }
1794
1795 // copy text into register, then delete text.
1796 // if dist <= 0, do not include, or go past, a NewLine
1797 //
1798 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1799 {
1800 Byte *p;
1801
1802 // make sure start <= stop
1803 if (start > stop) {
1804 // they are backwards, reverse them
1805 p = start;
1806 start = stop;
1807 stop = p;
1808 }
1809 if (dist <= 0) {
1810 // we cannot cross NL boundaries
1811 p = start;
1812 if (*p == '\n')
1813 return p;
1814 // dont go past a NewLine
1815 for (; p + 1 <= stop; p++) {
1816 if (p[1] == '\n') {
1817 stop = p; // "stop" just before NewLine
1818 break;
1819 }
1820 }
1821 }
1822 p = start;
1823 #if ENABLE_FEATURE_VI_YANKMARK
1824 text_yank(start, stop, YDreg);
1825 #endif
1826 if (yf == YANKDEL) {
1827 p = text_hole_delete(start, stop);
1828 } // delete lines
1829 return p;
1830 }
1831
1832 static void show_help(void)
1833 {
1834 puts("These features are available:"
1835 #if ENABLE_FEATURE_VI_SEARCH
1836 "\n\tPattern searches with / and ?"
1837 #endif
1838 #if ENABLE_FEATURE_VI_DOT_CMD
1839 "\n\tLast command repeat with \'.\'"
1840 #endif
1841 #if ENABLE_FEATURE_VI_YANKMARK
1842 "\n\tLine marking with 'x"
1843 "\n\tNamed buffers with \"x"
1844 #endif
1845 #if ENABLE_FEATURE_VI_READONLY
1846 "\n\tReadonly if vi is called as \"view\""
1847 "\n\tReadonly with -R command line arg"
1848 #endif
1849 #if ENABLE_FEATURE_VI_SET
1850 "\n\tSome colon mode commands with \':\'"
1851 #endif
1852 #if ENABLE_FEATURE_VI_SETOPTS
1853 "\n\tSettable options with \":set\""
1854 #endif
1855 #if ENABLE_FEATURE_VI_USE_SIGNALS
1856 "\n\tSignal catching- ^C"
1857 "\n\tJob suspend and resume with ^Z"
1858 #endif
1859 #if ENABLE_FEATURE_VI_WIN_RESIZE
1860 "\n\tAdapt to window re-sizes"
1861 #endif
1862 );
1863 }
1864
1865 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1866 {
1867 Byte c, b[2];
1868
1869 b[1] = '\0';
1870 strcpy((char *) buf, ""); // init buf
1871 if (strlen((char *) s) <= 0)
1872 s = (Byte *) "(NULL)";
1873 for (; *s > '\0'; s++) {
1874 int c_is_no_print;
1875
1876 c = *s;
1877 c_is_no_print = c > 127 && !Isprint(c);
1878 if (c_is_no_print) {
1879 strcat((char *) buf, SOn);
1880 c = '.';
1881 }
1882 if (c < ' ' || c == 127) {
1883 strcat((char *) buf, "^");
1884 if(c == 127)
1885 c = '?';
1886 else
1887 c += '@';
1888 }
1889 b[0] = c;
1890 strcat((char *) buf, (char *) b);
1891 if (c_is_no_print)
1892 strcat((char *) buf, SOs);
1893 if (*s == '\n') {
1894 strcat((char *) buf, "$");
1895 }
1896 }
1897 }
1898
1899 #if ENABLE_FEATURE_VI_DOT_CMD
1900 static void start_new_cmd_q(Byte c)
1901 {
1902 // release old cmd
1903 free(last_modifying_cmd);
1904 // get buffer for new cmd
1905 last_modifying_cmd = xmalloc(BUFSIZ);
1906 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
1907 // if there is a current cmd count put it in the buffer first
1908 if (cmdcnt > 0)
1909 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1910 else // just save char c onto queue
1911 last_modifying_cmd[0] = c;
1912 adding2q = 1;
1913 }
1914
1915 static void end_cmd_q(void)
1916 {
1917 #if ENABLE_FEATURE_VI_YANKMARK
1918 YDreg = 26; // go back to default Yank/Delete reg
1919 #endif
1920 adding2q = 0;
1921 return;
1922 }
1923 #endif /* FEATURE_VI_DOT_CMD */
1924
1925 #if ENABLE_FEATURE_VI_YANKMARK \
1926 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
1927 || ENABLE_FEATURE_VI_CRASHME
1928 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1929 {
1930 int cnt, i;
1931
1932 i = strlen((char *) s);
1933 p = text_hole_make(p, i);
1934 strncpy((char *) p, (char *) s, i);
1935 for (cnt = 0; *s != '\0'; s++) {
1936 if (*s == '\n')
1937 cnt++;
1938 }
1939 #if ENABLE_FEATURE_VI_YANKMARK
1940 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1941 #endif
1942 return p;
1943 }
1944 #endif
1945
1946 #if ENABLE_FEATURE_VI_YANKMARK
1947 static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
1948 {
1949 Byte *t;
1950 int cnt;
1951
1952 if (q < p) { // they are backwards- reverse them
1953 t = q;
1954 q = p;
1955 p = t;
1956 }
1957 cnt = q - p + 1;
1958 t = reg[dest];
1959 free(t); // if already a yank register, free it
1960 t = xmalloc(cnt + 1); // get a new register
1961 memset(t, '\0', cnt + 1); // clear new text[]
1962 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
1963 reg[dest] = t;
1964 return p;
1965 }
1966
1967 static Byte what_reg(void)
1968 {
1969 Byte c;
1970
1971 c = 'D'; // default to D-reg
1972 if (0 <= YDreg && YDreg <= 25)
1973 c = 'a' + (Byte) YDreg;
1974 if (YDreg == 26)
1975 c = 'D';
1976 if (YDreg == 27)
1977 c = 'U';
1978 return c;
1979 }
1980
1981 static void check_context(Byte cmd)
1982 {
1983 // A context is defined to be "modifying text"
1984 // Any modifying command establishes a new context.
1985
1986 if (dot < context_start || dot > context_end) {
1987 if (strchr((char *) modifying_cmds, cmd) != NULL) {
1988 // we are trying to modify text[]- make this the current context
1989 mark[27] = mark[26]; // move cur to prev
1990 mark[26] = dot; // move local to cur
1991 context_start = prev_line(prev_line(dot));
1992 context_end = next_line(next_line(dot));
1993 //loiter= start_loiter= now;
1994 }
1995 }
1996 return;
1997 }
1998
1999 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2000 {
2001 Byte *tmp;
2002
2003 // the current context is in mark[26]
2004 // the previous context is in mark[27]
2005 // only swap context if other context is valid
2006 if (text <= mark[27] && mark[27] <= end - 1) {
2007 tmp = mark[27];
2008 mark[27] = mark[26];
2009 mark[26] = tmp;
2010 p = mark[26]; // where we are going- previous context
2011 context_start = prev_line(prev_line(prev_line(p)));
2012 context_end = next_line(next_line(next_line(p)));
2013 }
2014 return p;
2015 }
2016 #endif /* FEATURE_VI_YANKMARK */
2017
2018 static int isblnk(Byte c) // is the char a blank or tab
2019 {
2020 return (c == ' ' || c == '\t');
2021 }
2022
2023 //----- Set terminal attributes --------------------------------
2024 static void rawmode(void)
2025 {
2026 tcgetattr(0, &term_orig);
2027 term_vi = term_orig;
2028 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2029 term_vi.c_iflag &= (~IXON & ~ICRNL);
2030 term_vi.c_oflag &= (~ONLCR);
2031 term_vi.c_cc[VMIN] = 1;
2032 term_vi.c_cc[VTIME] = 0;
2033 erase_char = term_vi.c_cc[VERASE];
2034 tcsetattr(0, TCSANOW, &term_vi);
2035 }
2036
2037 static void cookmode(void)
2038 {
2039 fflush(stdout);
2040 tcsetattr(0, TCSANOW, &term_orig);
2041 }
2042
2043 //----- Come here when we get a window resize signal ---------
2044 #if ENABLE_FEATURE_VI_USE_SIGNALS
2045 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2046 {
2047 signal(SIGWINCH, winch_sig);
2048 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2049 get_terminal_width_height(0, &columns, &rows);
2050 new_screen(rows, columns); // get memory for virtual screen
2051 redraw(TRUE); // re-draw the screen
2052 }
2053
2054 //----- Come here when we get a continue signal -------------------
2055 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2056 {
2057 rawmode(); // terminal to "raw"
2058 last_status_cksum = 0; // force status update
2059 redraw(TRUE); // re-draw the screen
2060
2061 signal(SIGTSTP, suspend_sig);
2062 signal(SIGCONT, SIG_DFL);
2063 kill(my_pid, SIGCONT);
2064 }
2065
2066 //----- Come here when we get a Suspend signal -------------------
2067 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2068 {
2069 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2070 clear_to_eol(); // Erase to end of line
2071 cookmode(); // terminal to "cooked"
2072
2073 signal(SIGCONT, cont_sig);
2074 signal(SIGTSTP, SIG_DFL);
2075 kill(my_pid, SIGTSTP);
2076 }
2077
2078 //----- Come here when we get a signal ---------------------------
2079 static void catch_sig(int sig)
2080 {
2081 signal(SIGINT, catch_sig);
2082 if(sig)
2083 longjmp(restart, sig);
2084 }
2085 #endif /* FEATURE_VI_USE_SIGNALS */
2086
2087 static int mysleep(int hund) // sleep for 'h' 1/100 seconds
2088 {
2089 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
2090 fflush(stdout);
2091 FD_ZERO(&rfds);
2092 FD_SET(0, &rfds);
2093 tv.tv_sec = 0;
2094 tv.tv_usec = hund * 10000;
2095 select(1, &rfds, NULL, NULL, &tv);
2096 return FD_ISSET(0, &rfds);
2097 }
2098
2099 #define readbuffer bb_common_bufsiz1
2100
2101 static int readed_for_parse;
2102
2103 //----- IO Routines --------------------------------------------
2104 static Byte readit(void) // read (maybe cursor) key from stdin
2105 {
2106 Byte c;
2107 int n;
2108 struct esc_cmds {
2109 const char *seq;
2110 Byte val;
2111 };
2112
2113 static const struct esc_cmds esccmds[] = {
2114 {"OA", (Byte) VI_K_UP}, // cursor key Up
2115 {"OB", (Byte) VI_K_DOWN}, // cursor key Down
2116 {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
2117 {"OD", (Byte) VI_K_LEFT}, // cursor key Left
2118 {"OH", (Byte) VI_K_HOME}, // Cursor Key Home
2119 {"OF", (Byte) VI_K_END}, // Cursor Key End
2120 {"[A", (Byte) VI_K_UP}, // cursor key Up
2121 {"[B", (Byte) VI_K_DOWN}, // cursor key Down
2122 {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right
2123 {"[D", (Byte) VI_K_LEFT}, // cursor key Left
2124 {"[H", (Byte) VI_K_HOME}, // Cursor Key Home
2125 {"[F", (Byte) VI_K_END}, // Cursor Key End
2126 {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home
2127 {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
2128 {"[4~", (Byte) VI_K_END}, // Cursor Key End
2129 {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
2130 {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
2131 {"OP", (Byte) VI_K_FUN1}, // Function Key F1
2132 {"OQ", (Byte) VI_K_FUN2}, // Function Key F2
2133 {"OR", (Byte) VI_K_FUN3}, // Function Key F3
2134 {"OS", (Byte) VI_K_FUN4}, // Function Key F4
2135 {"[15~", (Byte) VI_K_FUN5}, // Function Key F5
2136 {"[17~", (Byte) VI_K_FUN6}, // Function Key F6
2137 {"[18~", (Byte) VI_K_FUN7}, // Function Key F7
2138 {"[19~", (Byte) VI_K_FUN8}, // Function Key F8
2139 {"[20~", (Byte) VI_K_FUN9}, // Function Key F9
2140 {"[21~", (Byte) VI_K_FUN10}, // Function Key F10
2141 {"[23~", (Byte) VI_K_FUN11}, // Function Key F11
2142 {"[24~", (Byte) VI_K_FUN12}, // Function Key F12
2143 {"[11~", (Byte) VI_K_FUN1}, // Function Key F1
2144 {"[12~", (Byte) VI_K_FUN2}, // Function Key F2
2145 {"[13~", (Byte) VI_K_FUN3}, // Function Key F3
2146 {"[14~", (Byte) VI_K_FUN4}, // Function Key F4
2147 };
2148
2149 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2150
2151 (void) alarm(0); // turn alarm OFF while we wait for input
2152 fflush(stdout);
2153 n = readed_for_parse;
2154 // get input from User- are there already input chars in Q?
2155 if (n <= 0) {
2156 ri0:
2157 // the Q is empty, wait for a typed char
2158 n = read(0, readbuffer, BUFSIZ - 1);
2159 if (n < 0) {
2160 if (errno == EINTR)
2161 goto ri0; // interrupted sys call
2162 if (errno == EBADF)
2163 editing = 0;
2164 if (errno == EFAULT)
2165 editing = 0;
2166 if (errno == EINVAL)
2167 editing = 0;
2168 if (errno == EIO)
2169 editing = 0;
2170 errno = 0;
2171 }
2172 if(n <= 0)
2173 return 0; // error
2174 if (readbuffer[0] == 27) {
2175 // This is an ESC char. Is this Esc sequence?
2176 // Could be bare Esc key. See if there are any
2177 // more chars to read after the ESC. This would
2178 // be a Function or Cursor Key sequence.
2179 FD_ZERO(&rfds);
2180 FD_SET(0, &rfds);
2181 tv.tv_sec = 0;
2182 tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000
2183
2184 // keep reading while there are input chars and room in buffer
2185 while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2186 // read the rest of the ESC string
2187 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2188 if (r > 0) {
2189 n += r;
2190 }
2191 }
2192 }
2193 readed_for_parse = n;
2194 }
2195 c = readbuffer[0];
2196 if(c == 27 && n > 1) {
2197 // Maybe cursor or function key?
2198 const struct esc_cmds *eindex;
2199
2200 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2201 int cnt = strlen(eindex->seq);
2202
2203 if(n <= cnt)
2204 continue;
2205 if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2206 continue;
2207 // is a Cursor key- put derived value back into Q
2208 c = eindex->val;
2209 // for squeeze out the ESC sequence
2210 n = cnt + 1;
2211 break;
2212 }
2213 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2214 /* defined ESC sequence not found, set only one ESC */
2215 n = 1;
2216 }
2217 } else {
2218 n = 1;
2219 }
2220 // remove key sequence from Q
2221 readed_for_parse -= n;
2222 memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2223 (void) alarm(3); // we are done waiting for input, turn alarm ON
2224 return c;
2225 }
2226
2227 //----- IO Routines --------------------------------------------
2228 static Byte get_one_char(void)
2229 {
2230 static Byte c;
2231
2232 #if ENABLE_FEATURE_VI_DOT_CMD
2233 // ! adding2q && ioq == 0 read()
2234 // ! adding2q && ioq != 0 *ioq
2235 // adding2q *last_modifying_cmd= read()
2236 if (!adding2q) {
2237 // we are not adding to the q.
2238 // but, we may be reading from a q
2239 if (ioq == 0) {
2240 // there is no current q, read from STDIN
2241 c = readit(); // get the users input
2242 } else {
2243 // there is a queue to get chars from first
2244 c = *ioq++;
2245 if (c == '\0') {
2246 // the end of the q, read from STDIN
2247 free(ioq_start);
2248 ioq_start = ioq = 0;
2249 c = readit(); // get the users input
2250 }
2251 }
2252 } else {
2253 // adding STDIN chars to q
2254 c = readit(); // get the users input
2255 if (last_modifying_cmd != 0) {
2256 int len = strlen((char *) last_modifying_cmd);
2257 if (len + 1 >= BUFSIZ) {
2258 psbs("last_modifying_cmd overrun");
2259 } else {
2260 // add new char to q
2261 last_modifying_cmd[len] = c;
2262 }
2263 }
2264 }
2265 #else
2266 c = readit(); // get the users input
2267 #endif /* FEATURE_VI_DOT_CMD */
2268 return c; // return the char, where ever it came from
2269 }
2270
2271 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2272 {
2273 Byte buf[BUFSIZ];
2274 Byte c;
2275 int i;
2276 static Byte *obufp = NULL;
2277
2278 strcpy((char *) buf, (char *) prompt);
2279 last_status_cksum = 0; // force status update
2280 place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen
2281 clear_to_eol(); // clear the line
2282 write1((char *) prompt); // write out the :, /, or ? prompt
2283
2284 for (i = strlen((char *) buf); i < BUFSIZ;) {
2285 c = get_one_char(); // read user input
2286 if (c == '\n' || c == '\r' || c == 27)
2287 break; // is this end of input
2288 if (c == erase_char || c == 8 || c == 127) {
2289 // user wants to erase prev char
2290 i--; // backup to prev char
2291 buf[i] = '\0'; // erase the char
2292 buf[i + 1] = '\0'; // null terminate buffer
2293 write1("\b \b"); // erase char on screen
2294 if (i <= 0) { // user backs up before b-o-l, exit
2295 break;
2296 }
2297 } else {
2298 buf[i] = c; // save char in buffer
2299 buf[i + 1] = '\0'; // make sure buffer is null terminated
2300 putchar(c); // echo the char back to user
2301 i++;
2302 }
2303 }
2304 refresh(FALSE);
2305 free(obufp);
2306 obufp = (Byte *) xstrdup((char *) buf);
2307 return obufp;
2308 }
2309
2310 static int file_size(const Byte * fn) // what is the byte size of "fn"
2311 {
2312 struct stat st_buf;
2313 int cnt, sr;
2314
2315 if (fn == 0 || strlen((char *)fn) <= 0)
2316 return -1;
2317 cnt = -1;
2318 sr = stat((char *) fn, &st_buf); // see if file exists
2319 if (sr >= 0) {
2320 cnt = (int) st_buf.st_size;
2321 }
2322 return cnt;
2323 }
2324
2325 static int file_insert(Byte * fn, Byte * p, int size)
2326 {
2327 int fd, cnt;
2328
2329 cnt = -1;
2330 #if ENABLE_FEATURE_VI_READONLY
2331 readonly = FALSE;
2332 #endif
2333 if (fn == 0 || strlen((char*) fn) <= 0) {
2334 psbs("No filename given");
2335 goto fi0;
2336 }
2337 if (size == 0) {
2338 // OK- this is just a no-op
2339 cnt = 0;
2340 goto fi0;
2341 }
2342 if (size < 0) {
2343 psbs("Trying to insert a negative number (%d) of characters", size);
2344 goto fi0;
2345 }
2346 if (p < text || p > end) {
2347 psbs("Trying to insert file outside of memory");
2348 goto fi0;
2349 }
2350
2351 // see if we can open the file
2352 #if ENABLE_FEATURE_VI_READONLY
2353 if (vi_readonly) goto fi1; // do not try write-mode
2354 #endif
2355 fd = open((char *) fn, O_RDWR); // assume read & write
2356 if (fd < 0) {
2357 // could not open for writing- maybe file is read only
2358 #if ENABLE_FEATURE_VI_READONLY
2359 fi1:
2360 #endif
2361 fd = open((char *) fn, O_RDONLY); // try read-only
2362 if (fd < 0) {
2363 psbs("\"%s\" %s", fn, "cannot open file");
2364 goto fi0;
2365 }
2366 #if ENABLE_FEATURE_VI_READONLY
2367 // got the file- read-only
2368 readonly = TRUE;
2369 #endif
2370 }
2371 p = text_hole_make(p, size);
2372 cnt = read(fd, p, size);
2373 close(fd);
2374 if (cnt < 0) {
2375 cnt = -1;
2376 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2377 psbs("cannot read file \"%s\"", fn);
2378 } else if (cnt < size) {
2379 // There was a partial read, shrink unused space text[]
2380 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2381 psbs("cannot read all of file \"%s\"", fn);
2382 }
2383 if (cnt >= size)
2384 file_modified++;
2385 fi0:
2386 return cnt;
2387 }
2388
2389 static int file_write(Byte * fn, Byte * first, Byte * last)
2390 {
2391 int fd, cnt, charcnt;
2392
2393 if (fn == 0) {
2394 psbs("No current filename");
2395 return -2;
2396 }
2397 charcnt = 0;
2398 // FIXIT- use the correct umask()
2399 fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2400 if (fd < 0)
2401 return -1;
2402 cnt = last - first + 1;
2403 charcnt = write(fd, first, cnt);
2404 if (charcnt == cnt) {
2405 // good write
2406 //file_modified= FALSE; // the file has not been modified
2407 } else {
2408 charcnt = 0;
2409 }
2410 close(fd);
2411 return charcnt;
2412 }
2413
2414 //----- Terminal Drawing ---------------------------------------
2415 // The terminal is made up of 'rows' line of 'columns' columns.
2416 // classically this would be 24 x 80.
2417 // screen coordinates
2418 // 0,0 ... 0,79
2419 // 1,0 ... 1,79
2420 // . ... .
2421 // . ... .
2422 // 22,0 ... 22,79
2423 // 23,0 ... 23,79 status line
2424 //
2425
2426 //----- Move the cursor to row x col (count from 0, not 1) -------
2427 static void place_cursor(int row, int col, int opti)
2428 {
2429 char cm1[BUFSIZ];
2430 char *cm;
2431 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2432 char cm2[BUFSIZ];
2433 Byte *screenp;
2434 // char cm3[BUFSIZ];
2435 int Rrow= last_row;
2436 #endif
2437
2438 memset(cm1, '\0', BUFSIZ - 1); // clear the buffer
2439
2440 if (row < 0) row = 0;
2441 if (row >= rows) row = rows - 1;
2442 if (col < 0) col = 0;
2443 if (col >= columns) col = columns - 1;
2444
2445 //----- 1. Try the standard terminal ESC sequence
2446 sprintf((char *) cm1, CMrc, row + 1, col + 1);
2447 cm= cm1;
2448 if (! opti) goto pc0;
2449
2450 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2451 //----- find the minimum # of chars to move cursor -------------
2452 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2453 memset(cm2, '\0', BUFSIZ - 1); // clear the buffer
2454
2455 // move to the correct row
2456 while (row < Rrow) {
2457 // the cursor has to move up
2458 strcat(cm2, CMup);
2459 Rrow--;
2460 }
2461 while (row > Rrow) {
2462 // the cursor has to move down
2463 strcat(cm2, CMdown);
2464 Rrow++;
2465 }
2466
2467 // now move to the correct column
2468 strcat(cm2, "\r"); // start at col 0
2469 // just send out orignal source char to get to correct place
2470 screenp = &screen[row * columns]; // start of screen line
2471 strncat(cm2, (char* )screenp, col);
2472
2473 //----- 3. Try some other way of moving cursor
2474 //---------------------------------------------
2475
2476 // pick the shortest cursor motion to send out
2477 cm= cm1;
2478 if (strlen(cm2) < strlen(cm)) {
2479 cm= cm2;
2480 } /* else if (strlen(cm3) < strlen(cm)) {
2481 cm= cm3;
2482 } */
2483 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2484 pc0:
2485 write1(cm); // move the cursor
2486 }
2487
2488 //----- Erase from cursor to end of line -----------------------
2489 static void clear_to_eol(void)
2490 {
2491 write1(Ceol); // Erase from cursor to end of line
2492 }
2493
2494 //----- Erase from cursor to end of screen -----------------------
2495 static void clear_to_eos(void)
2496 {
2497 write1(Ceos); // Erase from cursor to end of screen
2498 }
2499
2500 //----- Start standout mode ------------------------------------
2501 static void standout_start(void) // send "start reverse video" sequence
2502 {
2503 write1(SOs); // Start reverse video mode
2504 }
2505
2506 //----- End standout mode --------------------------------------
2507 static void standout_end(void) // send "end reverse video" sequence
2508 {
2509 write1(SOn); // End reverse video mode
2510 }
2511
2512 //----- Flash the screen --------------------------------------
2513 static void flash(int h)
2514 {
2515 standout_start(); // send "start reverse video" sequence
2516 redraw(TRUE);
2517 (void) mysleep(h);
2518 standout_end(); // send "end reverse video" sequence
2519 redraw(TRUE);
2520 }
2521
2522 static void Indicate_Error(void)
2523 {
2524 #if ENABLE_FEATURE_VI_CRASHME
2525 if (crashme > 0)
2526 return; // generate a random command
2527 #endif
2528 if (!err_method) {
2529 write1(bell); // send out a bell character
2530 } else {
2531 flash(10);
2532 }
2533 }
2534
2535 //----- Screen[] Routines --------------------------------------
2536 //----- Erase the Screen[] memory ------------------------------
2537 static void screen_erase(void)
2538 {
2539 memset(screen, ' ', screensize); // clear new screen
2540 }
2541
2542 static int bufsum(unsigned char *buf, int count)
2543 {
2544 int sum = 0;
2545 unsigned char *e = buf + count;
2546 while (buf < e)
2547 sum += *buf++;
2548 return sum;
2549 }
2550
2551 //----- Draw the status line at bottom of the screen -------------
2552 static void show_status_line(void)
2553 {
2554 int cnt = 0, cksum = 0;
2555
2556 // either we already have an error or status message, or we
2557 // create one.
2558 if (!have_status_msg) {
2559 cnt = format_edit_status();
2560 cksum = bufsum(status_buffer, cnt);
2561 }
2562 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2563 last_status_cksum= cksum; // remember if we have seen this line
2564 place_cursor(rows - 1, 0, FALSE); // put cursor on status line
2565 write1((char*)status_buffer);
2566 clear_to_eol();
2567 if (have_status_msg) {
2568 if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2569 (columns - 1) ) {
2570 have_status_msg = 0;
2571 Hit_Return();
2572 }
2573 have_status_msg = 0;
2574 }
2575 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2576 }
2577 fflush(stdout);
2578 }
2579
2580 //----- format the status buffer, the bottom line of screen ------
2581 // format status buffer, with STANDOUT mode
2582 static void psbs(const char *format, ...)
2583 {
2584 va_list args;
2585
2586 va_start(args, format);
2587 strcpy((char *) status_buffer, SOs); // Terminal standout mode on
2588 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2589 strcat((char *) status_buffer, SOn); // Terminal standout mode off
2590 va_end(args);
2591
2592 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2593
2594 return;
2595 }
2596
2597 // format status buffer
2598 static void psb(const char *format, ...)
2599 {
2600 va_list args;
2601
2602 va_start(args, format);
2603 vsprintf((char *) status_buffer, format, args);
2604 va_end(args);
2605
2606 have_status_msg = 1;
2607
2608 return;
2609 }
2610
2611 static void ni(Byte * s) // display messages
2612 {
2613 Byte buf[BUFSIZ];
2614
2615 print_literal(buf, s);
2616 psbs("\'%s\' is not implemented", buf);
2617 }
2618
2619 static int format_edit_status(void) // show file status on status line
2620 {
2621 int cur, percent, ret, trunc_at;
2622 static int tot;
2623
2624 // file_modified is now a counter rather than a flag. this
2625 // helps reduce the amount of line counting we need to do.
2626 // (this will cause a mis-reporting of modified status
2627 // once every MAXINT editing operations.)
2628
2629 // it would be nice to do a similar optimization here -- if
2630 // we haven't done a motion that could have changed which line
2631 // we're on, then we shouldn't have to do this count_lines()
2632 cur = count_lines(text, dot);
2633
2634 // reduce counting -- the total lines can't have
2635 // changed if we haven't done any edits.
2636 if (file_modified != last_file_modified) {
2637 tot = cur + count_lines(dot, end - 1) - 1;
2638 last_file_modified = file_modified;
2639 }
2640
2641 // current line percent
2642 // ------------- ~~ ----------
2643 // total lines 100
2644 if (tot > 0) {
2645 percent = (100 * cur) / tot;
2646 } else {
2647 cur = tot = 0;
2648 percent = 100;
2649 }
2650
2651 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2652 columns : STATUS_BUFFER_LEN-1;
2653
2654 ret = snprintf((char *) status_buffer, trunc_at+1,
2655 #if ENABLE_FEATURE_VI_READONLY
2656 "%c %s%s%s %d/%d %d%%",
2657 #else
2658 "%c %s%s %d/%d %d%%",
2659 #endif
2660 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2661 (cfn != 0 ? (char *) cfn : "No file"),
2662 #if ENABLE_FEATURE_VI_READONLY
2663 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2664 #endif
2665 (file_modified ? " [modified]" : ""),
2666 cur, tot, percent);
2667
2668 if (ret >= 0 && ret < trunc_at)
2669 return ret; /* it all fit */
2670
2671 return trunc_at; /* had to truncate */
2672 }
2673
2674 //----- Force refresh of all Lines -----------------------------
2675 static void redraw(int full_screen)
2676 {
2677 place_cursor(0, 0, FALSE); // put cursor in correct place
2678 clear_to_eos(); // tel terminal to erase display
2679 screen_erase(); // erase the internal screen buffer
2680 last_status_cksum = 0; // force status update
2681 refresh(full_screen); // this will redraw the entire display
2682 show_status_line();
2683 }
2684
2685 //----- Format a text[] line into a buffer ---------------------
2686 static void format_line(Byte *dest, Byte *src, int li)
2687 {
2688 int co;
2689 Byte c;
2690
2691 for (co= 0; co < MAX_SCR_COLS; co++) {
2692 c= ' '; // assume blank
2693 if (li > 0 && co == 0) {
2694 c = '~'; // not first line, assume Tilde
2695 }
2696 // are there chars in text[] and have we gone past the end
2697 if (text < end && src < end) {
2698 c = *src++;
2699 }
2700 if (c == '\n')
2701 break;
2702 if (c > 127 && !Isprint(c)) {
2703 c = '.';
2704 }
2705 if (c < ' ' || c == 127) {
2706 if (c == '\t') {
2707 c = ' ';
2708 // co % 8 != 7
2709 for (; (co % tabstop) != (tabstop - 1); co++) {
2710 dest[co] = c;
2711 }
2712 } else {
2713 dest[co++] = '^';
2714 if(c == 127)
2715 c = '?';
2716 else
2717 c += '@'; // make it visible
2718 }
2719 }
2720 // the co++ is done here so that the column will
2721 // not be overwritten when we blank-out the rest of line
2722 dest[co] = c;
2723 if (src >= end)
2724 break;
2725 }
2726 }
2727
2728 //----- Refresh the changed screen lines -----------------------
2729 // Copy the source line from text[] into the buffer and note
2730 // if the current screenline is different from the new buffer.
2731 // If they differ then that line needs redrawing on the terminal.
2732 //
2733 static void refresh(int full_screen)
2734 {
2735 static int old_offset;
2736 int li, changed;
2737 Byte buf[MAX_SCR_COLS];
2738 Byte *tp, *sp; // pointer into text[] and screen[]
2739 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2740 int last_li= -2; // last line that changed- for optimizing cursor movement
2741 #endif
2742
2743 if (ENABLE_FEATURE_VI_WIN_RESIZE)
2744 get_terminal_width_height(0, &columns, &rows);
2745 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2746 tp = screenbegin; // index into text[] of top line
2747
2748 // compare text[] to screen[] and mark screen[] lines that need updating
2749 for (li = 0; li < rows - 1; li++) {
2750 int cs, ce; // column start & end
2751 memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer
2752 buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer
2753 // format current text line into buf
2754 format_line(buf, tp, li);
2755
2756 // skip to the end of the current text[] line
2757 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2758
2759 // see if there are any changes between vitual screen and buf
2760 changed = FALSE; // assume no change
2761 cs= 0;
2762 ce= columns-1;
2763 sp = &screen[li * columns]; // start of screen line
2764 if (full_screen) {
2765 // force re-draw of every single column from 0 - columns-1
2766 goto re0;
2767 }
2768 // compare newly formatted buffer with virtual screen
2769 // look forward for first difference between buf and screen
2770 for ( ; cs <= ce; cs++) {
2771 if (buf[cs + offset] != sp[cs]) {
2772 changed = TRUE; // mark for redraw
2773 break;
2774 }
2775 }
2776
2777 // look backward for last difference between buf and screen
2778 for ( ; ce >= cs; ce--) {
2779 if (buf[ce + offset] != sp[ce]) {
2780 changed = TRUE; // mark for redraw
2781 break;
2782 }
2783 }
2784 // now, cs is index of first diff, and ce is index of last diff
2785
2786 // if horz offset has changed, force a redraw
2787 if (offset != old_offset) {
2788 re0:
2789 changed = TRUE;
2790 }
2791
2792 // make a sanity check of columns indexes
2793 if (cs < 0) cs= 0;
2794 if (ce > columns-1) ce= columns-1;
2795 if (cs > ce) { cs= 0; ce= columns-1; }
2796 // is there a change between vitual screen and buf
2797 if (changed) {
2798 // copy changed part of buffer to virtual screen
2799 memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2800
2801 // move cursor to column of first change
2802 if (offset != old_offset) {
2803 // opti_cur_move is still too stupid
2804 // to handle offsets correctly
2805 place_cursor(li, cs, FALSE);
2806 } else {
2807 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2808 // if this just the next line
2809 // try to optimize cursor movement
2810 // otherwise, use standard ESC sequence
2811 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2812 last_li= li;
2813 #else
2814 place_cursor(li, cs, FALSE); // use standard ESC sequence
2815 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2816 }
2817
2818 // write line out to terminal
2819 {
2820 int nic = ce-cs+1;
2821 char *out = (char*)sp+cs;
2822
2823 while (nic-- > 0) {
2824 putchar(*out);
2825 out++;
2826 }
2827 }
2828 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2829 last_row = li;
2830 #endif
2831 }
2832 }
2833
2834 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2835 place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2836 last_row = crow;
2837 #else
2838 place_cursor(crow, ccol, FALSE);
2839 #endif
2840
2841 if (offset != old_offset)
2842 old_offset = offset;
2843 }
2844
2845 //---------------------------------------------------------------------
2846 //----- the Ascii Chart -----------------------------------------------
2847 //
2848 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
2849 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
2850 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
2851 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
2852 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
2853 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
2854 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
2855 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
2856 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
2857 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
2858 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
2859 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
2860 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
2861 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
2862 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
2863 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
2864 //---------------------------------------------------------------------
2865
2866 //----- Execute a Vi Command -----------------------------------
2867 static void do_cmd(Byte c)
2868 {
2869 Byte c1, *p, *q, *msg, buf[9], *save_dot;
2870 int cnt, i, j, dir, yf;
2871
2872 c1 = c; // quiet the compiler
2873 cnt = yf = dir = 0; // quiet the compiler
2874 p = q = save_dot = msg = buf; // quiet the compiler
2875 memset(buf, '\0', 9); // clear buf
2876
2877 show_status_line();
2878
2879 /* if this is a cursor key, skip these checks */
2880 switch (c) {
2881 case VI_K_UP:
2882 case VI_K_DOWN:
2883 case VI_K_LEFT:
2884 case VI_K_RIGHT:
2885 case VI_K_HOME:
2886 case VI_K_END:
2887 case VI_K_PAGEUP:
2888 case VI_K_PAGEDOWN:
2889 goto key_cmd_mode;
2890 }
2891
2892 if (cmd_mode == 2) {
2893 // flip-flop Insert/Replace mode
2894 if (c == VI_K_INSERT) goto dc_i;
2895 // we are 'R'eplacing the current *dot with new char
2896 if (*dot == '\n') {
2897 // don't Replace past E-o-l
2898 cmd_mode = 1; // convert to insert
2899 } else {
2900 if (1 <= c || Isprint(c)) {
2901 if (c != 27)
2902 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
2903 dot = char_insert(dot, c); // insert new char
2904 }
2905 goto dc1;
2906 }
2907 }
2908 if (cmd_mode == 1) {
2909 // hitting "Insert" twice means "R" replace mode
2910 if (c == VI_K_INSERT) goto dc5;
2911 // insert the char c at "dot"
2912 if (1 <= c || Isprint(c)) {
2913 dot = char_insert(dot, c);
2914 }
2915 goto dc1;
2916 }
2917
2918 key_cmd_mode:
2919 switch (c) {
2920 //case 0x01: // soh
2921 //case 0x09: // ht
2922 //case 0x0b: // vt
2923 //case 0x0e: // so
2924 //case 0x0f: // si
2925 //case 0x10: // dle
2926 //case 0x11: // dc1
2927 //case 0x13: // dc3
2928 #if ENABLE_FEATURE_VI_CRASHME
2929 case 0x14: // dc4 ctrl-T
2930 crashme = (crashme == 0) ? 1 : 0;
2931 break;
2932 #endif
2933 //case 0x16: // syn
2934 //case 0x17: // etb
2935 //case 0x18: // can
2936 //case 0x1c: // fs
2937 //case 0x1d: // gs
2938 //case 0x1e: // rs
2939 //case 0x1f: // us
2940 //case '!': // !-
2941 //case '#': // #-
2942 //case '&': // &-
2943 //case '(': // (-
2944 //case ')': // )-
2945 //case '*': // *-
2946 //case ',': // ,-
2947 //case '=': // =-
2948 //case '@': // @-
2949 //case 'F': // F-
2950 //case 'K': // K-
2951 //case 'Q': // Q-
2952 //case 'S': // S-
2953 //case 'T': // T-
2954 //case 'V': // V-
2955 //case '[': // [-
2956 //case '\\': // \-
2957 //case ']': // ]-
2958 //case '_': // _-
2959 //case '`': // `-
2960 //case 'g': // g-
2961 //case 'u': // u- FIXME- there is no undo
2962 //case 'v': // v-
2963 default: // unrecognised command
2964 buf[0] = c;
2965 buf[1] = '\0';
2966 if (c < ' ') {
2967 buf[0] = '^';
2968 buf[1] = c + '@';
2969 buf[2] = '\0';
2970 }
2971 ni((Byte *) buf);
2972 end_cmd_q(); // stop adding to q
2973 case 0x00: // nul- ignore
2974 break;
2975 case 2: // ctrl-B scroll up full screen
2976 case VI_K_PAGEUP: // Cursor Key Page Up
2977 dot_scroll(rows - 2, -1);
2978 break;
2979 #if ENABLE_FEATURE_VI_USE_SIGNALS
2980 case 0x03: // ctrl-C interrupt
2981 longjmp(restart, 1);
2982 break;
2983 case 26: // ctrl-Z suspend
2984 suspend_sig(SIGTSTP);
2985 break;
2986 #endif
2987 case 4: // ctrl-D scroll down half screen
2988 dot_scroll((rows - 2) / 2, 1);
2989 break;
2990 case 5: // ctrl-E scroll down one line
2991 dot_scroll(1, 1);
2992 break;
2993 case 6: // ctrl-F scroll down full screen
2994 case VI_K_PAGEDOWN: // Cursor Key Page Down
2995 dot_scroll(rows - 2, 1);
2996 break;
2997 case 7: // ctrl-G show current status
2998 last_status_cksum = 0; // force status update
2999 break;
3000 case 'h': // h- move left
3001 case VI_K_LEFT: // cursor key Left
3002 case 8: // ctrl-H- move left (This may be ERASE char)
3003 case 127: // DEL- move left (This may be ERASE char)
3004 if (cmdcnt-- > 1) {
3005 do_cmd(c);
3006 } // repeat cnt
3007 dot_left();
3008 break;
3009 case 10: // Newline ^J
3010 case 'j': // j- goto next line, same col
3011 case VI_K_DOWN: // cursor key Down
3012 if (cmdcnt-- > 1) {
3013 do_cmd(c);
3014 } // repeat cnt
3015 dot_next(); // go to next B-o-l
3016 dot = move_to_col(dot, ccol + offset); // try stay in same col
3017 break;
3018 case 12: // ctrl-L force redraw whole screen
3019 case 18: // ctrl-R force redraw
3020 place_cursor(0, 0, FALSE); // put cursor in correct place
3021 clear_to_eos(); // tel terminal to erase display
3022 (void) mysleep(10);
3023 screen_erase(); // erase the internal screen buffer
3024 last_status_cksum = 0; // force status update
3025 refresh(TRUE); // this will redraw the entire display
3026 break;
3027 case 13: // Carriage Return ^M
3028 case '+': // +- goto next line
3029 if (cmdcnt-- > 1) {
3030 do_cmd(c);
3031 } // repeat cnt
3032 dot_next();
3033 dot_skip_over_ws();
3034 break;
3035 case 21: // ctrl-U scroll up half screen
3036 dot_scroll((rows - 2) / 2, -1);
3037 break;
3038 case 25: // ctrl-Y scroll up one line
3039 dot_scroll(1, -1);
3040 break;
3041 case 27: // esc
3042 if (cmd_mode == 0)
3043 indicate_error(c);
3044 cmd_mode = 0; // stop insrting
3045 end_cmd_q();
3046 last_status_cksum = 0; // force status update
3047 break;
3048 case ' ': // move right
3049 case 'l': // move right
3050 case VI_K_RIGHT: // Cursor Key Right
3051 if (cmdcnt-- > 1) {
3052 do_cmd(c);
3053 } // repeat cnt
3054 dot_right();
3055 break;
3056 #if ENABLE_FEATURE_VI_YANKMARK
3057 case '"': // "- name a register to use for Delete/Yank
3058 c1 = get_one_char();
3059 c1 = tolower(c1);
3060 if (islower(c1)) {
3061 YDreg = c1 - 'a';
3062 } else {
3063 indicate_error(c);
3064 }
3065 break;
3066 case '\'': // '- goto a specific mark
3067 c1 = get_one_char();
3068 c1 = tolower(c1);
3069 if (islower(c1)) {
3070 c1 = c1 - 'a';
3071 // get the b-o-l
3072 q = mark[(int) c1];
3073 if (text <= q && q < end) {
3074 dot = q;
3075 dot_begin(); // go to B-o-l
3076 dot_skip_over_ws();
3077 }
3078 } else if (c1 == '\'') { // goto previous context
3079 dot = swap_context(dot); // swap current and previous context
3080 dot_begin(); // go to B-o-l
3081 dot_skip_over_ws();
3082 } else {
3083 indicate_error(c);
3084 }
3085 break;
3086 case 'm': // m- Mark a line
3087 // this is really stupid. If there are any inserts or deletes
3088 // between text[0] and dot then this mark will not point to the
3089 // correct location! It could be off by many lines!
3090 // Well..., at least its quick and dirty.
3091 c1 = get_one_char();
3092 c1 = tolower(c1);
3093 if (islower(c1)) {
3094 c1 = c1 - 'a';
3095 // remember the line
3096 mark[(int) c1] = dot;
3097 } else {
3098 indicate_error(c);
3099 }
3100 break;
3101 case 'P': // P- Put register before
3102 case 'p': // p- put register after
3103 p = reg[YDreg];
3104 if (p == 0) {
3105 psbs("Nothing in register %c", what_reg());
3106 break;
3107 }
3108 // are we putting whole lines or strings
3109 if (strchr((char *) p, '\n') != NULL) {
3110 if (c == 'P') {
3111 dot_begin(); // putting lines- Put above
3112 }
3113 if (c == 'p') {
3114 // are we putting after very last line?
3115 if (end_line(dot) == (end - 1)) {
3116 dot = end; // force dot to end of text[]
3117 } else {
3118 dot_next(); // next line, then put before
3119 }
3120 }
3121 } else {
3122 if (c == 'p')
3123 dot_right(); // move to right, can move to NL
3124 }
3125 dot = string_insert(dot, p); // insert the string
3126 end_cmd_q(); // stop adding to q
3127 break;
3128 case 'U': // U- Undo; replace current line with original version
3129 if (reg[Ureg] != 0) {
3130 p = begin_line(dot);
3131 q = end_line(dot);
3132 p = text_hole_delete(p, q); // delete cur line
3133 p = string_insert(p, reg[Ureg]); // insert orig line
3134 dot = p;
3135 dot_skip_over_ws();
3136 }
3137 break;
3138 #endif /* FEATURE_VI_YANKMARK */
3139 case '$': // $- goto end of line
3140 case VI_K_END: // Cursor Key End
3141 if (cmdcnt-- > 1) {
3142 do_cmd(c);
3143 } // repeat cnt
3144 dot = end_line(dot);
3145 break;
3146 case '%': // %- find matching char of pair () [] {}
3147 for (q = dot; q < end && *q != '\n'; q++) {
3148 if (strchr("()[]{}", *q) != NULL) {
3149 // we found half of a pair
3150 p = find_pair(q, *q);
3151 if (p == NULL) {
3152 indicate_error(c);
3153 } else {
3154 dot = p;
3155 }
3156 break;
3157 }
3158 }
3159 if (*q == '\n')
3160 indicate_error(c);
3161 break;
3162 case 'f': // f- forward to a user specified char
3163 last_forward_char = get_one_char(); // get the search char
3164 //
3165 // dont separate these two commands. 'f' depends on ';'
3166 //
3167 //**** fall thru to ... ';'
3168 case ';': // ;- look at rest of line for last forward char
3169 if (cmdcnt-- > 1) {
3170 do_cmd(';');
3171 } // repeat cnt
3172 if (last_forward_char == 0) break;
3173 q = dot + 1;
3174 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3175 q++;
3176 }
3177 if (*q == last_forward_char)
3178 dot = q;
3179 break;
3180 case '-': // -- goto prev line
3181 if (cmdcnt-- > 1) {
3182 do_cmd(c);
3183 } // repeat cnt
3184 dot_prev();
3185 dot_skip_over_ws();
3186 break;
3187 #if ENABLE_FEATURE_VI_DOT_CMD
3188 case '.': // .- repeat the last modifying command
3189 // Stuff the last_modifying_cmd back into stdin
3190 // and let it be re-executed.
3191 if (last_modifying_cmd != 0) {
3192 ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd);
3193 }
3194 break;
3195 #endif
3196 #if ENABLE_FEATURE_VI_SEARCH
3197 case '?': // /- search for a pattern
3198 case '/': // /- search for a pattern
3199 buf[0] = c;
3200 buf[1] = '\0';
3201 q = get_input_line(buf); // get input line- use "status line"
3202 if (strlen((char *) q) == 1)
3203 goto dc3; // if no pat re-use old pat
3204 if (strlen((char *) q) > 1) { // new pat- save it and find
3205 // there is a new pat
3206 free(last_search_pattern);
3207 last_search_pattern = (Byte *) xstrdup((char *) q);
3208 goto dc3; // now find the pattern
3209 }
3210 // user changed mind and erased the "/"- do nothing
3211 break;
3212 case 'N': // N- backward search for last pattern
3213 if (cmdcnt-- > 1) {
3214 do_cmd(c);
3215 } // repeat cnt
3216 dir = BACK; // assume BACKWARD search
3217 p = dot - 1;
3218 if (last_search_pattern[0] == '?') {
3219 dir = FORWARD;
3220 p = dot + 1;
3221 }
3222 goto dc4; // now search for pattern
3223 break;
3224 case 'n': // n- repeat search for last pattern
3225 // search rest of text[] starting at next char
3226 // if search fails return orignal "p" not the "p+1" address
3227 if (cmdcnt-- > 1) {
3228 do_cmd(c);
3229 } // repeat cnt
3230 dc3:
3231 if (last_search_pattern == 0) {
3232 msg = (Byte *) "No previous regular expression";
3233 goto dc2;
3234 }
3235 if (last_search_pattern[0] == '/') {
3236 dir = FORWARD; // assume FORWARD search
3237 p = dot + 1;
3238 }
3239 if (last_search_pattern[0] == '?') {
3240 dir = BACK;
3241 p = dot - 1;
3242 }
3243 dc4:
3244 q = char_search(p, last_search_pattern + 1, dir, FULL);
3245 if (q != NULL) {
3246 dot = q; // good search, update "dot"
3247 msg = (Byte *) "";
3248 goto dc2;
3249 }
3250 // no pattern found between "dot" and "end"- continue at top
3251 p = text;
3252 if (dir == BACK) {
3253 p = end - 1;
3254 }
3255 q = char_search(p, last_search_pattern + 1, dir, FULL);
3256 if (q != NULL) { // found something
3257 dot = q; // found new pattern- goto it
3258 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3259 if (dir == BACK) {
3260 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3261 }
3262 } else {
3263 msg = (Byte *) "Pattern not found";
3264 }
3265 dc2:
3266 if (*msg) psbs("%s", msg);
3267 break;
3268 case '{': // {- move backward paragraph
3269 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3270 if (q != NULL) { // found blank line
3271 dot = next_line(q); // move to next blank line
3272 }
3273 break;
3274 case '}': // }- move forward paragraph
3275 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3276 if (q != NULL) { // found blank line
3277 dot = next_line(q); // move to next blank line
3278 }
3279 break;
3280 #endif /* FEATURE_VI_SEARCH */
3281 case '0': // 0- goto begining of line
3282 case '1': // 1-
3283 case '2': // 2-
3284 case '3': // 3-
3285 case '4': // 4-
3286 case '5': // 5-
3287 case '6': // 6-
3288 case '7': // 7-
3289 case '8': // 8-
3290 case '9': // 9-
3291 if (c == '0' && cmdcnt < 1) {
3292 dot_begin(); // this was a standalone zero
3293 } else {
3294 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3295 }
3296 break;
3297 case ':': // :- the colon mode commands
3298 p = get_input_line((Byte *) ":"); // get input line- use "status line"
3299 #if ENABLE_FEATURE_VI_COLON
3300 colon(p); // execute the command
3301 #else
3302 if (*p == ':')
3303 p++; // move past the ':'
3304 cnt = strlen((char *) p);
3305 if (cnt <= 0)
3306 break;
3307 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3308 strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines
3309 if (file_modified && p[1] != '!') {
3310 psbs("No write since last change (:quit! overrides)");
3311 } else {
3312 editing = 0;
3313 }
3314 } else if (strncasecmp((char *) p, "write", cnt) == 0
3315 || strncasecmp((char *) p, "wq", cnt) == 0
3316 || strncasecmp((char *) p, "wn", cnt) == 0
3317 || strncasecmp((char *) p, "x", cnt) == 0) {
3318 cnt = file_write(cfn, text, end - 1);
3319 if (cnt < 0) {
3320 if (cnt == -1)
3321 psbs("Write error: %s", strerror(errno));
3322 } else {
3323 file_modified = 0;
3324 last_file_modified = -1;
3325 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3326 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3327 p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3328 editing = 0;
3329 }
3330 }
3331 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3332 last_status_cksum = 0; // force status update
3333 } else if (sscanf((char *) p, "%d", &j) > 0) {
3334 dot = find_line(j); // go to line # j
3335 dot_skip_over_ws();
3336 } else { // unrecognised cmd
3337 ni((Byte *) p);
3338 }
3339 #endif /* !FEATURE_VI_COLON */
3340 break;
3341 case '<': // <- Left shift something
3342 case '>': // >- Right shift something
3343 cnt = count_lines(text, dot); // remember what line we are on
3344 c1 = get_one_char(); // get the type of thing to delete
3345 find_range(&p, &q, c1);
3346 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
3347 p = begin_line(p);
3348 q = end_line(q);
3349 i = count_lines(p, q); // # of lines we are shifting
3350 for ( ; i > 0; i--, p = next_line(p)) {
3351 if (c == '<') {
3352 // shift left- remove tab or 8 spaces
3353 if (*p == '\t') {
3354 // shrink buffer 1 char
3355 (void) text_hole_delete(p, p);
3356 } else if (*p == ' ') {
3357 // we should be calculating columns, not just SPACE
3358 for (j = 0; *p == ' ' && j < tabstop; j++) {
3359 (void) text_hole_delete(p, p);
3360 }
3361 }
3362 } else if (c == '>') {
3363 // shift right -- add tab or 8 spaces
3364 (void) char_insert(p, '\t');
3365 }
3366 }
3367 dot = find_line(cnt); // what line were we on
3368 dot_skip_over_ws();
3369 end_cmd_q(); // stop adding to q
3370 break;
3371 case 'A': // A- append at e-o-l
3372 dot_end(); // go to e-o-l
3373 //**** fall thru to ... 'a'
3374 case 'a': // a- append after current char
3375 if (*dot != '\n')
3376 dot++;
3377 goto dc_i;
3378 break;
3379 case 'B': // B- back a blank-delimited Word
3380 case 'E': // E- end of a blank-delimited word
3381 case 'W': // W- forward a blank-delimited word
3382 if (cmdcnt-- > 1) {
3383 do_cmd(c);
3384 } // repeat cnt
3385 dir = FORWARD;
3386 if (c == 'B')
3387 dir = BACK;
3388 if (c == 'W' || isspace(dot[dir])) {
3389 dot = skip_thing(dot, 1, dir, S_TO_WS);
3390 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3391 }
3392 if (c != 'W')
3393 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3394 break;
3395 case 'C': // C- Change to e-o-l
3396 case 'D': // D- delete to e-o-l
3397 save_dot = dot;
3398 dot = dollar_line(dot); // move to before NL
3399 // copy text into a register and delete
3400 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3401 if (c == 'C')
3402 goto dc_i; // start inserting
3403 #if ENABLE_FEATURE_VI_DOT_CMD
3404 if (c == 'D')
3405 end_cmd_q(); // stop adding to q
3406 #endif
3407 break;
3408 case 'G': // G- goto to a line number (default= E-O-F)
3409 dot = end - 1; // assume E-O-F
3410 if (cmdcnt > 0) {
3411 dot = find_line(cmdcnt); // what line is #cmdcnt
3412 }
3413 dot_skip_over_ws();
3414 break;
3415 case 'H': // H- goto top line on screen
3416 dot = screenbegin;
3417 if (cmdcnt > (rows - 1)) {
3418 cmdcnt = (rows - 1);
3419 }
3420 if (cmdcnt-- > 1) {
3421 do_cmd('+');
3422 } // repeat cnt
3423 dot_skip_over_ws();
3424 break;
3425 case 'I': // I- insert before first non-blank
3426 dot_begin(); // 0
3427 dot_skip_over_ws();
3428 //**** fall thru to ... 'i'
3429 case 'i': // i- insert before current char
3430 case VI_K_INSERT: // Cursor Key Insert
3431 dc_i:
3432 cmd_mode = 1; // start insrting
3433 break;
3434 case 'J': // J- join current and next lines together
3435 if (cmdcnt-- > 2) {
3436 do_cmd(c);
3437 } // repeat cnt
3438 dot_end(); // move to NL
3439 if (dot < end - 1) { // make sure not last char in text[]
3440 *dot++ = ' '; // replace NL with space
3441 file_modified++;
3442 while (isblnk(*dot)) { // delete leading WS
3443 dot_delete();
3444 }
3445 }
3446 end_cmd_q(); // stop adding to q
3447 break;
3448 case 'L': // L- goto bottom line on screen
3449 dot = end_screen();
3450 if (cmdcnt > (rows - 1)) {
3451 cmdcnt = (rows - 1);
3452 }
3453 if (cmdcnt-- > 1) {
3454 do_cmd('-');
3455 } // repeat cnt
3456 dot_begin();
3457 dot_skip_over_ws();
3458 break;
3459 case 'M': // M- goto middle line on screen
3460 dot = screenbegin;
3461 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3462 dot = next_line(dot);
3463 break;
3464 case 'O': // O- open a empty line above
3465 // 0i\n ESC -i
3466 p = begin_line(dot);
3467 if (p[-1] == '\n') {
3468 dot_prev();
3469 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3470 dot_end();
3471 dot = char_insert(dot, '\n');
3472 } else {
3473 dot_begin(); // 0
3474 dot = char_insert(dot, '\n'); // i\n ESC
3475 dot_prev(); // -
3476 }
3477 goto dc_i;
3478 break;
3479 case 'R': // R- continuous Replace char
3480 dc5:
3481 cmd_mode = 2;
3482 break;
3483 case 'X': // X- delete char before dot
3484 case 'x': // x- delete the current char
3485 case 's': // s- substitute the current char
3486 if (cmdcnt-- > 1) {
3487 do_cmd(c);
3488 } // repeat cnt
3489 dir = 0;
3490 if (c == 'X')
3491 dir = -1;
3492 if (dot[dir] != '\n') {
3493 if (c == 'X')
3494 dot--; // delete prev char
3495 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3496 }
3497 if (c == 's')
3498 goto dc_i; // start insrting
3499 end_cmd_q(); // stop adding to q
3500 break;
3501 case 'Z': // Z- if modified, {write}; exit
3502 // ZZ means to save file (if necessary), then exit
3503 c1 = get_one_char();
3504 if (c1 != 'Z') {
3505 indicate_error(c);
3506 break;
3507 }
3508 if (file_modified) {
3509 #if ENABLE_FEATURE_VI_READONLY
3510 if (vi_readonly || readonly) {
3511 psbs("\"%s\" File is read only", cfn);
3512 break;
3513 }
3514 #endif
3515 cnt = file_write(cfn, text, end - 1);
3516 if (cnt < 0) {
3517 if (cnt == -1)
3518 psbs("Write error: %s", strerror(errno));
3519 } else if (cnt == (end - 1 - text + 1)) {
3520 editing = 0;
3521 }
3522 } else {
3523 editing = 0;
3524 }
3525 break;
3526 case '^': // ^- move to first non-blank on line
3527 dot_begin();
3528 dot_skip_over_ws();
3529 break;
3530 case 'b': // b- back a word
3531 case 'e': // e- end of word
3532 if (cmdcnt-- > 1) {
3533 do_cmd(c);
3534 } // repeat cnt
3535 dir = FORWARD;
3536 if (c == 'b')
3537 dir = BACK;
3538 if ((dot + dir) < text || (dot + dir) > end - 1)
3539 break;
3540 dot += dir;
3541 if (isspace(*dot)) {
3542 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3543 }
3544 if (isalnum(*dot) || *dot == '_') {
3545 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3546 } else if (ispunct(*dot)) {
3547 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3548 }
3549 break;
3550 case 'c': // c- change something
3551 case 'd': // d- delete something
3552 #if ENABLE_FEATURE_VI_YANKMARK
3553 case 'y': // y- yank something
3554 case 'Y': // Y- Yank a line
3555 #endif
3556 yf = YANKDEL; // assume either "c" or "d"
3557 #if ENABLE_FEATURE_VI_YANKMARK
3558 if (c == 'y' || c == 'Y')
3559 yf = YANKONLY;
3560 #endif
3561 c1 = 'y';
3562 if (c != 'Y')
3563 c1 = get_one_char(); // get the type of thing to delete
3564 find_range(&p, &q, c1);
3565 if (c1 == 27) { // ESC- user changed mind and wants out
3566 c = c1 = 27; // Escape- do nothing
3567 } else if (strchr("wW", c1)) {
3568 if (c == 'c') {
3569 // don't include trailing WS as part of word
3570 while (isblnk(*q)) {
3571 if (q <= text || q[-1] == '\n')
3572 break;
3573 q--;
3574 }
3575 }
3576 dot = yank_delete(p, q, 0, yf); // delete word
3577 } else if (strchr("^0bBeEft$", c1)) {
3578 // single line copy text into a register and delete
3579 dot = yank_delete(p, q, 0, yf); // delete word
3580 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3581 // multiple line copy text into a register and delete
3582 dot = yank_delete(p, q, 1, yf); // delete lines
3583 if (c == 'c') {
3584 dot = char_insert(dot, '\n');
3585 // on the last line of file don't move to prev line
3586 if (dot != (end-1)) {
3587 dot_prev();
3588 }
3589 } else if (c == 'd') {
3590 dot_begin();
3591 dot_skip_over_ws();
3592 }
3593 } else {
3594 // could not recognize object
3595 c = c1 = 27; // error-
3596 indicate_error(c);
3597 }
3598 if (c1 != 27) {
3599 // if CHANGING, not deleting, start inserting after the delete
3600 if (c == 'c') {
3601 strcpy((char *) buf, "Change");
3602 goto dc_i; // start inserting
3603 }
3604 if (c == 'd') {
3605 strcpy((char *) buf, "Delete");
3606 }
3607 #if ENABLE_FEATURE_VI_YANKMARK
3608 if (c == 'y' || c == 'Y') {
3609 strcpy((char *) buf, "Yank");
3610 }
3611 p = reg[YDreg];
3612 q = p + strlen((char *) p);
3613 for (cnt = 0; p <= q; p++) {
3614 if (*p == '\n')
3615 cnt++;
3616 }
3617 psb("%s %d lines (%d chars) using [%c]",
3618 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3619 #endif
3620 end_cmd_q(); // stop adding to q
3621 }
3622 break;
3623 case 'k': // k- goto prev line, same col
3624 case VI_K_UP: // cursor key Up
3625 if (cmdcnt-- > 1) {
3626 do_cmd(c);
3627 } // repeat cnt
3628 dot_prev();
3629 dot = move_to_col(dot, ccol + offset); // try stay in same col
3630 break;
3631 case 'r': // r- replace the current char with user input
3632 c1 = get_one_char(); // get the replacement char
3633 if (*dot != '\n') {
3634 *dot = c1;
3635 file_modified++; // has the file been modified
3636 }
3637 end_cmd_q(); // stop adding to q
3638 break;
3639 case 't': // t- move to char prior to next x
3640 last_forward_char = get_one_char();
3641 do_cmd(';');
3642 if (*dot == last_forward_char)
3643 dot_left();
3644 last_forward_char= 0;
3645 break;
3646 case 'w': // w- forward a word
3647 if (cmdcnt-- > 1) {
3648 do_cmd(c);
3649 } // repeat cnt
3650 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3651 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3652 } else if (ispunct(*dot)) { // we are on PUNCT
3653 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3654 }
3655 if (dot < end - 1)
3656 dot++; // move over word
3657 if (isspace(*dot)) {
3658 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3659 }
3660 break;
3661 case 'z': // z-
3662 c1 = get_one_char(); // get the replacement char
3663 cnt = 0;
3664 if (c1 == '.')
3665 cnt = (rows - 2) / 2; // put dot at center
3666 if (c1 == '-')
3667 cnt = rows - 2; // put dot at bottom
3668 screenbegin = begin_line(dot); // start dot at top
3669 dot_scroll(cnt, -1);
3670 break;
3671 case '|': // |- move to column "cmdcnt"
3672 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3673 break;
3674 case '~': // ~- flip the case of letters a-z -> A-Z
3675 if (cmdcnt-- > 1) {
3676 do_cmd(c);
3677 } // repeat cnt
3678 if (islower(*dot)) {
3679 *dot = toupper(*dot);
3680 file_modified++; // has the file been modified
3681 } else if (isupper(*dot)) {
3682 *dot = tolower(*dot);
3683 file_modified++; // has the file been modified
3684 }
3685 dot_right();
3686 end_cmd_q(); // stop adding to q
3687 break;
3688 //----- The Cursor and Function Keys -----------------------------
3689 case VI_K_HOME: // Cursor Key Home
3690 dot_begin();
3691 break;
3692 // The Fn keys could point to do_macro which could translate them
3693 case VI_K_FUN1: // Function Key F1
3694 case VI_K_FUN2: // Function Key F2
3695 case VI_K_FUN3: // Function Key F3
3696 case VI_K_FUN4: // Function Key F4
3697 case VI_K_FUN5: // Function Key F5
3698 case VI_K_FUN6: // Function Key F6
3699 case VI_K_FUN7: // Function Key F7
3700 case VI_K_FUN8: // Function Key F8
3701 case VI_K_FUN9: // Function Key F9
3702 case VI_K_FUN10: // Function Key F10
3703 case VI_K_FUN11: // Function Key F11
3704 case VI_K_FUN12: // Function Key F12
3705 break;
3706 }
3707
3708 dc1:
3709 // if text[] just became empty, add back an empty line
3710 if (end == text) {
3711 (void) char_insert(text, '\n'); // start empty buf with dummy line
3712 dot = text;
3713 }
3714 // it is OK for dot to exactly equal to end, otherwise check dot validity
3715 if (dot != end) {
3716 dot = bound_dot(dot); // make sure "dot" is valid
3717 }
3718 #if ENABLE_FEATURE_VI_YANKMARK
3719 check_context(c); // update the current context
3720 #endif
3721
3722 if (!isdigit(c))
3723 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3724 cnt = dot - begin_line(dot);
3725 // Try to stay off of the Newline
3726 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3727 dot--;
3728 }
3729
3730 #if ENABLE_FEATURE_VI_CRASHME
3731 static int totalcmds = 0;
3732 static int Mp = 85; // Movement command Probability
3733 static int Np = 90; // Non-movement command Probability
3734 static int Dp = 96; // Delete command Probability
3735 static int Ip = 97; // Insert command Probability
3736 static int Yp = 98; // Yank command Probability
3737 static int Pp = 99; // Put command Probability
3738 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3739 char chars[20] = "\t012345 abcdABCD-=.$";
3740 char *words[20] = { "this", "is", "a", "test",
3741 "broadcast", "the", "emergency", "of",
3742 "system", "quick", "brown", "fox",
3743 "jumped", "over", "lazy", "dogs",
3744 "back", "January", "Febuary", "March"
3745 };
3746 char *lines[20] = {
3747 "You should have received a copy of the GNU General Public License\n",
3748 "char c, cm, *cmd, *cmd1;\n",
3749 "generate a command by percentages\n",
3750 "Numbers may be typed as a prefix to some commands.\n",
3751 "Quit, discarding changes!\n",
3752 "Forced write, if permission originally not valid.\n",
3753 "In general, any ex or ed command (such as substitute or delete).\n",
3754 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3755 "Please get w/ me and I will go over it with you.\n",
3756 "The following is a list of scheduled, committed changes.\n",
3757 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3758 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3759 "Any question about transactions please contact Sterling Huxley.\n",
3760 "I will try to get back to you by Friday, December 31.\n",
3761 "This Change will be implemented on Friday.\n",
3762 "Let me know if you have problems accessing this;\n",
3763 "Sterling Huxley recently added you to the access list.\n",
3764 "Would you like to go to lunch?\n",
3765 "The last command will be automatically run.\n",
3766 "This is too much english for a computer geek.\n",
3767 };
3768 char *multilines[20] = {
3769 "You should have received a copy of the GNU General Public License\n",
3770 "char c, cm, *cmd, *cmd1;\n",
3771 "generate a command by percentages\n",
3772 "Numbers may be typed as a prefix to some commands.\n",
3773 "Quit, discarding changes!\n",
3774 "Forced write, if permission originally not valid.\n",
3775 "In general, any ex or ed command (such as substitute or delete).\n",
3776 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3777 "Please get w/ me and I will go over it with you.\n",
3778 "The following is a list of scheduled, committed changes.\n",
3779 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3780 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3781 "Any question about transactions please contact Sterling Huxley.\n",
3782 "I will try to get back to you by Friday, December 31.\n",
3783 "This Change will be implemented on Friday.\n",
3784 "Let me know if you have problems accessing this;\n",
3785 "Sterling Huxley recently added you to the access list.\n",
3786 "Would you like to go to lunch?\n",
3787 "The last command will be automatically run.\n",
3788 "This is too much english for a computer geek.\n",
3789 };
3790
3791 // create a random command to execute
3792 static void crash_dummy()
3793 {
3794 static int sleeptime; // how long to pause between commands
3795 char c, cm, *cmd, *cmd1;
3796 int i, cnt, thing, rbi, startrbi, percent;
3797
3798 // "dot" movement commands
3799 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3800
3801 // is there already a command running?
3802 if (readed_for_parse > 0)
3803 goto cd1;
3804 cd0:
3805 startrbi = rbi = 0;
3806 sleeptime = 0; // how long to pause between commands
3807 memset(readbuffer, '\0', BUFSIZ); // clear the read buffer
3808 // generate a command by percentages
3809 percent = (int) lrand48() % 100; // get a number from 0-99
3810 if (percent < Mp) { // Movement commands
3811 // available commands
3812 cmd = cmd1;
3813 M++;
3814 } else if (percent < Np) { // non-movement commands
3815 cmd = "mz<>\'\""; // available commands
3816 N++;
3817 } else if (percent < Dp) { // Delete commands
3818 cmd = "dx"; // available commands
3819 D++;
3820 } else if (percent < Ip) { // Inset commands
3821 cmd = "iIaAsrJ"; // available commands
3822 I++;
3823 } else if (percent < Yp) { // Yank commands
3824 cmd = "yY"; // available commands
3825 Y++;
3826 } else if (percent < Pp) { // Put commands
3827 cmd = "pP"; // available commands
3828 P++;
3829 } else {
3830 // We do not know how to handle this command, try again
3831 U++;
3832 goto cd0;
3833 }
3834 // randomly pick one of the available cmds from "cmd[]"
3835 i = (int) lrand48() % strlen(cmd);
3836 cm = cmd[i];
3837 if (strchr(":\024", cm))
3838 goto cd0; // dont allow colon or ctrl-T commands
3839 readbuffer[rbi++] = cm; // put cmd into input buffer
3840
3841 // now we have the command-
3842 // there are 1, 2, and multi char commands
3843 // find out which and generate the rest of command as necessary
3844 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
3845 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3846 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
3847 cmd1 = "abcdefghijklmnopqrstuvwxyz";
3848 }
3849 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3850 c = cmd1[thing];
3851 readbuffer[rbi++] = c; // add movement to input buffer
3852 }
3853 if (strchr("iIaAsc", cm)) { // multi-char commands
3854 if (cm == 'c') {
3855 // change some thing
3856 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3857 c = cmd1[thing];
3858 readbuffer[rbi++] = c; // add movement to input buffer
3859 }
3860 thing = (int) lrand48() % 4; // what thing to insert
3861 cnt = (int) lrand48() % 10; // how many to insert
3862 for (i = 0; i < cnt; i++) {
3863 if (thing == 0) { // insert chars
3864 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3865 } else if (thing == 1) { // insert words
3866 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3867 strcat((char *) readbuffer, " ");
3868 sleeptime = 0; // how fast to type
3869 } else if (thing == 2) { // insert lines
3870 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3871 sleeptime = 0; // how fast to type
3872 } else { // insert multi-lines
3873 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3874 sleeptime = 0; // how fast to type
3875 }
3876 }
3877 strcat((char *) readbuffer, "\033");
3878 }
3879 readed_for_parse = strlen(readbuffer);
3880 cd1:
3881 totalcmds++;
3882 if (sleeptime > 0)
3883 (void) mysleep(sleeptime); // sleep 1/100 sec
3884 }
3885
3886 // test to see if there are any errors
3887 static void crash_test()
3888 {
3889 static time_t oldtim;
3890 time_t tim;
3891 char d[2], msg[BUFSIZ];
3892
3893 msg[0] = '\0';
3894 if (end < text) {
3895 strcat((char *) msg, "end<text ");
3896 }
3897 if (end > textend) {
3898 strcat((char *) msg, "end>textend ");
3899 }
3900 if (dot < text) {
3901 strcat((char *) msg, "dot<text ");
3902 }
3903 if (dot > end) {
3904 strcat((char *) msg, "dot>end ");
3905 }
3906 if (screenbegin < text) {
3907 strcat((char *) msg, "screenbegin<text ");
3908 }
3909 if (screenbegin > end - 1) {
3910 strcat((char *) msg, "screenbegin>end-1 ");
3911 }
3912
3913 if (strlen(msg) > 0) {
3914 alarm(0);
3915 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3916 totalcmds, last_input_char, msg, SOs, SOn);
3917 fflush(stdout);
3918 while (read(0, d, 1) > 0) {
3919 if (d[0] == '\n' || d[0] == '\r')
3920 break;
3921 }
3922 alarm(3);
3923 }
3924 tim = (time_t) time((time_t *) 0);
3925 if (tim >= (oldtim + 3)) {
3926 sprintf((char *) status_buffer,
3927 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3928 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3929 oldtim = tim;
3930 }
3931 return;
3932 }
3933 #endif