Magellan Linux

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

Parent Directory Parent Directory | Revision Log Revision Log


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