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