Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


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